1

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?

Shadow
  • 33,525
  • 10
  • 51
  • 64
Paul Buciuman
  • 325
  • 3
  • 10
  • 19

2 Answers2

1

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());
    }
}
ehecatl
  • 170
  • 1
  • 8
0

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

Qingfei Yuan
  • 1,196
  • 1
  • 8
  • 12