ManyToOne mapping and findingBySchoolId - spring-boot

I am trying to create teacher and school objects where 1 school can have multiple teachers, but each teacher works at one school. I am tying to make a query where I get only teachers that work at certain school by getting its id as a parameter.
School object
#Getter
#Setter
#Entity
#Table(name="school")
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class School {
#Id
#SequenceGenerator(
name = "school_sequence",
sequenceName = "school_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "school_sequence"
)
#Column(name="id")
private Long id;
private String name;
#JsonIgnore
#OneToMany//(mappedBy = "school")
private List<Teacher> teacher;
}
Teacher object
#Getter
#Setter
#Entity
#Table(name="teacher")
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class Teacher {
#Id
#SequenceGenerator(
name = "teacher_sequence",
sequenceName = "teacher_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "teacher_sequence"
)
#Column(name="id")
private Long id;
private String name;
private String email;
/* #Column(name="schoolId")
private Long schoolId;*/
//(cascade = CascadeType.ALL) Don't use this! it will prevent you to have different teacher queries with
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "schoolId", referencedColumnName = "id") //the same school ids.
private School school;
}
Teacher controller
#RestController
#RequestMapping(path = "api/v1/teacher")
public class TeacherController {
private final TeacherService teacherService;
#Autowired
public TeacherController(TeacherService teacherService) {
this.teacherService = teacherService;
}
#GetMapping
public List<Teacher> getTeacher(){
return teacherService.getTeacher();
}
#GetMapping(path = "schoolID")
public List<Teacher> getTeacherBySchool(#PathVariable("schoolId") Long schoolId, School school){
return teacherService.getTeacherBySchool(schoolId, school);
}
Teacher Service
#Service
public class TeacherService {
private final TeacherRepository teacherRepository;
#Autowired
public TeacherService(TeacherRepository teacherRepository) {
this.teacherRepository = teacherRepository;
}
public List<Teacher> getTeacher(){
return teacherRepository.findAll();
}
//public List<Teacher> getTeacherBySchool
How would I implement this on teacher controller and teacher service?
Update on my progress!!!
Teacher Controller
#GetMapping(path = "/teachers/{schoolID}")
public List<Teacher> getTeacherBySchool(#PathVariable("schoolID") Long schoolId){
School school = new School();
school.setId(schoolId);
return teacherService.getTeacherBySchool(school);
}
Teacher Service
public List<Teacher> getTeacherBySchool(Long schoolId){
return teacherRepository.findBySchool(school);
}
Teacher Repository
#Repository
public interface TeacherRepository extends JpaRepository<Teacher, Long> {
#Query
List<Teacher> findBySchool(List<School> school);
These changes do what I want them to do, but as I said. It is not a good coding practice. What I want is that less code on TeacherController, and make my TeacherService communicate with SchoolService to get the right schools.

I think you can just add the following to your repository:
public List<Teacher> findBySchoolId(Long schoolId);
Or just write the query yourself:
entityManager.createQuery("FROM Teacher t WHERE t.school.id = :schoolId")
.setParameter("schoolId", schoolId)
.getResultList();

Related

Spring Boot JPA Using Many-to-Many relationship with additional attributes in the join table

I have two simple classes Student and Course. I am trying to set up many to many relationship between these classes. I want to use additional table whose PRIMARY KEY is the combination of the primary keys of student and course tables (student_id and course_id).
The student class:
#Entity
#Table(name = "student")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#OneToMany(mappedBy = "student")
private Set<CourseStudent> courses;
}
The course class:
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String courseName;
#OneToMany(mappedBy = "course")
Set<CourseStudent> students;
}
The entity that stores the relationship between course and the student:
#Entity
#NoArgsConstructor
#Data
public class CourseStudent {
#EmbeddedId
CourseStudentKey id;
#ManyToOne
#MapsId("studentId")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("courseId")
#JoinColumn(name = "course_id")
Course course;
public CourseStudent(Student student, Course course) {
this.student = student;
this.course = course;
this.rating = 0;
}
int rating;
}
Attention: Since I want to have additional features in this entity (for example, storing the rating of the students for courses), I don't want to use #JoinTable idea that we implement in the Student class.
Since I have multiple attributes in the primary key of CourseStudent entity, I used the following class
#Embeddable
#Data
public class CourseStudentKey implements Serializable {
#Column(name = "student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
}
I have the following POST request to insert the student into a course:
#PostMapping("/insert/students/{studentId}/courses/{courseId}")
public CourseStudent insertStudentIntoCourse(#PathVariable(value = "studentId") Long studentId,
#PathVariable(value = "courseId") Long courseId) {
if (!studentRepository.existsById(studentId)) {
throw new ResourceNotFoundException("Student id " + studentId + " not found");
}
if (!courseRepository.existsById(courseId)) {
throw new ResourceNotFoundException("Course id " + courseId + " not found");
}
CourseStudent courseStudent = new CourseStudent(
studentRepository.findById(studentId).get(),
courseRepository.findById(courseId).get()
);
return courseStudentRepository.save(courseStudent);
}
I have manually added Student and the Course into my local database and send this request by using Postman.
http://localhost:8080/insert/students/1/courses/1
However, I get the following error:
{
"timestamp": "2022-08-04T12:33:18.547+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/insert/students/1/courses/1"
}
In the console, I get NullPointerException. What is the thing I am doing wrong here?

FetchType.LAZY in ManyToMany doesnot work

I'm having a problem with, fetch = FetchType.LAZY, it just doesn't work. I've already spent a lot of time solving this problem, can anyone help with this? I'll be very thankful. I have a genre and a country that are associated with movie manyTomany. No matter how hard I try to initialize the LAZY download, it doesn't work.I need the movie to have EAGER, and the genre and country to have LAZY.
I expect to get movie with its genre and country, But with SELECT * FROM movie WHERE id = 1 - I get an endless loop, although genre and country has LAZY download.
Sample code - below
Entities:
Movie
#Entity
#Getter
#Setter
#ToString(of = {"id", "year", "name"})
#EqualsAndHashCode(of = {"id", "year"})
#NoArgsConstructor
#AllArgsConstructor
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
**********
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "movie_genre",
joinColumns = {
#JoinColumn(name = "movie_id")},
inverseJoinColumns = {
#JoinColumn(name = "genre_id")})
private Set<Genre> genres = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "movie_country",
joinColumns = {
#JoinColumn(name = "movie_id")},
inverseJoinColumns = {
#JoinColumn(name = "country_id")})
private Set<Country> countries = new HashSet<>();
}
Genre
#Entity
#Getter
#Setter
#ToString(exclude = "movies")
#EqualsAndHashCode(exclude = "movies")
#NoArgsConstructor
#AllArgsConstructor
public class Genre {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Size(max = 20)
private String name;
#ManyToMany(mappedBy = "genres")
private Set<Movie> movies = new HashSet<>();
}
Country
#Entity
#Getter
#Setter
#ToString(exclude = "movies")
#EqualsAndHashCode(exclude = "movies")
#NoArgsConstructor
#AllArgsConstructor
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Size(max = 20)
private String name;
#ManyToMany(mappedBy = "countries")
private Set<Movie> movies = new HashSet<>();
}
Controller
#RestController
public class TestController {
#Autowired
private MovieService movieService;
#Autowired
private CountryService countryService;
#Autowired
private GenreService genreService;
#GetMapping("movie")
public List<Movie> getMovieMovie(){
return movieService.getAll();
}
#GetMapping("movie/add")
public Movie create(){
Movie movie = new Movie();
movie.setName("test");
movie.setImg("test");
movie.setTime("test");
movie.setYear((short) 2332);
movie.setMovieLink("test");
movie.getCountries().add(countryService.getCountry(1));
movie.getGenres().add(genreService.getGenre(1));
return movieService.create(movie);
}
}
Service
#Service
public class MovieService {
#Autowired
private MovieRepository movieRepository;
public List<Movie> getAll(){
return movieRepository.findAll();
}
#Transactional
public Movie create(Movie mocie){
return movieRepository.save(mocie);
}
}
Lazy loading works as expected, as it loads all data lazy.
What you are looking for is a way to break loop in the bi-directional mapping.
There you can use #JsonManagedReference and #JsonBackReference that you have to set on the relationships.
Please also read this: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion

RequestDto keep getting Null in Jpa

I'm trying to make invoice in my project.to make it, I need to POST all info that I already put in the DB. I'm trying to use #RequestBody using by requestDto but it keep getting null.
#Data
#Getter
public class InvoiceRequestDto {
private String note;
private AddressRequest addressRequest;
private BuyerRequest buyerRequest;
private SellerRequest sellerRequest;
private OrderRequest orderRequest;
other request in InvoiceRequestDto also look like this.
#Data
#Getter
public class BuyerRequest {
private String companyName;
private String email;
private String buyerManager;
private String buyerManagerNumber;
private String faxNumber;
this is service, I debugged in here and getting null from all requestDto in InvoiceRequestDto.
#Transactional
public Invoice postInvoice(InvoiceRequestDto invoiceRequestDto) {
try {
Buyer buyerPost = buyerRepository.findByBuyerManager(invoiceRequestDto.getBuyerRequest().getBuyerManager());
Seller sellerPost = sellerRepository.findBySellerManager(invoiceRequestDto.getSellerRequest().getSellerManager());
OrderItem orderPost = orderRepository.getByOrderNumber(invoiceRequestDto.getOrderRequest().getOrderNumber());
Invoice newInvoice = new Invoice(invoiceRequestDto.getNote(), orderPost, buyerPost, sellerPost);
Invoice saved = invoiceRepository.save(newInvoice);
return saved;
} catch (Exception e) {
e.printStackTrace();
return null;
}
this is controller.
#PostMapping("api/order/new")
public ResponseEntity<Long> postInvoice(#RequestBody InvoiceRequestDto invoiceRequestDto){
Long result = invoiceService.postInvoice(invoiceRequestDto).getId();
return ResponseEntity.ok(result);
this is Invoice Entity.
#Getter
#Entity
public class Invoice extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "invoice_id")
private Long id;
#ManyToOne
#JoinColumn(name = "buyer_id")
private Buyer buyer;
#ManyToOne
#JoinColumn(name = "seller_id")
private Seller seller;
#ManyToOne
#JoinColumn(name = "product_id")
private Product product;
#ManyToOne
#JoinColumn(name = "delivery_id")
private Delivery delivery;
#ManyToOne
#JoinColumn(name = "orderItem_id")
private OrderItem orderItem;
private boolean finalized;
private String note;
#Builder
public Invoice(String note, OrderItem orderPost, Buyer buyerPost, Seller sellerPost){
this.note = note;
this.orderItem = orderPost;
this.buyer = buyerPost;
this.seller = sellerPost;
}
Instead of using external class like this
#Data
#Getter
public class BuyerRequest {
private String companyName;
private String email;
private String buyerManager;
private String buyerManagerNumber;
private String faxNumber;
Try to use inner static classes in your InvoiceRequestDto like below and try again.
#Data
#AllArgsConstructor
#NoArgsConstructor
public class InvoiceRequestDto {
private String note;
private AddressRequest addressRequest;
private BuyerRequest buyerRequest;
private SellerRequest sellerRequest;
private OrderRequest orderRequest;
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class AddressRequest {
// neccessary fields
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class BuyerRequest {
// neccessary fields
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class SellerRequest {
// neccessary fields
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class OrderRequest {
// neccessary fields
}
}

Springboot add problem in oneTOMany relation

I'm writing 3 tables in the following relation:
Club class:
#Setter
#Getter
#Entity
#Table(name = "Club")
public class Club {
#Id
#GeneratedValue
private Long id;
private String name;
private String type;
private String mainPage;
private String logo;
#OneToMany(mappedBy="clubProductKey.club", cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "clubProductKey.club", allowSetters=true)
private Set<ClubProduct> clubProducts;
...
Product class:
#Setter
#Getter
#Entity
#Table(name = "Product")
public class Product {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy="clubProductKey.product", cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "clubProductKey.product", allowSetters=true)
private Set<ClubProduct> clubProducts;
...
ClubProduct class:
#Setter
#Getter
#Entity
#Table(name = "ClubProduct")
public class ClubProduct {
#EmbeddedId
private ClubProductKey clubProductKey;
...
ClubProductKey class:
#Setter
#Getter
#Embeddable
public class ClubProductKey implements Serializable {
#ManyToOne(cascade = {CascadeType.MERGE,CascadeType.REFRESH })
#JoinColumn(name = "club_id", referencedColumnName = "id")
#JsonIgnoreProperties(value = "clubProducts", allowSetters=true)
private Club club;
#ManyToOne(cascade = {CascadeType.MERGE,CascadeType.REFRESH })
#JoinColumn(name = "product_id", referencedColumnName = "id")
#JsonIgnoreProperties(value = "clubProducts", allowSetters=true)
private Product product;
...
ClubProductRepository class:
public interface ClubProductRepository extends JpaRepository<ClubProduct, ClubProductKey> {
public List<ClubProduct> findByClubProductKeyClub(Club club);
public List<ClubProduct> findByClubProductKeyProduct(Product product);
}
I try to save clubProduct like this:
#Service
public class ClubProductServiceImp implements ClubProductService {
#Autowired
private ClubProductRepository clubProductRepository;
...
ClubProduct savedClubProduct = clubProductRepository.save(clubProduct);
return savedClubProduct;
}
However I find that the clubProduct is not saved in the clubProducts list in the club or product entity, the list is null. Must I add lines like club.getClubProducts.add(clubProduct) or is there any other way to make it added automatically?
Thank you.
The #OnetoMany mapping in your Club class uses the attribute mappedby which means that it represents the owning side of the relation responsible for handling the mapping. However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.
The answer is yes, you have to manage the java relations yourself so that the clubProducts gets persisted. You are using an instance of the repository class club to persist the data so , you should add a setter method like :
public void addClubProduct(ClubProduct clubProduct) {
if (clubProduct!= null) {
if (clubProduct== null) {
clubProduct= new ArrayList<ClubProduct>();
}
clubProducts.add(clubProduct);
clubProduct.setClubProduct(this);
}
}
also a method to remove it from the list and use these method in your code to set the values to the list properly before initiating save . Read related article

Why is my mapped DTO List null? What is the best way to map and persist Child Lists?

I have a simple problem - but I think "I am standing on the tube".
I have a spring boot rest api with JPA, Modelmapper, Entities and DTOs.
But the mapping doesn't work.
Entities:
#Getter
#Setter
#MappedSuperclass
public class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
#Getter
#Setter
#Entity(name = "contacts")
public class Contact extends AbstractEntity {
#NotBlank
private String firstName;
#NotBlank
private String lastName;
#Valid
#OneToMany(mappedBy = "contact", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PhoneNumber> phoneNumberList;
}
#Getter
#Setter
#Entity(name = "phone_numbers")
public class PhoneNumber extends AbstractEntity {
#NotBlank
private String label;
#NotBlank
private String number;
#ManyToOne
#JoinColumn(name = "contact_id", referencedColumnName = "id")
#Setter(value = AccessLevel.NONE)
private Contact contact;
}
The DTOs:
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class ContactDTO {
private Long id;
private String firstName;
private String lastName;
List<PhoneNumberDTO> phoneNumberDTOList = new ArrayList<>();
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class PhoneNumberDTO {
private Long id;
private String label;
private String number;
}
My ModelMapperConfig:
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PRIVATE);
return modelMapper;
}
Repo:
public interface ContactRepository extends JpaRepository<Contact, Long{
}
Service (only the create method):
#Override
public ContactDTO createOne(ContactDTO contactDTO) {
Contact contact = modelMapper.map(contactDTO, Contact.class);
contactRepository.save(contact);
return contactDTO;
}
Is this the correct way to persist the Contact with its multiple phonenumbers?
And how can I create a simple mapping?
If i want to persist it, there comes an error:
Column 'contact_id' cannot be null

Resources