I'm new to Hibernate, and while there are literally so many examples to look at, there seems to be so much flexibility here that it is sometimes very difficult to narrow down all the options in the best way. I have been working on the project for a while now, and despite reading many books, articles and forums, I still have a little dizziness. Any advice from veterans would be greatly appreciated.
So, I have a model that includes two classes with a one-to-many relationship from parent to child. Each class has a surrogate primary key and a unique business key with a limited restriction.
<class name="Container"> <id name="id" type="java.lang.Long"> <generator class="identity"/> </id> <properties name="containerBusinessKey" unique="true" update="false"> <property name="name" not-null="true"/> <property name="owner" not-null="true"/> </properties> <set name="items" inverse="true" cascade="all-delete-orphan"> <key column="container" not-null="true"/> <one-to-many class="Item"/> </set> </class> <class name="Item"> <id name="id" type="java.lang.Long"> <generator class="identity"/> </id> <properties name="itemBusinessKey" unique="true" update="false"> <property name="type" not-null="true"/> <property name="color" not-null="true"/> </properties> <many-to-one name="container" not-null="true" update="false" class="Container"/> </class>
The beans behind these comparisons are as boring as you can imagine - nothing happens. With that in mind, consider the following code:
Container c = new Container("Things", "Me"); c.addItem(new Item("String", "Blue")); c.addItem(new Item("Wax", "Red")); Transaction t = session.beginTransaction(); session.saveOrUpdate(c); t.commit();
Everything works fine for the first time, and both Container and Item are saved. However, if the above block of code runs again, Hibernate throws ConstraintViolationException values - duplicates for the name and owner columns. Since the new Container instance has a null identifier, Hibernate assumes this is an unsaved transient instance. Expected, but not desirable. Since Container permanent and temporary objects have the same business key value, we really need to release an update.
Simply convincing Hibernate that our new instance of Container same as our old one. With a quick request, we can get the Container identifier that we would like to update and set our transition object identifier to match.
Container c = new Container("Things", "Me"); c.addItem(new Item("String", "Blue")); c.addItem(new Item("Wax", "Red")); Query query = session.createSQLQuery("SELECT id FROM Container" + "WHERE name = ? AND owner = ?"); query.setString(0, c.getName()); query.setString(1, c.getOwner()); BigInteger id = (BigInteger)query.uniqueResult(); if (id != null) { c.setId(id.longValue()); } Transaction t = session.beginTransaction(); session.saveOrUpdate(c); t.commit();
This almost satisfies Hibernate, but because of the one-to-many relationship from Container to the Item cascade, the same ConstraintViolationException also thrown for the Item child objects.
My question is: what is the best practice in this situation? The use of surrogate primary keys is strongly recommended, and the use of business key equality is also recommended. However, when you present these two recommendations for collaboration, two of Hibernate’s greatest conveniences — saveOrUpdate and cascading operations — seem almost useless. As I can see, I have only two options:
- Manually select and set an identifier for each object in the mapping. This clearly works, but even for a moderate-sized circuit, this is a lot of extra work, which seems to be easy for Hibernate to do.
- Write a custom interceptor to retrieve and set the object identifiers for each operation. It looks cleaner than the first option, but rather hard, and it seems wrong to me that you should write a plugin that overrides the default behavior of Hibernate to match the recommended design.
Is there a better way? Am I making completely wrong assumptions? I hope that I just missed something.
Thanks.