2

I am running into the following behaviour in hibernate which I can't explain and I was hoping that someone can provide an explanation of what is going on.

I have a bidirectional @OneToMany association between Instructor and Course. Since they have a CascadeType.PERSIST I am expecting that when I create a Course object and associate it to Instructor and vice versa and then call the .save() method on the instructor both objects will be persisted in the database, although that results in only the Instructor to be persisted without the courses.

If I call the persist() method for ex: session.persist(instructor) both instructor and the courses are persisted.

Here are my entities and my main method.

Instructor:

@Entity
@Table(name = "instructor")
public class Instructor {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "email")
    private String email;

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "instructor_detail_id")
    private InstructorDetail instructorDetail;

    @OneToMany(mappedBy = "instructor", cascade = {CascadeType.DETACH,
    CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.LAZY)
    private List<Course> courses = new ArrayList<>();

    public Instructor() {
    }

    public Instructor(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public InstructorDetail getInstructorDetail() {
        return instructorDetail;
    }

    public void setInstructorDetail(InstructorDetail instructorDetail) {
        this.instructorDetail = instructorDetail;
    }

    public List<Course> getCourses() {
        return courses;
    }

    public void setCourses(List<Course> courses) {
        this.courses = courses;
    }

    @Override
    public String toString() {
        return "Instructor{" +
            "id=" + id +
            ", firstName='" + firstName + '\'' +
            ", lastName='" + lastName + '\'' +
            ", email='" + email + '\'' +
            ", instructorDetail=" + instructorDetail +
            ", courses=" + courses +
            '}';
    }

    public void addCourse(Course course) {
        courses.add(course);
        course.setInstructor(this);
    }
}

Course

@Entity
@Table(name = "course")
public class Course {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column(name = "title")
    private String title;

    @ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE,
        CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.LAZY)
    @JoinColumn(name = "instructor_id")
    private Instructor instructor;

    public Course(String title) {
        this.title = title;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Instructor getInstructor() {
        return instructor;
    }

    public void setInstructor(Instructor instructor) {
        this.instructor = instructor;
    }
}

Main

public class Test {

    public static void main(String[] args) {

        //create session factory
        SessionFactory sessionFactory = new Configuration()
            .configure("com/ysoft/config/hibernate.cfg.xml")
            .addAnnotatedClass(Instructor.class)
            .addAnnotatedClass(InstructorDetail.class)
            .addAnnotatedClass(Course.class)
            .buildSessionFactory();

        //create session
        Session session = sessionFactory.getCurrentSession();

        try (sessionFactory) {
            session.beginTransaction();

            Instructor instructor = new Instructor("John", "Doe", "John.Doe@email.com");
            InstructorDetail instructorDetail = new InstructorDetail("http://www.something.com", "Coding");

            Course mathCourse = new Course("Math");
            Course englishCourse = new Course("English");
            Course sportsCourse = new Course("Sports");

            instructor.addCourse(mathCourse);
            instructor.addCourse(englishCourse);
            instructor.addCourse(sportsCourse);

            instructor.setInstructorDetail(instructorDetail);

            session.save(instructor);

            session.getTransaction().commit();

            System.out.println("Persisted" + instructor);
        }
    }
}
Kirby
  • 15,127
  • 10
  • 89
  • 104

1 Answers1

4

If I changed the @OneToMany cascade array to CascadeType.ALL then it worked - but I don't know why, since the relevant types were already there.

The child side shouldn't have cascade specified - to quote the latest Hibernate docs:

The @OneToMany association is by definition a parent association, even if it’s a unidirectional or a bidirectional one. Only the parent side of an association makes sense to cascade its entity state transitions to children.

Update:

According to this it seems calling save() rather than using JPA EntityManager.persist() it also needs Hibernate-specific cascade type SAVE_UPDATE. (Which isn't compatible with JPA @OneToMany definition)

boot-and-bonnet
  • 731
  • 2
  • 5
  • 16