Wednesday, November 12, 2008

Customizing Code Generation in Hibernate

Hibernate includes set of tools that can generate Java code from hbm xml definitions. The code generation is rather less documented, and it is not straight forward to customize the code generation. While implementing hibernate in one of my projects, we needed to do just that - customize the code generation. Though many propose to use hibernate annotations instead of hbm xml files, we were not quite ready for that. Since during the design and development phase, most of our database structure was in flux, I felt more comfortable generating the bulk of our code rather than writing them by hand - less errors! So I took a bit of time to delve into hibernate tools and here's what we did...

Hibernate tools (hibernate-tools.jar) uses freemarker templates for code generation. The templates are located inside the jar file. We extracted the hibernate-tools.jar contents into a folder. Among other things there were two folders containing the templates used for code generation - pojo (to generate pojo classes) and dao (to generate the data access objects).

Our intention was to modify the code generation to:
  1. Incorporate logging using log4j logger in the DAO methods.
  2. Externalize the creation of hibernate session as our application wants to control the session scope. We would not use the session factory method of DAO.
  3. The DAO contains many utility methods to fetch, persist and search data (finder methods). Since we externalized the hibernate session, all the utility methods in our DAO can now become static. This will make it easier for our application to use the utility methods without instantiating an object every time.
  4. We would like to add finder methods for arbitrary properties as specified in the hbm, apart from the finders based on id and natural id into the DAO as static methods, instead of generating it with the POJO.
  5. For ease of use our application would sometimes like to instantiate the DAO object and treat it as the POJO object while accessing the static methods as a DAO. This is not really a 'required' feature (and is against the design principles) and can be skipped though.
  6. While generating POJO classes, we would like to limit the extra class code specified in hbm files to the parent class and not generate them in the component classes.
  7. Add setter and getter validations to the property values in POJOs.
  8. Since we added setter and getter validations, we would like to use them in class constructors as well. So we need to modify the class constructors to call the setter methods instead of directly assigning values to fields.

Now here's how we went about changing the templates (each item below corresponds to the objectives above) ...
  1. Modified dao/daohome.ftl and introduced a static variable named log which is an instance of org.apache.log4j.Logger initialized with the name of the DAO class.
  2. Modified all utility methods in dao/daohome.ftl and included a method parameter of type org.hibernate.Session and named _session. Inside the methods, we used _session instead of a call to sessionFactory.getCurrentSession(). We also removed the getSessionFactory method which was not used anymore.
  3. Modified dao/daohome.ftl and made all utility methods static.
  4. Created a new ftl in dao folder named PojoFinderMethods.ftl and included it in dao/daohome.ftl inside the class declaration. In the PojoFinderMethods.ftl, we examined each property declaration and if a finder attribute was declared, generated a method with name as the attribute value that fired a hbm query to match the property to a passed argument.
  5. Modified dao/daohome.ftl and added extends clause to the class declaration.
  6. Modified pojo/Pojo.ftl and included a condition !pojo.isComponent() around the line that included PojoExtraClassCode.ftl.
  7. Modified pojo/PojoPropertyAccessors.ftl to look for "pre-cond" and "post-cond" attributes in the hbm specification. We expect java code in the attribute values which we plonk into the output. In cases where we used any external validators (like apache validator framework) we included them as additional imports in the hbm.
  8. Modified pojo/PojoConstructors.ftl and changed the direct assignments to call the setter methods.

Once these were done, we modified our build.xml to point the template directory to the modified templates.

<hibernatetool destdir="${sourcedir}" templatepath="${hbmtemplatedir}">
<configuration configurationfile="...hibernate.cfg.xml"/>
<hbm2java jdk5="true" ejb3="false"/>

Fired the ant script and out came sweet generated code! You can find all the modified templates we used here.


Anonymous said...

My only question is how to know which file you need to modify to change something i.e. have you found a documentation or did you reverse engineered hibernate generator?

Tanmay said...

No we did not reverse engineer. A look at the ftl script files reveals that Pojo.ftl and daohome.ftl are the respective starting points for the DAO and the POJO code generators. The freemarker syntax is not that difficult to grasp. Just follow through the code and you can trace the code generation.

Anonymous said...

thank you thank you thank you

Anonymous said...

Hi Tan,

Thank you for this usefull information, it helped me a lot modifying the templates to our needs!

Greetings, Jeroen

Anonymous said...

Thank you, it's very useful