Getting the Id from Lazy Loaded Object Using Annotations in Hibernate

We've been fighting for some time with lazy loaded objects in Hibernate that cause a select even when we are just calling getId() on them. Given that the id of the field is stored in the object so that it can find the lazy object, it is a real waste to do the select. After some searching and poking around a bit, we finally figured out how we can stop this behavior. Given that the web was unhelpful, I thought I'd put together this page. We are running with Hibernate version 3.2.4.sp1 and annotations version 3.3.0.ga. I'm not sure if we are behind the times or if these "bugs" or "features" have changed in later Hibernate releases.

We are using field level annotations on our hibernate objects which let us easily add fields to classes and then regenerate the schema without having to keep the hibernate XML configurations in sync. I suspect that this is easily accomplished if you already are using the XML. Hibernate documentation states:

How can I retrieve the identifier of an associated object, without fetching the association?

Just do it. The following code does not result in any SELECT statement, even if the item association is lazy ( Long itemId = bid.getItem().getId(); ). This works if getItem() returns a proxy and if you mapped the identifier property with regular accessor methods. If you enabled direct field access for the id of an Item, the Item proxy will be initialized if you call getId(). This method is then treated like any other business method of the proxy, initialization is required if it is called.

As you see from the above statement, this special handling of the getId() only works if you are mapping the object using property access type instead of field access. We use field access for all of our objects which allows us to better control the access to the fields of our classes instead of having set methods on everything. The trick is to define all of your fields with "field" access type but override and use "property" type just on the id field. You will need to add the @AccessType("property") annotation to the id field of your object and make sure you have a valid getId() and setId() methods. We are using package protected setId() calls for testing purposes -- using private may work if you want to restrict access more, YMMV.

public class Foo {
	// the id field is set to be property accessed
	@Id @GeneratedValue @AccessType("property")
	private long id;
	// other fields can use the field access type
	@Column private String stuff;
	public long getId() { return id; }
	public void setId(long id) { this.id = id; }
	String getStuff() { return stuff; }
	// NOTE: we don't need a setStuff method

This will use the getId() and setId() accessors for the id but it will use the reflection fu to set the stuff field. Good stuff. Now when we call getId(), Hibernate knows that the method is the id accessor and allows it to be called directly without invoking the lazy object initializer which saves on a select. Obviously if you call getStuff() it will cause a select to lazy load in the stuff string.

Caveat Alert!!

We have found one anomaly with this. First of all, the Jboss documentation states the following about how the @AccessType annotation is propagated:

The access type is overriden for the annotated element, if overriden on a class, all the properties of the given class inherit the access type. For root entities, the access type is considered to be the default one for the whole hierarchy (overridable at class or property level).

If the access type is marked as "property", the getters are scanned for annotations, if the access type is marked as "field", the fields are scanned for annotations. Otherwise the elements marked with @Id or @embeddedId are scanned.

You can override an access type for a property, but the element to annotate will not be influenced: for example an entity having access type field, can annotate a field with @AccessType("property"), the access type will then be property for this attribute, the the annotations still have to be carried on the field.

Our experience shows that the @AccessType("property") override works well and does not seem to be inherited in the manner described loosely above. All of the other fields in the class are still accessed as field type and any subclass fields are also accessed with reflection.

Exception!! The one place were we have had to override the annotation is in a subclass which uses the @CollectionOfElements annotation -- maybe only with lazy loaded collections.

// we have to have this access-type here because of the lazy collection // of Foo objects @AccessType("field") public class Bar { @Column @CollectionOfElements(fetch = FetchType.LAZY) private Foo[] foos; @Column(length=20) private String stuff; public String getStuff() { return stuff; }

Because this class has a lazy loaded collection of Foo objects that are using the "property" access type on the id field, it seemed to make all of Bar's fields property access as well. This would cause, for example, the stuff column length attribute to be ignored. To work around this we put the field access type annotation on the Bar class.

I consider myself to be far from a Hibernate guru so please send me mail to correct any problems with the above documentation or to share your own bits of wisdom that I can add to this page. Enjoy.

Free Spam Protection   Eggnog Recipe   Android ORM   Simple Java Magic   JMX using HTTP   OAuth 2.0 Simple Example   Great Eggnog Recipe   Christopher Randolph