I'm doing a site for a photographer friend and decided to use Hibernate (v3.1.2). I got the Hibernate in Action book, read through it and got cracking. I'd appreciate any pointers on a difficulty I've been having with two properties of an object that are not entirely independent. I'm not looking for a solution; I'd be delighted to just be told where I should be reading next.
The central notion is of a job that is comprised of many photos. Each photo has a category embedded in its metadata. An archive is uploaded from which I extract the photos. For each one, I parse out its category. I then create a set of photos, set it as a property on the job and save the job using Hibernate. This all works fine and the database tables for jobs and photos get populated as required. The relevant sections from Job.hbm.xml and Photo.hbm.xml are as follows (I've trimmed out simple string properties from both, for clarity):
Code:
<class name="Job" table="jobs" schema="public">
<id name="id" type="int">
<column name="id" />
<generator class="sequence">
<param name="sequence">jobs_id_seq</param>
</generator>
</id>
<set name="photos" inverse="true" cascade="all">
<key column="job_id"/>
<one-to-many class="Photo"/>
</set>
</class>
Code:
<class name="Photo" table="photos" schema="public">
<id name="id" type="long">
<column name="id" />
<generator class="sequence">
<param name="sequence">photos_id_seq</param>
</generator>
</id>
<property name="category" type="string">
<column name="category" length="10" not-null="true" />
</property>
<property name="content" type="binary">
<column name="content" not-null="true" />
</property>
<many-to-one name="job" class="Job">
<column name="job_id" not-null="true" />
</many-to-one>
</class>
So far so good. I also decided, as an optimisation, to retain the list of categories that appear in a job as a property of that job (seeing as I have to process each photo when they're being uploaded and I have access to that information at that point). So I put an extra string field in class Job that is a list of all the unique categories, using a special character to delimit them. The code inside the
setPhotos() method also sets this list of categories.
At this point, there was a slight change to the requirements. In certain cases, categories will have a natural notion of order (whether this is a temporal order or whatever). So he would like to be able to edit this order, so that they are presented to users in a more natural way. I promptly renamed the property from "categories" to "categoryOrdering", and did up a second page that would allow categories to be re-ordered (modifying this string property), before the overall job was saved. I can't get this to work. I followed as many potential leads as I could from the forums but have drawn a blank. (E.g. I implemented
equals() and
hashCode() on both entities in terms of a business key, and so on.)
What's happening is that the
setPhotos() method is being called a second time, which overwrites the re-ordered list of categories back to the original un-ordered list of categories. I can't see why it should be doing this. I understand that the Photo objects will obtain keys as part of the cascade, but there are no additions or deletions from the set of photos, so I'm not sure why the overall property is being re-set. (In line with the discussion on page 171 of HiA, where the modification of a Bid does not lead to the version of Item being incremented.) It occurred to me that it might have something to do with proxies but I tried it with the lazy attribute of the associate set to both true and false and the results were identical. Finally, I put print statements in all the setters of the Job class to see how many Job objects were involved. The following is typical output:
[After the initial upload page, called by the web framework]
Code:
setTitle called on model.Job@17cd7
setYear called on model.Job@a097132d
setFolderName called on model.Job@a097132d
setPhotos called on model.Job@a097132d
[After the category re-ordering page, when I save the Job in Hibernate]
Code:
setId called on model.Job@a097132d
setTitle called on model.Job@a097132d
setYear called on model.Job@a097132d
setFolderName called on model.Job@a097132d
setCategoryOrdering called on model.Job@a097132d
setPhotos called on model.Job@a097132d
In other words, all calls are on the same object, except for the very first one. That object doesn't seem to be involved again (but the title that was set is captured somewhere in the system). The problem is in the last two lines.
setCategoryOrdering() sets the string that holds the ordered list of categories correctly and then
setPhotos() overwrites it back to what it was. This would be the correct behaviour if we were dealing with a new set of photos, but the set of photos is unmodified (modulo the acquisition of IDs).
One possible approach is to introduce a separate Category entity, but the above is "idiomatic" Java to me and I would like to first-of-all attempt to get it working before adapting my code to the persistence framework. More to the point, I'd like to get to the bottom of why it's happening to deepen my understanding of Hibernate.
Thanks in advance,
John.