7

I have below 1-m relationship on entities which Mentor to Students. The mentor has composite primary key which i use as foreign key in student

@Entity
public class Mentor implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    private MentorPK id;
    private String email;
    @OneToMany(mappedBy="mentor")
    private Set<Student> students;

    public MentorPK getId() {
        return id;
    }            
    //getters and setters
}

@Embeddable
public class MentorPK implements Serializable {

    private static final long serialVersionUID = 1L;
    private String name;
    private String add;
    //getters and setters
    //override equals and hashcode
}


@Entity
public class Student implements Serializable{
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    private String name;    

    @ManyToOne
    @MapsId("id")
    @JoinColumns({
        @JoinColumn(name="name_fk", referencedColumnName="name"),
        @JoinColumn(name="address_fk", referencedColumnName="address")
    })
    private Mentor mentor;

    //Getters and setters
}

I then persist the above as below but only the mentor is persisted where student table is empty.

How can I persist the mentor with students?

Set<Student> students = new HashSet<Student>();

Student s1 = new Student();
s1.setName("Student 1");

Student s2 = new Student();
s2.setName("Student 2");

students.add(s1);
students.add(s2);

MentorPK mpk = new MentorPK();
mpk.setAddress("C");
mpk.setName("D");

Mentor m = new Mentor();
m.setId(mpk);
m.setEmail("emaill");
m.setStudents(students);

studentManager.saveMentor(m);
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
Harshana
  • 7,297
  • 25
  • 99
  • 173

5 Answers5

2

Try changing annotation of students field to

@OneToMany(mappedBy="mentor", cascade = CascadeType.PERSIST)

  • did not work by adding cascade = CascadeType.PERSIST attribute – Harshana May 11 '15 at 16:49
  • I have just created a small sample project using code you provided (fixing address/add field name inconsistency and removing obsolete @MapsId annotation) and adding the cascade type and it worked as expected, after calling entityManager.persist(m) the students were persisted as well. – Petros Splinakis May 12 '15 at 12:12
  • I have just noticed that you mention hibernate, I have created the example using eclipselink. You may want to try using latest version and if it is still not working for you then check if this is a reported bug or report it as a bug yourself. – Petros Splinakis May 12 '15 at 13:01
  • Why would `CascadeType.PERSIST` be preferred over `CascadeType.ALL` in this case? Based upon the data model provided the `Mentor` "owns" their associated `Students`, so it's reasonable to expect `delete` operations to cascade as well. – aroth May 14 '15 at 03:26
  • i guess Students own the relationship know because foreign keys resides on Students side – Harshana May 14 '15 at 03:49
  • @aroth I just proposed PERSIST since this was what was asked in the original question, ALL would be fine as well, it all depends on the design of the application. – Petros Splinakis May 14 '15 at 08:27
2

When you use a composite-key, mapped as an Embeddable you need to use @EmbeddedId:

@Entity
public class Mentor {

    @EmbeddedId
    private MentorPK id;

    private String email;
    @OneToMany(mappedBy="mentor")
    private Set<Student> students;

    public MentorPK getId() {
        return id;
    }            
    //getters and setters
}

and the Student becomes:

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    private String name;    

    @ManyToOne
    @JoinColumns({
        @JoinColumn(name="name_fk", referencedColumnName="name"),
        @JoinColumn(name="address_fk", referencedColumnName="address")
    })
    private Mentor mentor;

    //Getters and setters
}

The @MapsId is used when both the @Id and the @ManyToOne share the same database columns, which is not your case, since you have a numeric identifier and a composite foreign-key.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
2

You may need to create the reference from Student to Mentor in each Student.

So in your code, after you create m you need to:

s1.setMentor(m);
s2.setMentor(m);

Otherwise Hibernate may not know what to populate columns name_fk and address_fk with.

DuncanKinnear
  • 4,563
  • 2
  • 34
  • 65
  • didnt work like that also. i guess i have to persist the student first and then persist the mentor with students – Harshana May 18 '15 at 17:00
1

How about changing:

@OneToMany(mappedBy="mentor")
private Set<Student> students;

to:

@OneToMany(mappedBy="mentor")
private Set<Student> students = new HashSet();

or

@OneToMany(cascade=CascadeType.ALL)
 @JoinColumn(name="student_id")
@org.hibernate.annotations.IndexColumn(name="idx") 
private Set<Student> students = new HashSet();

also try not to skip

 = new HashSet();

part

arienn
  • 230
  • 6
  • 18
1

As I understand your request, you would like to have the Mentor be the owner of the relationship. You do not obtain this with the line @OneToMany(mappedBy="mentor"). This actually puts the Student as the owner of the relation.

I have tested this domain model and did a few modifications to the annotations in order to have the test code work as you expect.

Student

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private int id;
    private String name;

    @ManyToOne
    @JoinColumns({
            @JoinColumn(name="name_fk", referencedColumnName="name", insertable = false, updatable = false),
            @JoinColumn(name="address_fk", referencedColumnName="address", insertable = false, updatable = false )
    })
    private Mentor mentor;

//setters and getters
}

Mentor

public class Mentor implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    private MentorPK id;
    private String email;
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumns({
            @JoinColumn(name="name_fk", referencedColumnName="name"),
            @JoinColumn(name="address_fk", referencedColumnName="address")
    })
    private Set<Student> students;
//setters and getters
}

It even works without doing:s1.setMentor(m);s2.setMentor(m);. I did not expect it but it seems that hibernate is dealing with this.

A related article is here.

Attention: drop the database tables after you change the annotations in order to allow hibernate to recreate the tables.

Community
  • 1
  • 1
Cristian Sevescu
  • 1,364
  • 8
  • 11
  • Thank. But actually i want student to be the owner of the relationship. That is why i put mappedBy in Mentor side. So i think the model is correct but the way i persist the data in client is wrong. Can you answer how to persist the student with mentor in correct way – Harshana May 21 '15 at 02:40
  • I try below, Student s1 = new Student(); s1.setName("Student 1"); MentorPK mpk = new MentorPK(); mpk.setAddress("C"); mpk.setName("D"); Mentor m = new Mentor(); m.setId(mpk); m.setEmail("emaill"); s1.setMentor(m); //studentManager.saveMentor(m); studentManager.saveStudent(s1); But it gave me "EntityNotFoundException: Unable to find com.ejb.entities.Mentor with id com.ejb.entities.MentorPK@c22". Then i persist the mentor before save student. Then i got cannot insert NULL into ("SYS"."STUDENT"."ADDRESS_FK") exception – Harshana May 21 '15 at 02:40