We have seen one to many relations before but Hibernate also supports an optional one to many relationship. Consider chocolates and Bags. There is a possibility that some chocolates exists outside of a bag. Every chocolate can at any given time be in one and only one bag. However a particular chocolate may also not be in any bag. This makes the one to many relation an optional association.
One way to implement the above would be to make the foreign key column in chocolate table an optional field. However if you prefer to avoid null-able fields in your schema then an alternative approach is use a separate table to store this relation. Thus an entry is made to the table if and only if the chocolate is in a bag. The two classes for the same would be as before:
Chocolate.hbm.xml
Bag.hbm.xml
On start up the hibernate logs indicate the generated SQL:
For the second session the queries are as below:
To delete a bag, the code below was used:
One way to implement the above would be to make the foreign key column in chocolate table an optional field. However if you prefer to avoid null-able fields in your schema then an alternative approach is use a separate table to store this relation. Thus an entry is made to the table if and only if the chocolate is in a bag. The two classes for the same would be as before:
public class Bag { private Integer id; private String code; private Set<Chocolate> chocolates = new HashSet<Chocolate>(); public synchronized void addChocolates(final Chocolate chocolate) { if (null != chocolate.getBag()) { this.removeChocolateFromBag(chocolate); } chocolate.setBag(this); chocolates.add(chocolate); } private synchronized void removeChocolateFromBag(final Chocolate chocolate) { chocolates.remove(chocolate); } //setter and getter methods }The Chocolates class is
public class Chocolate { private Integer id; private String name; private String brand; private Bag bag; //setter and getter methods }The mapping table is completely invisible to the java entities. They are managed from the hibernate mappings.
Chocolate.hbm.xml
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.association.one_to_many.optional"> <class name="Chocolate" table="CHOCOLATE"> <id name="id" type="integer"> <column name="ID" /> <generator class="identity" /> </id> <property name="name" type="string"> <column name="NAME" /> </property> <property name="brand" type="string"> <column name="BRAND" /> </property> <join table="BAG_CHOCOLATE" optional="true" inverse="true"> <key column="CHOCOLATE_ID" unique="true" not-null="true" /> <many-to-one name="bag" column="BAG_ID" foreign-key="BAG_CHOCOLATE_FK_1" /> </join> </class> </hibernate-mapping>Each Chocolate has an optional many-to-one relation with Bag. Earlier this would have been achieved by a simple foreign key column. Here the join element is used, where the table attribute indicates the third table. Similar relation was used in the optional one to one relation that we saw in an earlier post. Here the unique attribute is not set. Optional attribute and not-null attribute is set to "true" to indicate that the relation is not always there. The inverse attribute is set here to ensure that the relation is not manged from here.
Bag.hbm.xml
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.association.one_to_many.optional"> <class name="Bag" table="BAG"> <id name="id" type="integer"> <column name="ID" /> <generator class="identity" /> </id> <property name="code" type="string"> <column name="CODE" /> </property> <set name="chocolates" table="BAG_CHOCOLATE" cascade="all"> <key column="BAG_ID" /> <!-- Foreign Key --> <many-to-many class="Chocolate" column="CHOCOLATE_ID" unique="true" foreign-key="BAG_CHOCOLATE_FK_2" /> </set> </class> </hibernate-mapping>A set of chocolates is present in the Bag. Instead of <one-to-many> we used a <many-to-many> as <one-to-many> does not include the column element. The unique="true" property ensures that Chocolate_Id value is unique(ensuring the one-to-many semantics). Cascade settings have also been included.
On start up the hibernate logs indicate the generated SQL:
create table BAG ( ID integer not null auto_increment, CODE varchar(255), primary key (ID) ) create table BAG_CHOCOLATE ( CHOCOLATE_ID integer not null unique, BAG_ID integer, primary key (BAG_ID, CHOCOLATE_ID) ) create table CHOCOLATE ( ID integer not null auto_increment, NAME varchar(255), BRAND varchar(255), primary key (ID) ) alter table BAG_CHOCOLATE add index BAG_CHOCOLATE_FK_1 (BAG_ID), add constraint BAG_CHOCOLATE_FK_1 foreign key (BAG_ID) references BAG (ID) alter table BAG_CHOCOLATE add index BAG_CHOCOLATE_FK_2 (CHOCOLATE_ID), add constraint BAG_CHOCOLATE_FK_2 foreign key (CHOCOLATE_ID) references CHOCOLATE (ID)If we create a Chocolate and a Bag without the association between them
static void createUnRelated() { Chocolate chocolate1 = new Chocolate(); chocolate1.setName("Eclairs"); chocolate1.setBrand("Cadburys"); Bag chocolateBag = new Bag(); chocolateBag.setCode("Bag01"); Session session = sessionFactory.openSession(); Transaction t = session.beginTransaction(); session.save(chocolate1); session.save(chocolateBag); t.commit(); System.out.println("The Chocolate Bag with name " + chocolateBag.getCode() + " was created with id " + chocolateBag.getId()); System.out.println("Chocolate1 saved with id " + chocolate1.getId()); }The logs indicate that only two insert SQL statements were fired:
2969 [main] DEBUG org.hibernate.SQL - insert into CHOCOLATE (NAME, BRAND) values (?, ?) ... 3094 [main] DEBUG org.hibernate.id.IdentifierGeneratorFactory - Natively genera ted identity: 1 ... 3219 [main] DEBUG org.hibernate.SQL - insert into BAG (CODE) values (?) ... 3219 [main] DEBUG org.hibernate.id.IdentifierGeneratorFactory - Natively genera ted identity: 1 ... 3281 [main] DEBUG org.hibernate.impl.SessionImpl - after transaction completion The Chocolate Bag with name Bag01 was created with id 1 Chocolate1 saved with id 1To create the relationship between the two entities I execute the below code:
static void createRelationOnly() { Session session = sessionFactory.openSession(); Chocolate chocolate1 = (Chocolate) session.get(Chocolate.class, 1); Bag chocolateBag = (Bag) session.get(Bag.class, 1); // This will not trigger any saves because of the inverse chocolate1.setBag(chocolateBag); Transaction t = session.beginTransaction(); chocolateBag.addChocolates(chocolate1); t.commit(); }The logs indicate that the insert for the join table was fired:
2813 [main] DEBUG org.hibernate.SQL - insert into BAG_CHOCOLATE (BAG_ID, CHOCOLATE_ID) values (?, ?) ... 2829 [main] DEBUG org.hibernate.type.IntegerType - binding '1' to parameter: 1 2829 [main] DEBUG org.hibernate.type.IntegerType - binding '1' to parameter: 2The code to load the entities is as below:
static void testLoad() { Session session = sessionFactory.openSession(); Chocolate chocolate1 = (Chocolate) session.get(Chocolate.class, 1); System.out.println("Chocolate " + chocolate1.getName() + " is from brand " + chocolate1.getBrand()); System.out.println("The Bag is " + chocolate1.getBag().getCode()); session.close(); session = sessionFactory.openSession(); Bag bag = (Bag) session.get(Bag.class, 1); System.out.println("The Bag is " + bag.getCode() + " and the number of chocolates are " + bag.getChocolates().size()); session.close(); }The queries fired are as below:
select chocolate0_.ID as ID2_0_, chocolate0_.NAME as NAME2_0_, chocolate0_.BRAND as BRAND2_0_, chocolate0_1_.BAG_ID as BAG2_1_0_ from CHOCOLATE chocolate0_ left outer join BAG_CHOCOLATE chocolate0_1_ on chocolate0_.ID=chocolate0_1_.CHOCOLATE_ID where chocolate0_.ID=? select bag0_.ID as ID0_0_, bag0_.CODE as CODE0_0_ from BAG bag0_ where bag0_.ID=?In the first session, the code to load the chocolate resulted in a join query that will return at most a single chocolate record. The join column will have value null/not null depending on the relation. Attempt to access the bag via the association results in an additional select query.
For the second session the queries are as below:
select bag0_.ID as ID0_0_, bag0_.CODE as CODE0_0_ from BAG bag0_ where bag0_.ID=? select chocolates0_.BAG_ID as BAG2_1_, chocolates0_.CHOCOLATE_ID as CHOCOLATE1_1_, chocolate1_.ID as ID2_0_, chocolate1_.NAME as NAME2_0_, chocolate1_.BRAND as BRAND2_0_, chocolate1_1_.BAG_ID as BAG2_1_0_ from BAG_CHOCOLATE chocolates0_ left outer join CHOCOLATE chocolate1_ on chocolates0_.CHOCOLATE_ID=chocolate1_.ID left outer join BAG_CHOCOLATE chocolate1_1_ on chocolate1_.ID=chocolate1_1_.CHOCOLATE_ID where chocolates0_.BAG_ID=?The attempt to load the Bag object will result will result in a simple select like above. Loading the one to many association will result in the second (double join) query:
select bag0_.ID as ID0_0_, bag0_.CODE as CODE0_0_ from BAG bag0_ where bag0_.ID=? select chocolates0_.BAG_ID as BAG2_1_, chocolates0_.CHOCOLATE_ID as CHOCOLATE1_1_, chocolate1_.ID as ID2_0_, chocolate1_.NAME as NAME2_0_, chocolate1_.BRAND as BRAND2_0_, chocolate1_1_.BAG_ID as BAG2_1_0_ from BAG_CHOCOLATE chocolates0_ left outer join CHOCOLATE chocolate1_ on chocolates0_.CHOCOLATE_ID=chocolate1_.ID left outer join BAG_CHOCOLATE chocolate1_1_ on chocolate1_.ID=chocolate1_1_.CHOCOLATE_ID where chocolates0_.BAG_ID=?
To delete a bag, the code below was used:
static void deleteElements() { Session session = sessionFactory.openSession(); Transaction t = session.beginTransaction(); Bag chocolateBag = (Bag) session.get(Bag.class, 1); //cascade will take care of the rest session.delete(chocolateBag); t.commit(); }The logs below indicate the select statement fired as a part of the load and the subsequent delete statements that followed:
2500 [main] DEBUG org.hibernate.SQL - delete from BAG_CHOCOLATE where BAG_ID=? ... 2531 [main] DEBUG org.hibernate.SQL - delete from CHOCOLATE where ID=?
2875 [main] DEBUG org.hibernate.SQL - delete from BAG where ID=?First the records were removed from the link table, then the chocolates associated and then the bag record.
No comments:
Post a Comment