Full source code is provided in package: de.laliluna.relation.one2many Uni-directional
We have a class JavaClub1 having a set of JavaClubMember1. The member does not know about the relation.
Classes | Tables |
As a consequence, the relation is managed on the one-side of the relation. This kind of relationship is not very efficient. An insert of a club with two members leads to 5 queries. If this relation is created or updated frequently, you should consider to create a bi-directional relation where the many-side manages the relation. This can be achieved with inverse="true" or the mappedBy attribute. Generated queries:
Hibernate: insert into tjavaclub (name, id) values (?, ?) Hibernate: insert into tjavaclubmember (name, club_id, id) values (?, ?, ?) Hibernate: insert into tjavaclubmember (name, club_id, id) values (?, ?, ?) Hibernate: update tjavaclubmember set club_id=? where id=? Hibernate: update tjavaclubmember set club_id=? where id=?
Annotation mapping.
import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Entity; ........ snip ........ @Entity public class JavaClub1 implements Serializable { @OneToMany @JoinColumn(name="club_id", nullable=false) private Set<JavaClubMember1> members = new HashSet<JavaClubMember1>();
The class Club1 has no annotations related to the relation.
XML mapping.
public class JavaClub1 implements Serializable { private Set members = new HashSet(); public Set getMembers() { return members; } public void setMembers(Set members) { this.members = members; } ........ snip .......
<hibernate-mapping package="de.laliluna.relation.one2many"> <class name="JavaClub1" table="tjavaclub"> ...... snip .......... <set name="members"> <key column="club_id" not-null="true"></key> <one-to-many class="JavaClubMember1"/> </set> </class> </hibernate-mapping>
Table structure.
CREATE TABLE tjavaclub ( id int4 NOT NULL, name varchar(255), CONSTRAINT tjavaclub_pkey PRIMARY KEY (id) ) ; CREATE TABLE tjavaclubmember ( id int4 NOT NULL, name varchar(255), club_id int4 NOT NULL, PRIMARY KEY (id), FOREIGN KEY (club_id) REFERENCES tjavaclub (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION ) ;
Some examples of use:
/* create and set relation */ JavaClub1 club1 = new JavaClub1("Hib club"); JavaClubMember1 member1 = new JavaClubMember1("Eric"); JavaClubMember1 member2 = new JavaClubMember1("Peter"); // relation is uni-directional => we can only set the relation on one // side club1.getMembers().add(member1); club1.getMembers().add(member2); session.save(club1); session.save(member1); session.save(member2); /* delete a member */ session.update(clubMember1); JavaClub1 club1 = (JavaClub1) session.createQuery( "from JavaClub1 c where ? in elements(c.members) ").setEntity( 0, clubMember1).uniqueResult(); // first take away the member from the club, than delete it. club1.getMembers().remove(clubMember1); session.delete(clubMember1); /* simple select which initializes the club only, further queries are issued, * if you access the members*/ List list = session.createQuery("from JavaClub1").list(); /* select using fetch to initialize everything with one query and remove * double entries from the result */ list = session.createQuery( "from JavaClub1 c left join fetch c.members") setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list(); /* same query using criteria instead of HQL */ list = session.createCriteria(JavaClub1.class) .setFetchMode("members", FetchMode.JOIN) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
Hibernate creates one instance for each line of a result set.\newline If a club has two members, you will receive two lines when you select the club and left join the members. Your list would have double entries of clubs. You had to use a HashSet in former times or you received double entries.
Set set = new HashSet(session.createCriteria(JavaClub1.class) .setFetchMode("members", FetchMode.JOIN).list());
Now there is a better approach:
List result = new DistinctRootEntityResultTransformer() .transformList(session.createQuery("from JavaClub1 c left join fetch c.members") .list());
If you use criteria queries, take the following approach.
List list = results = session.createCriteria(JavaClub3.class) .addOrder(Order.desc("name")) .setFetchMode("members", FetchMode.JOIN) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)).list();
Uni-directional (other side)
We have a class JavaClub2 and JavaClubMember2 where the club does not know about the relation. This kind of relation is more efficient than the one before. When you create a club with two members only three queries are issued.
Hibernate: insert into tjavaclub (name, id) values (?, ?) Hibernate: insert into tjavaclubmember (name, club_id, id) values (?, ?, ?) Hibernate: insert into tjavaclubmember (name, club_id, id) values (?, ?, ?)
Classes | Tables |
Annotation mapping.
import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; ..... snip ...... @Entity public class JavaClubMember2 implements Serializable{ @ManyToOne @JoinColumn(name="club_id") private JavaClub2 club;
The @ManyToOne annotation specifies the relation. @JoinColumn(name="club_id") specifies how the tables are joined. It is optional and you may rely on the default values.
XML mapping.
import java.io.Serializable; import java.util.HashSet; import java.util.Set; public class JavaClub1 implements Serializable { private Set members = new HashSet(); public Set getMembers() { return members; } public void setMembers(Set members) { this.members = members; } ...... snip .....
<hibernate-mapping package="de.laliluna.example3"> <class name="JavaClubMember2" table="tjavaclubmember"> ...... snip ........ <many-to-one name="club" class="JavaClub2"> <column name="club_id" not-null="true"></column> </many-to-one> </class> </hibernate-mapping>
The resulting tables are of course the same as in our first example. Examples of use:
/* create and set relation */ JavaClub2 club2 = new JavaClub2("Hib club"); JavaClubMember2 member1 = new JavaClubMember2("Eric"); JavaClubMember2 member2 = new JavaClubMember2("Peter"); member1.setClub(club2); member2.setClub(club2); // we did not configure any cascadeType, so we have to save any of the objects session.save(club2); session.save(member1); session.save(member2); /* delete */ // just delete, we do not have to update or reconnect session.delete(clubMember2); /* select JavaClubMember but do not initialize the Club */ List list = session.createQuery("from JavaClubMember2").list(); /* select JavaClubMembers and initialize the club directly using a join */ List list = session.createQuery( "from JavaClubMember2 m left join fetch m.club").list(); /* same using criteria instead of HQL */ List list = session.createCriteria(JavaClubMember2.class).setFetchMode( "club", FetchMode.JOIN).list();
Bi-directional
Classes | Tables |
In a 1 to n relation you should consider to manage the relation on the many-side (= JavaClubmember), as this leads to less queries, if you set a relation. Queries, if the relation is managed on the one-side:
Hibernate: insert into tjavaclub (name, id) values (?, ?) Hibernate: insert into tjavaclubmember (name, club_id, id) values (?, ?, ?) Hibernate: insert into tjavaclubmember (name, club_id, id) values (?, ?, ?) Hibernate: update tjavaclubmember set club_id=? where id=? Hibernate: update tjavaclubmember set club_id=? where id=?
Queries, if the relation is managed on the many-side:
Hibernate: insert into tjavaclub (name, id) values (?, ?) Hibernate: insert into tjavaclubmember (name, club_id, id) values (?, ?, ?) Hibernate: insert into tjavaclubmember (name, club_id, id) values (?, ?, ?)
Annotation mapping.
import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; ..... snip ..... @Entity public class JavaClubMember3 implements Serializable{ @ManyToOne @JoinColumn(name = "club_id", nullable = false) private JavaClub3 club;
@ManyToOne specifies the relation. @JoinColumn(name = "club_id", nullable = false) specifies how the table is joined and that a member cannot exist without a club, club_id cannot be null.
import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.OneToMany; ...... snip ........ @Entity public class JavaClub3 implements Serializable { @OneToMany(mappedBy="club") private Set<JavaClubMember3> members = new HashSet<JavaClubMember3>();
@OneToMany(mappedBy="club") specifies that the relation is managed by the club property of the JavaClubMember3.
XML mapping. Although you might choose, which side manages the relation, you must manage the relation on the many-side, if your foreign key (club_id) cannot be null. In this case set, you have to use inverse="true". See the discussion in xref:RefUsingrelationsandcascading.
import java.util.HashSet; import java.util.Set; ......... snip ...... public class JavaClub3 implements Serializable { private Set members = new HashSet(); public Set getMembers() { return members; } public void setMembers(Set members) { this.members = members; }. ......... snip ......
public class JavaClubMember3 implements Serializable { private JavaClub3 club; public JavaClub3 getClub() { return club; } public void setClub(JavaClub3 club) { this.club = club; } ......... snip ......
<hibernate-mapping package="de.laliluna.example3"> <class name="JavaClub3" table="tjavaclub"> ..... snip ........ <set name="members" inverse="true"> <!-- we have a set and the relation is managed on the other side --> <key column="club_id" not-null="true"></key> <!-- defines how the tables are joined. --> <one-to-many class="JavaClubMember3"/> <!-- target class of the relation --> </set> </class> </hibernate-mapping>
<hibernate-mapping package="de.laliluna.example3"> <class name="JavaClubMember3" table="tjavaclubmember"> ......... snip ......... <many-to-one name="club" class="JavaClub3"> <!-- specifies property and target class --> <column name="club_id" not-null="true"></column> <!-- join column--> </many-to-one> </class> </hibernate-mapping>
The resulting tables are once again the same. Do not forget to set and delete the relations on both sides.
member1.setClub(club); club.getMembers().add(member1); member2.setClub(club); club.getMembers().add(member2);
Examples of use:
/* create and set relation */ JavaClub3 club = new JavaClub3("Hib club"); JavaClubMember3 member1 = new JavaClubMember3("Eric"); JavaClubMember3 member2 = new JavaClubMember3("Peter"); // relation is bi-directional => we must set the relation on both sides member1.setClub(club); member2.setClub(club); club.getMembers().add(member1); club.getMembers().add(member2); // no cascade configured so save everything session.save(club); session.save(member1); session.save(member2); /* delete */ // we must reattach the member to the new session session.update(clubMember3); clubMember3.getClub().getMembers().remove(clubMember3); session.delete(clubMember3); /* have a look in the uni-directional cases for query samples */
Bi-directional with join table
Sometimes you do not want to have a foreign key in the table of the many side but define the relation in a separate join table.
Classes | Tables |
Annotation mapping.
import java.io.Serializable; import java.util.HashSet; import java.util.Set; import javax.persistence.Entity; import javax.persistence.OneToMany; ..... snip ...... @Entity public class JavaClub4 implements Serializable { @OneToMany(mappedBy="club") private Set<JavaClubMember4> members = new HashSet<JavaClubMember4>();
@OneToMany(mappedBy="club") defines the relation and that it is managed by the property club of JavaClubMember.
import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToOne; ... snip ....... @Entity public class JavaClubMember4 implements Serializable { @ManyToOne @JoinTable(name = "club_member", joinColumns = { @JoinColumn(name = "member_id") }, inverseJoinColumns = { @JoinColumn(name = "club_id") } ) private JavaClub4 club;
@JoinTable(name = "club_member".., specifies the join table. joinColumns specifies which columns reference the JavaClubMember primary key. inverseJoinColumns specifies which columns reference the JavaClub primary key.
XML mapping. On the JavaClub4 side, we define a many-to-many relation and set the JavaClubMember4 to unique. This might be confusing but is the correct approach. Annotation mapping is somewhat clearer for this kind of mapping. I set inverse to true, to have more efficient updates.
public class JavaClubMember4 implements Serializable { private JavaClub4 club; public JavaClub4 getClub() { return club; } public void setClub(JavaClub4 club) { this.club = club; }
public class JavaClub4 implements Serializable { private Set<JavaClubMember4> members = new HashSet<JavaClubMember4>(); public Set<JavaClubMember4> getMembers() { return members; } public void setMembers(Set<JavaClubMember4> members) { this.members = members; }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > <hibernate-mapping package="de.laliluna.example3"> <class name="JavaClub4" table="tjavaclub4"> ........ snip ....... <set name="members" inverse="true" table="club_member"> <key column="club_id"></key> <many-to-many class="JavaClubMember4" column="member_id" unique="true"/> </set> </class> </hibernate-mapping>
The JavaClubMember4 (many side of relation) defines the join.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > <hibernate-mapping package="de.laliluna.example3"> <class name="JavaClubMember4" table="tjavaclubmember4"> ........ snip ........... <join table="club_member" > <key column="member_id" ></key> <many-to-one name="club" class="JavaClub4"> <column name="club_id" not-null="true"></column> </many-to-one> </join> </class> </hibernate-mapping>
The resulting tables are:
CREATE TABLE javaclub4 ( id int4 NOT NULL, name varchar(255), PRIMARY KEY (id) ) ; CREATE TABLE javaclubmember4 ( id int4 NOT NULL, name varchar(255), PRIMARY KEY (id) ) ; CREATE TABLE club_member ( member_id int4 NOT NULL, club_id int4 NOT NULL, PRIMARY KEY (member_id), FOREIGN KEY (member_id) REFERENCES javaclubmember4 (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION, FOREIGN KEY (club_id) REFERENCES javaclub4 (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION );
Do not forget to set and delete the relations on both sides.
member1.setClub(club); club.getMembers().add(member1); member2.setClub(club); club.getMembers().add(member2);
Samples of use:
/* create and set relation */ session.beginTransaction(); JavaClub4 club = new JavaClub4("Hib club"); JavaClubMember4 member1 = new JavaClubMember4("Eric"); JavaClubMember4 member2 = new JavaClubMember4("Peter"); // relation is bi-directional => we must set the relation on both sides member1.setClub(club); member2.setClub(club); club.getMembers().add(member1); club.getMembers().add(member2); // no cascade configured so save everything session.save(club); session.save(member1); session.save(member2); /* delete */ // we must reattach the member (our session is closed) session.buildLockRequest(LockOptions.NONE).lock(clubMember4); clubMember4.getClub().getMembers().remove(clubMember4); session.delete(clubMember4); /* query samples can be found in the previous samples */