hi everyone i just started to learn spring boot and was wondering how can i save objects with many to many relationship through form submission?
say we have two entities of book and publisher
#Entity
public class Book{
private long id;
private String name;
private List<Publisher> publishers;
public Book() {
}
public Book(String name) {
this.name = name;
}
public Book(String name, Set<Publisher> publishers){
this.name = name;
this.publishers = publishers;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "book_publisher", joinColumns = #JoinColumn(name = "book_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "publisher_id", referencedColumnName = "id"))
public List<Publisher> getPublishers() {
return publishers;
}
public void setPublishers(List<Publisher> publishers) {
this.publishers = publishers;
}
}
#Entity
public class Publisher {
private Long id;
private String name;
private List<Book> books;
public Publisher(){
}
public Publisher(String name){
this.name = name;
}
public Publisher(String name, List<Book> books){
this.name = name;
this.books = books;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#ManyToMany(mappedBy = "publishers")
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
}
and then we have a book repository
public interface BookRepository extends CRUDRepository<Book, Long>{
}
how would crud methods look like in bookcontroller?
the real question is the "connection table" has additional data , for example date the publishing of the book by the publisher , ISBN of the book from that publisher and so on ...
if the answer is yes then it requires another form , and that solves your issue , if not , you receive from the form list of publishers of the book you enter and add them separately to the connection table and the book to the book table through the book repository ...
from my point of view the design of the database is lacking info.
when you will answer to that question, ill try to help you to do it the spring way ...
currenly from db point of view to make such connection between objects you need existing objects . and the first stage fromwise is form for Publisher and form for Book.
many to many is known to be a headache .
i would suggest to add JPA tag to the question, and remove the spring-data .
Related
I'm experimenting with mapstruct and follow this tutorial:
mapstruct tut
I have this entity:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_company")
#SequenceGenerator(name = "seq_company", allocationSize = 1)
#Column(nullable = false, updatable = false)
private Long id;
private String name;
private String shortName;
public Company() {
}
public Company(Long id, String name, String shortName) {
this.id = id;
this.name = name;
this.shortName = shortName;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
}
And this is the simple dto:
public class CompanyDto {
#JsonProperty("id")
private Long id;
#JsonProperty("name")
private String name;
#JsonProperty("shortName")
private String shortName;
}
And here is the mapper interface:
#Mapper(componentModel = "spring")
public interface CompanyMapper {
CompanyDto companyToCompanyDto(Company company);
Company companyDtoToCompany(CompanyDto companyDto);
List<CompanyDto> companiesToCompanyDtos(List<Company> companies);
}
I certanly oversee something, because there is no setters in the generated implementation, f. e.:
#Override
public Company companyDtoToCompany(CompanyDto companyDto) {
if ( companyDto == null ) {
return null;
}
Company company = new Company();
return company;
}
What goes here wrong?
I've noticed that your CompanyDto class has private fields but no getters or setters. There is no standard way to access the fields in that class. You might need to add those in order to map in or out of that class.
I'm trying to map 2 entities (Course and Student), I have 2 Java classes and 2 MySQL tables, having a ManyToMany relationship. I created the junction table and java class Enrolment (as I want extra information such as the date of enrolment of a student to a course).
I'm trying to insert data using hibernate in this Enrolments table in the MySQL but I keep getting errors. Here are my POJO classes:
Course class:
#Entity
#Table(name = "course")
public class Course {
private int id;
#Column(name = "chapter_id")
private int chapterId;;
#Column(name = "name")
private String title;
#Column(name = "teacher_user_id")
private int teacherId;
#OneToMany(targetEntity=Enrolment.class, mappedBy="course", fetch=FetchType.LAZY)
// #JoinTable(name = "enrolment",
// joinColumns = #JoinColumn(name = "course_id"),
// inverseJoinColumns = #JoinColumn(name = "student_user_id"))
private List<Enrolment> enrolments = new ArrayList<Enrolment>();
public Course(){}
public Course(int id, int chapterId, String title, int teacherId) {
super();
this.id = id;
this.chapterId = chapterId;
this.title = title;
this.teacherId = teacherId;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getChapterId() {
return chapterId;
}
public void setChapterId(int chapterId) {
this.chapterId = chapterId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getTeacherId() {
return teacherId;
}
public void setTeacherId(int teacherId) {
this.teacherId = teacherId;
}
#OneToMany(mappedBy = "course")
public List<Enrolment> getEnrolments() {
return enrolments;
}
public void setEnrolments(List<Enrolment> courses) {
this.enrolments = courses;
}
public void addEnrolment(Enrolment enrolment) {
this.enrolments.add(enrolment);
}
}
Student class (this class is inherited from User parent class, I will attach User Class down below as well. In the database there are different tables as well: User and then Student and Teacher that inherit User parent entity):
#Entity
#Table(name = "student")
#PrimaryKeyJoinColumn(name = "user_id")
#OnDelete(action = OnDeleteAction.CASCADE)
public class Student extends User {
private int grade;
private List<Enrolment> enrolments = new ArrayList<Enrolment>();
public Student(){}
public Student(String fname, String lname, String email, String password, String address, String phone,
int userType, int grade, boolean isAdmin)
{
super(fname, lname, email, password, address, phone, userType, isAdmin);
this.grade=grade;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public void setEnrolments(List<Enrolment> courses) {
this.enrolments = courses;
}
#OneToMany(mappedBy = "student")
public List<Enrolment> getEnrolments() {
return enrolments;
}
public void addCourse(Enrolment course) {
this.enrolments.add(course);
}
public void addEnrolment(Enrolment enrolment) {
this.enrolments.add(enrolment);
}
}
User Class:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String firstname;
private String lastname;
private String email;
private String password;
private String address;
private String phone;
#Column(name = "user_type_id")
private int userType;
#Column(name = "is_admin")
private boolean isAdmin;
public User(String fname, String lname, String email, String password, String address, String phone,
int userType, boolean isAdmin) {
//super();
this.firstname = fname;
this.lastname = lname;
this.email = email;
this.password = password;
this.address = address;
this.phone = phone;
this.userType = userType;
this.isAdmin = isAdmin;
}
public User() {}
//getters & setters
And finally this is the Enrolment class:
#Entity
#Table(name = "enrolment")
public class Enrolment {
private int id;
private Student user;
private Course course;
#Column(name = "enrolment_date")
private Date enrolmentDate;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "student_user_id")
public User getUser() {
return user;
}
public void setUser(Student user) {
this.user = user;
}
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "course_id")
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
public Date getEnrolmentDate() {
return enrolmentDate;
}
public void setEnrolmentDate(Date enrolmentDate) {
this.enrolmentDate = enrolmentDate;
}
So I'm trying to read a course and a student from database and insert the information in this Enrolment table but it gives errors since trying to read a Course. Here is the DAO method:
#SuppressWarnings("deprecation")
#Transactional
public List<Course> getCoursesOfChapter(int chapterId) {
Configuration con = new Configuration().configure("hibernate.cfg.xml").addAnnotatedClass(Course.class);
SessionFactory sf = con.buildSessionFactory();
Session session = sf.openSession();
Transaction tx = session.beginTransaction();
Query query = session.createQuery("from Course where chapter_id = :chapterId");
query.setParameter("chapterId",chapterId);
// List list = query.list();
tx.commit();
return (List<Course>) query.list();
It throws the error at the session factory building:
Exception in thread "main" org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: models.Course.enrolments[models.Enrolment]
at org.hibernate.cfg.annotations.CollectionBinder.bindManyToManySecondPass(CollectionBinder.java:1255)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:808)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:733)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:54)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1696)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1664)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:287)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:84)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:474)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:85)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:689)
at dao.CourseDAO.getCourse(CourseDAO.java:52)
at webapp.Main.main(Main.java:132)
Finally, this is my call:
CourseDAO cdao = new CourseDAO();
Course course = cdao.getCourse(1);
I've tried playing with the annotations, make them ManyToMany instead of ManyToOne. I tried to map the User class instead of Student but still didn't work. I tried to make it without the junction class of Enrolment and just generate it without having an actual class for it but still didn't work (as I had to work with 2 session.save() methods one after the other which also gave some error that I couldn't solve). Probably it's a small thing that I'm missing here but I just can't figure it out, sorry for too long code but I really need to solve it fast. If you read through here, I really thank you!
So my question is: Am I missing something here from these mappings and annotations or I should change the structure of my classes?
Boiling down a problem to the bare minimum greatly helps others help you. Here are simpler versions of your student, course and enrollment classes that can be unit tested easily. The many-to-many association between course and student is separated into two many-to-one associations from Enrollment. Note that the associations are bidirectional and that the many side is mapped by the one side. Student cascades persistence operations to Enrollment, which reflects how schools normally work: students enroll in courses, not the other way around.
Course.java
#Entity
public class Course {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(mappedBy = "course")
private List<Enrollment> enrollments;
Course() {
}
Course(String title) {
this.title = title;
this.enrollments = new ArrayList<>();
}
void add(Enrollment enrollment) {
enrollments.add(enrollment);
}
Long getId() {
return id;
}
List<Enrollment> getEnrollments() {
return enrollments;
}
}
Student.java
#Entity
public class Student {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "student", cascade = ALL, orphanRemoval = true)
private List<Enrollment> enrollments;
Student() {
}
Student(String name) {
this.name = name;
this.enrollments = new ArrayList<>();
}
void enroll(Course course) {
enrollments.add(new Enrollment(course, this));
}
}
Enrollment.java
#Entity
public class Enrollment {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private Course course;
#ManyToOne
private Student student;
Enrollment() {
}
Enrollment(Course course, Student student) {
this.course = course;
this.student = student;
course.add(this);
}
}
The test case below checks that the entities are mapped and associated correctly. You can run it with Spring Boot.
SchoolTest.java
#RunWith(SpringRunner.class)
#SpringBootTest
#Transactional
public class SchoolTest {
#Autowired
private CourseRepository courseRepository;
#Autowired
private StudentRepository studentRepository;
#Test
public void run() {
Course course = courseRepository.save(new Course("cs_101"));
int studentCount = 3;
for (int i = 1; i <= studentCount; i++) {
Student student = new Student("student_" + i);
student.enroll(course);
studentRepository.save(student);
}
// push changes to the database and clear the existing entities
// to make the subsequent operations load from the database
entityManager.flush();
entityManager.clear();
Optional<Course> savedCourse = courseRepository.findById(course.getId());
assertTrue(savedCourse.isPresent());
assertEquals(studentCount, savedCourse.get().getEnrollments().size());
}
}
As the warning said, your Enrollment is not registered in Hibernate. If you really don't need it. Please use transient annotation. read more here
My picture table with two OneToOne relations to the User table:
Picture entity:
#Entity
#Table(name = "pictures")
public class Picture implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
public int getId() {
return id;
}
#OneToOne
#JoinColumn(name = "customer_id", nullable = true)
private User customer;
public User getCustomer() {
return customer;
}
public void setCustomer(User customer) {
this.customer = customer;
}
#OneToOne
#JoinColumn(name = "photographer_id", nullable = false)
private User photographer;
public User getPhotographer() {
return photographer;
}
public void setPhotographer(User photographer) {
this.photographer = photographer;
}
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
private BigDecimal price;
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Picture(String title,
String url,
BigDecimal price)
{
this.title = title;
this.url = url;
this.price = price;
}
public Picture() {
// Empty constructor
}
}
PictureDaoImpl:
#Override
public void insert(Picture object) {
sessionFactory.getCurrentSession().save(object);
}
How can I update the two foreign keys customer_id and photographer_id?
In the entity the two foreign keys are OneToOne relations to User
The error I get is Column 'photographer_id' cannot be null
You made an extremely wrong DB design.
Between DB entities you can't make two relations, between tables\entities
can be only and only single relation.
According to your design you need to relation of #OneToMany relation between Picture and User, for every single/one picture you have many users, even if da facto you'll eventually have only two users, because two are still not one/single, so it's being considered as Many.
I suggest that first do your redesign DB.
I have a database in which I need to make a Join from Java with a CriteriaBuilder.
I have this code so far:
CriteriaBuilder cb = entman.getCriteriaBuilder();
CriteriaQuery<Company> query = cb.createQuery(Company.class);
Root<Employee> teacher = query.from(Employee.class);
Join<Employee, Company> employees = teacher.join("id");
query.select(employees).where(cb.equal(teacher.get("name"), ""));
List<Company> results = entman.createQuery(query).getResultList();
return results;
After I run this code ( with springboot) i get this error: Cannot join to attribute of basic type
Does anyone know what should I do to make it work ?
PS: I will provide any other information if needed.
Thanks in advance.
company database
employee database
L.E.:
Employee:
#Entity
public class Employee {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
#Size(min = 1)
private String name;
#Column(nullable = false)
#Temporal(TemporalType.DATE)
private Date hire_date;
#ManyToOne
//#JoinColumn(name = "id")
private Company company;
public Employee() {}
public Employee(Long id, String name, Date date, Company company) {
setId(id);
setName(name);
setHire_date(date);
setCompany(company);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getHire_date() {
return hire_date;
}
public void setHire_date(Date hire_date) {
this.hire_date = hire_date;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
Company:
#Entity
public class Company {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
#Size(min = 1)
private String name;
#OneToMany(mappedBy="company", cascade = CascadeType.ALL, fetch=FetchType.EAGER, orphanRemoval=true)
private Collection<Employee> employees;
public Company() {}
public Company(Long id, String name) {
setId(id);
setName(name);
}
public Company(Long id, String name, Collection<Employee> employees) {
setId(id);
setName(name);
setEmployees(employees);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Collection<Employee> getEmployees()
{
return employees;
}
public void setEmployees(Collection<Employee> employees) {
this.employees = employees;
}
#Override
public String toString() {
return "Company [id=" + id + ", name=" + name + ", employees=" + employees.toString() + "]";
}
}
You need to have a Teacher element, not a reference to id because you cannot join a #Column field...
Change id mapping from #Column:
#Column(name = "id")
private Integer id;
with a #ManyToOne (or the needed one) association:
#ManyToOne
#JoinColumn(name = "id")
private Teacher teacher;
After this your join will work as expected.
I am making a small library project in Java EE. I have created 3 tables and class with authors, genres and books. Now I am trying to connect it using hibernate, but i haven't ide how confire annotations ... Please help me :)
bookTable:
| id_book | author_id | title | genre_id | description | photo |
genreTable:
| genre_id | genre |
authorTable:
| author_id | author|
It is easy structure:
bookTable.author_id - authorTable.author_id = ManyToMany
bookTable.genre_id - genreTable.genre_id = OneToOne
Below there are my pojo class:
Book
#Entity
#Table(name = "books")
public class Book implements Serializable{
private static final long serialVersionUID = -5057364006691079475L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Integer user_id;
private Author author;
private String description;
private BookGenre genre;
private String title;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Integer getUser_id() {
return user_id;
}
public void setUser_id(Integer user_id) {
this.user_id = user_id;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
public BookGenre getGenre() {
return genre;
}
public void setGenre(BookGenre genre) {
this.genre = genre;
}
}
Author
#Entity
#Table(name = "author")
public class Author implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "author_id")
private Integer author_id;
#Column(name = "author")
private String author;
public Integer getAuthor_id() {
return author_id;
}
public void setAuthor_id(Integer author_id) {
this.author_id = author_id;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
Genre
#Entity
#Table(name = "genre")
public class BookGenre implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "genre_id")
private Integer genreId;
#Column(name = "genre")
private String genre;
public Integer getGenreId() {
return genreId;
}
public void setGenreId(Integer genreId) {
this.genreId = genreId;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
}
Should it be a uni-directional or bi-directional association?
Have a look at this example:
https://howtoprogramwithjava.com/hibernate-manytomany-unidirectional-bidirectional/
It even uses your entity names :)
That are entitys and no pojos, but they look good so far. For them you normally don't need to take care. Best way, you autogenerate them after you connected your project with the database. Hibernate will take care for everything. What is more interesting is how your DAO looks, bcz. that is the layer communication with your database. The entity is only the representation of the database table on Java side. I guess you already connected your project with the database?
Please provide your Database Access Object (DAO) for further help. If you havent that so far here you can get help.