Today I needed to do a small performance enhancement on an application using Hibernate annotations. The optimization was to ensure that a @Lob property on a Hibernate entity was lazy loaded. By default Hibernate will allow you to lazy load associations, but any lazy loading of an entities properties is not enabled by default. This is how I went about achieving the small optimization...
I had an entity class called "UploadedData", and on it was a property called "data". It looked like below:
@Entity
@Table(name = "uploaded_datas")
public class UploadedData
{
....
@Column(name = "name")
private String name;
@Lob
@Column(name = "data")
@Basic(fetch = FetchType.LAZY)
private byte[] data;
....
}
Note that the "data" attribute is annotated to be lazily fetched. This was to avoid having to load large chunks of binary data into memory when it wasn't necessarily required to be made available to the client (often I only ever needed to get the "name" of the entity).
However, the SQL generated for selecting from the "uploaded_datas" table naturally still contained the binary data column in the list of selected columns when selecting entities from the table. It looked like this (notice the inclusion of the "data" column in the list of selected columns):
select
uploadedda0_.id as id11_1_,
uploadedda0_.uploaded_data_category_id as uploaded7_11_1_,
uploadedda0_.content_type as content2_11_1_,
uploadedda0_.downloadable as download3_11_1_,
uploadedda0_.filename as filename11_1_,
uploadedda0_.name as name11_1_,
uploadedda0_.data as data11_1_, -- <========== eagerly loaded data
uploadedda1_.id as id10_0_,
uploadedda1_.name as name10_0_
from
uploaded_datas uploadedda0_
left outer join
uploaded_datas_categories uploadedda1_
on uploadedda0_.uploaded_data_category_id=uploadedda1_.id
In order to enable lazy fetching on this attribute I needed to do some byte code instrumentation...
So, I'm naturally using Maven. I included the following dependencies into the relevant module:
org.hibernate
hibernate-core
3.3.1.GA
runtime
javassist
javassist
3.8.0.GA
runtime
org.apache.ant
ant
1.7.1
This provided the hibernate classes required to instrument entities for lazy fetching of attributes. And the ant classes needed to run ant. Next, I hooked in a call to the relevant Ant plugin that the hibernate dudes made available for instrumenting classes:
.....
maven-antrun-plugin
process-classes
run
.....
Notice that I specified the name of the only class (for the time being) that I want instrumented. Pretty easy. The ant task would be run during the "process-classes" phase of the maven lifecycle, and perform whatever magic it needs to enable the lazy-fetching annotation to work correctly. And thats it! All I needed to do after that was re-compile using maven (mvn clean install), and this automatically triggered the byte code instrumentation via the ant task. Brilliant! Once again a prime example of a build system working via configuration over implementation (where maven == "configuration" and ant == "implementation")...
I had Hibernates SQL output turned on throughout this whole process. The final result of loading a page with alot of uploaded data entities was as follows:
-- no longer includes the data column in the list of selected columns!!
select
uploadedda0_.id as id11_1_,
uploadedda0_.uploaded_data_category_id as uploaded7_11_1_,
uploadedda0_.content_type as content2_11_1_,
uploadedda0_.downloadable as download3_11_1_,
uploadedda0_.filename as filename11_1_,
uploadedda0_.name as name11_1_,
uploadedda1_.id as id10_0_,
uploadedda1_.name as name10_0_
from
uploaded_datas uploadedda0_
left outer join
uploaded_datas_categories uploadedda1_
on uploadedda0_.uploaded_data_category_id=uploadedda1_.id
-- a NEW SECOND, EXPLICIT select statement will load data only when required
select
uploadedda_.data as uploaded6_11_
from
uploaded_datas uploadedda_
where
uploadedda_.id=?
And thats it! The "data" column is not included in the first select (which returned all rows from the uploaded_datas table). However, when the "data" was required from one of the UploadedData entities then Hibernate generated the second select, returning the huge BLOB for that entity specifically. Brilliant ...
Comments ...