Hibernate Association Mappings Introduction Association mappings are the often most difficult thing to get right. In this section we'll go through the canonical cases one by one, starting with unidirectional mappings, and then considering the bidirectional cases. We'll use Person and Address in all the examples. We'll classify associations by whether or not they map to an intervening join table, and by multiplicity. Nullable foreign keys are not considered good practice in traditional data modelling, so all our examples use not null foreign keys. This is not a requirement of Hibernate, and the mappings will all work if you drop the nullability constraints. Unidirectional associations Many- to-One: A unidirectional many-to-one association is the most common kind of unidirectional association. <many-to-one name="address" column="addressId" not-null="true"/> create table Person ( personId bigint not null primary key, addressId bigint not null ) create table Address ( addressId bigint not null primary key ) One- to-One: A unidirectional one-to-one association on a foreign key is almost identical. The only difference is the column unique constraint. <many-to-one name="address" column="addressId" unique="true" not-null="true"/>
create table Person ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key ) A unidirectional one-to-one association on a primary key usually uses a special id generator. (Notice that we've reversed the direction of the association in this example.) <param name="property">person create table Person ( personId bigint not null primary key ) create table Address ( personId bigint not null primary key ) One- to- Many: A unidirectional one-to-many association on a foreign key is a very unusual case, and is not really recommended. <set name="addresses"> create table Person ( personId bigint not null primary key ) create table Address ( addressId bigint not null primary key, personId bigint not null )
We think it's better to use a join table for this kind of association. Unidirectional associations with join tables One-to-Many: A unidirectional one-to-many association on a join table is much preferred. Notice that by specifying unique="true", we have changed the multiplicity from many-to-many to one-to-many. <set name="addresses" table="PersonAddress"> <many-to-many column="addressId" unique="true" class="Address"/> create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId not null, addressId bigint not null primary key ) create table Address ( addressId bigint not null primary key ) Many- to-One: A unidirectional many-to-one association on a join table is quite common when the association is optional. <join table="PersonAddress" optional="true"> <many-to-one name="address" column="addressId" not-null="true"/>
create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null primary key, addressId bigint not null ) create table Address ( addressId bigint not null primary key ) One- to- One: A unidirectional one-to-one association on a join table is extremely unusual, but possible. <join table="PersonAddress" optional="true"> <many-to-one name="address" column="addressId" not-null="true" unique="true"/> create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key ) Many- to- Many: Finally, we have a unidirectional many-to-many association. <set name="addresses" table="PersonAddress"> <many-to-many column="addressId" class="Address"/>
create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) ) create table Address ( addressId bigint not null primary key ) Bidirectional associations One- to- Many / Many- to- One: A bidirectional many-to-one association is the most common kind of association. (This is the standard parent/child relationship.) <many-to-one name="address" column="addressId" not-null="true"/> <set name="people" inverse="true"> create table Person ( personId bigint not null primary key, addressId bigint not null ) create table Address ( addressId bigint not null primary key ) If you use a List (or other indexed collection) you need to set the key column of the foreign key to not null, and let Hibernate manage the association from the collections side to maintain the index of each element (making the other side virtually inverse by setting update="false" and insert="false"): ... <many-to-one name="address" column="addressId" not-null="true" insert="false" update="false"/> ... <list name="people">
<list-index column="peopleIdx"/> It is important that you define not-null="true" on the element of the collection mapping if the underlying foreign key column is NOT NULL. Don't only declare not-null="true" on a possible nested element, but on the element. One- to- One: A bidirectional one-to-one association on a foreign key is quite common. <many-to-one name="address" column="addressId" unique="true" not-null="true"/> create table Person ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key ) A bidirectional one-to-one association on a primary key uses the special id generator. <param name="property">person create table Person ( personId bigint not null primary key )
create table Address ( personId bigint not null primary key ) Bidirectional associations with join tables One- to- Many / Many- to- One: A bidirectional one-to-many association on a join table. Note that the inverse="true" can go on either end of the association, on the collection, or on the join. <set name="addresses" table="PersonAddress"> <many-to-many column="addressId" unique="true" class="Address"/> <join table="PersonAddress" inverse="true" optional="true"> <many-to-one name="person" column="personId" not-null="true"/> create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null, addressId bigint not null primary key ) create table Address ( addressId bigint not null primary key ) One- to-One: A bidirectional one-to-one association on a join table is extremely unusual, but possible. <join table="PersonAddress" optional="true"> <many-to-one name="address"
column="addressId" not-null="true" unique="true"/> <join table="PersonAddress" optional="true" inverse="true"> <many-to-one name="person" column="personId" not-null="true" unique="true"/> create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key ) Many- to-Many: Finally, we have a bidirectional many-to-many association. <set name="addresses" table="PersonAddress"> <many-to-many column="addressId" class="Address"/> <set name="people" inverse="true" table="PersonAddress"> <many-to-many column="personId" class="Person"/> create table Person ( personId bigint not null primary key )
create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) ) create table Address ( addressId bigint not null primary key ) More complex association mappings: More complex association joins are extremely rare. Hibernate makes it possible to handle more complex situations using SQL fragments embedded in the mapping document. For example, if a table with historical account information data defines accountNumber, effectiveEndDate and effectiveStartDatecolumns, mapped as follows: <properties name="currentAccountKey"> <property name="accountNumber" type="string" not-null="true"/> <property name="currentAccount" type="boolean"> case when effectiveEndDate is null then 1 else 0 end <property name="effectiveEndDate" type="date"/> <property name="effectiveStateDate" type="date" not-null="true"/> Then we can map an association to the current instance (the one with null effectiveEndDate) using: <many-to-one name="currentAccountInfo" property-ref="currentAccountKey" class="AccountInfo"> '1' In a more complex example, imagine that the association between Employee and Organization is maintained in an Employment table full of historical employment data. Then an association to the employee's most recent employer (the one with the most recent startDate) might be mapped this way: <join> <subselect> select employeeId, orgId from Employments group by orgId having startDate = max(startDate) <many-to-one name="mostRecentEmployer" class="Organization" column="orgId"/> You can get quite creative with this functionality, but it is usually more practical to handle these kinds of cases using HQL or a criteria query.
Hibernate: Understanding Associations - Associations: What are They? To represent relationships between classes, associations are used. Before going into the details of how Hibernate perceives associations, an understanding of the working of container managed associations is needed. Managed association means that, if a change is made to one end of the association, it will be reflected at the other end. For example, let's consider the Order table. If it has a one-to-many relationship with the Product table, the Order class will have a one-to-many association with the Product class. So when changes are made to the attributes participating in this association, they will be reflected at the other end automatically. The developer doesn’t have to mange the associations manually. How Hibernate implements the management of association is different from that of Container Managed Relationships/Associations or CMR generally provided by EJB CMP. In CMR the association is bidirectional, whereas Hibernate treats each association as different. The primary reason is that Hibernate builds its persistence based on Plain Old Java Object or POJO, and in Java associations are unidirectional. Thus Hibernate doesn’t implement CMR. So the associations are unidirectional. In essence it means if the on-update-cascade attribute is set in the mapping for Order and not in Product, any operation on Product would not affect Order. Keeping these points in mind, let's move on to the different types of associations supported by Hibernate. Hibernate mainly supports two types of associations: 1. One-to-Many 2. Many-to-One To work with associations, the changes would be required in both the mapping as well as in the persistence class. The details are as follows: 1. One-to-Many: In this kind of association one object of a class is in a relationship with many objects of another class. The class that is having the single instance contains a collection of instances of the other class. To specify this association the mapping file would have to be modified. The added code would be: The name attribute takes the name of the variable that is participating in the association. The column attribute is used to specify the table column that is participating in the association. The class attribute takes the class name to which this class is associated. In the Persistent class the following change would be there: class { //other variable declarations Set =new HashSet(); //constructors and getter/setter code follows : }
Then the constructor with the added parameter for Set must be given along with the getter and setter for the Set. In the third section I will be discussing a real world example to illustrate this point. 2. Many-to-One: This is the opposite of the One-to-Many association. In this case, the class that is having a Many-to-One association contains the object of the class. For example, if class A has a Many-to-One association with class B, then each instance of B would have an instance of A. And the identity of this instance can be the same for multiple objects of B. The change in the mapping would be: <many-to-one name=”nameOfheVariable column="NAME_OF_THE_COLUMN" class="ClassName" not-null="true"/> The attributes are the same as the case of One-to-Many. In the case of code it would be class { o=new //construtors and getter/setter code follows : } The associations will be clearer when I discuss the real world usage of association in the next section. Hibernate: Understanding Associations - Associations in the Real World Till now I was using only one table. Let's make things interesting by adding one more table. This table is the Product table. Each Order can have more than one Product. Hence the relationship between Order and Product is One-to-Many. The schema of the Product table is: CREATE TABLE PRODUCT( ID VARCHAR NOT NULL PRIMARY KEY, NAME VARCHAR NOT NULL, PRICE DOUBLE NOT NULL, AMOUNT INTEGER NOT NULL, ORDER_ID VARCHAR NOT NULL) The next step is to create the persistent class for the Product table. The persistent class is as follows: package com.someorg.persist; public class Product { private String id; private String name; private double price; private int amount; private Order order;
public Product(String id, String name, double price, int amount, Order order) { this.order=order; //others not shown for brevity } public String getId() { return id; } public void setId(String string) { id = string; } // default constructor and other // getters/setters not shown for brevity // ... } The next part is Product.hbm.xml, that is the mapping file: <property name="name"> <property name="price"> <property name="amount"> <property name="orderId"> <many-to-one name="orderId"
column="ORDER_ID" class="ORDER" not-null="true"/> That is all that is required for the Product table. Now we need to make some changes in the Order class. package com.someorg.persist; import java.util.Date; import java.util.HashSet; import java.util.Set; public class Order { private String id; private Date date; private double priceTotal; private Set products =new HashSet(); // Automatically set the creation time of // this Order public Order() { this.date = new Date(); } public Order(String id, Date date, private double priceTotal, Set products){ this.products=products; //others are not shown for brevity } public String getId() { return id; } public void setProducts(Set products) { this.products = products; } public Set getProducts () { return products; } public void setId(String string) { id = string; } // other getters/setters not shown for // brevity // ... } The next part is changing in the Order.hbm.xml, which is:
"-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <property name="date" not-null="false" > <property name="priceTotal" not-null="false" > <set name="products"> The next step is to test it. To test it I will be using the Criteria query. In a QBC the joins are done using the setFetchMode method of the Criteria class. The mode would be JOIN. Here is how it works: import java.util.List; //other imports // use as // java test. FindOrderById name public class FindOrderById { public static void main(String[] args) throws Exception { // query to issue String query = "select order from Order " + "where order.id=:id"; // search for what? String name = args[0]; // init Configuration cfg = new Configuration() .addClass(Order.class); SessionFactory sf = cfg.buildSessionFactory(); // open session Session sess = sf.openSession(); // search and return Criteria criteria = session.createCriteria(Order.class); criteria.add( Expression.eq("id", name) ) .setFetchMode(“products”,FetchMode.JOIN); List result = criteria.list();
if (list.size() == 0) { System.out.println("No Order having id " + name); System.exit(0); } Order o = (Order) list.get(0); sess.close(); System.out.println("Found Order: " + o);//this is just an example Here the o // //object can be traversed to achieve anything } } That brings us to the end of this discussion. Though the complete picture is becoming clear, some edges are still hazy. These edges will be brought into sharper focus in the forthcoming discussions. Till next time.