When using <hbm2java> or the eclipse plugin to generate POJO java code you have the possibility to control certain aspects of the code generation. This is primarily done through the <meta> tag in the mapping files. The following section describes the possible meta tags and their use.
The <meta> tag is a simple way of annotating the hbm.xml with information, so tools have a natural place to store/read information that is not directly related to the Hibernate core.
You can use the
<meta> tag to e.g. tell
hbm2java
to only generate
"protected" setters, have
classes always implement a certain set of interfaces or even have them
extend a certain base class and even more.
The following example shows how to use various <meta> attributes and the resulting java code.
<class name="Person"> <meta attribute="class-description"> Javadoc for the Person class @author Frodo </meta> <meta attribute="implements">IAuditable</meta> <id name="id" type="long"> <meta attribute="scope-set">protected</meta> <generator class="increment"/> </id> <property name="name" type="string"> <meta attribute="field-description">The name of the person</meta> </property> </class>
The above hbm.xml will produce something like the following (code shortened for better understanding). Notice the Javadoc comment and the protected set methods:
// default package import java.io.Serializable; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; /** * Javadoc for the Person class * @author Frodo */ public class Person implements Serializable, IAuditable { public Long id; public String name; public Person(java.lang.String name) { this.name = name; } public Person() { } public java.lang.Long getId() { return this.id; } protected void setId(java.lang.Long id) { this.id = id; } /** * The name of the person */ public java.lang.String getName() { return this.name; } public void setName(java.lang.String name) { this.name = name; } }
Table 6.1. Supported meta tags
Attribute | Description |
---|---|
class-description | inserted into the javadoc for classes |
field-description | inserted into the javadoc for fields/properties |
interface | If true an interface is generated instead of an class. |
implements | interface the class should implement |
extends | class the class should extend (ignored for subclasses) |
generated-class | overrule the name of the actual class generated |
scope-class | scope for class |
scope-set | scope for setter method |
scope-get | scope for getter method |
scope-field | scope for actual field |
default-value | default initializatioin value for a field |
use-in-tostring | include this property in the
toString() |
use-in-equals | include this property in the equals()
and hashCode() method. If no use-in-equals is
specificed, no equals/hashcode will be generated. |
gen-property | property will not be generated if false (use with care) |
property-type | Overrides the default type of property. Use this with any tag's to specify the concrete type instead of just Object. |
class-code | Extra code that will inserted at the end of the class |
extra-import | Extra import that will inserted at the end of all other imports |
Attributes declared via the <meta> tag are per default "inherited" inside an hbm.xml file.
What does that mean? It means that if you e.g want to have all your
classes implement IAuditable
then you just add an
<meta
attribute="implements">IAuditable</meta> in the top of
the
hbm.xml file, just after
<hibernate-mapping>. Now all classes defined in
that
hbm.xml file will implement
IAuditable
!
This applies to all
<meta>
-tags. Thus it can also e.g. be used to
specify that all fields should be declare protected, instead of the
default private. This is done by adding <meta
attribute="scope-field">protected</meta>
at e.g. just
under the <class>
tag and all fields of that
class will be protected.
To avoid having a <meta> tag inherited then you can simply specify inherit = "false" for the attribute, e.g. <meta attribute = "scope-class" inherit = "false">public abstract</meta> will restrict the "class-scope" to the current class, not the subclasses.
The following are some good practices when using <meta> attributes.
If we have two entities with a bi-directional association
between them and define at class scope level the meta attributes:
use-in-string, use-in-equals
:
<hibernate-mapping> <class name="Person"> <meta attribute="use-in-tostring">true</meta> <meta attribute="use-in-equals">true</meta> ... </class> </hibernate-mapping>
and for Event.hbm file:
<hibernate-mapping> <class name="events.Event" table="EVENTS"> <meta attribute="use-in-tostring">true</meta> <meta attribute="use-in-equals">true</meta> <id name="id" column="EVENT_ID"> <generator class="native"/> </id> <property name="date" type="timestamp" column="EVENT_DATE"/> <property name="title"/> <set name="participants" table="PERSON_EVENT" inverse="true"> <key column="EVENT_ID"/> <many-to-many column="PERSON_ID" class="events.Person"/> </set> </class> </hibernate-mapping>
Then
<hbm2java> will assume you want to
include all properties and collections in the
toString()/equals()
methods and this can result in
infinite recursive calls.
To remedy this you have to decide which side of the association
will include the other part (if at all) in the
toString()/equals()
methods. Therefore it is not a
good practice to put at class scope such meta attributes, unless you
are defining a class without bi-directional associations
We recomend instead to add the meta
attributes at the property level:
<hibernate-mapping> <class name="events.Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <meta attribute="use-in-tostring">true</meta> <generator class="native"/> </id> <property name="date" type="timestamp" column="EVENT_DATE"/> <property name="title"> <meta attribute="use-in-tostring">true</meta> <meta attribute="use-in-equals">true</meta> </property> <set name="participants" table="PERSON_EVENT" inverse="true"> <key column="EVENT_ID"/> <many-to-many column="PERSON_ID" class="events.Person"/> </set> </class> </hibernate-mapping>
and now for Person
:
<hibernate-mapping> <class name="Person"> <meta attribute="class-description"> Javadoc for the Person class @author Frodo </meta> <meta attribute="implements">IAuditable</meta> <id name="id" type="long"> <meta attribute="scope-set">protected</meta> <meta attribute="use-in-tostring">true</meta> <generator class="increment"/> </id> <property name="name" type="string"> <meta attribute="field-description">The name of the person</meta> <meta attribute="use-in-tostring">true</meta> </property> </class> </hibernate-mapping>
For equal()/hashCode()
method generation, you
have to take into account that the attributes that participate on such
method definition, should take into account only attributes with
business meaning (the name, social security number, etc, but no
generated id's, for example).
This is important because Java's hashbased collections, such as java.util.Set relies on equals() and hashcode() to be correct and not change for objects in the set; this can be a problem if the id gets assigned for an object after you inserted it into a set.
Therefore automatically configuration the generation of
equals()/hashCode()
methods specifying at class
scope level the
<meta> attribute
use-in-equals
could be a dangerous decision that
could produce non expected side-effect.
Here you can get more in-depth explanation on the subject of equals() and hashcode().
This section shows an example for using meta attributes (including userspecific attributes) together with the code generation features in Hibernate Tools.
The usecase being implemented is to automatically insert some pre- and post-conditions into the getter and setters of the generated POJO.
With an <meta attribute="class-code">, you can add addional methods on a given class, nevertheless such <meta> attribute can not be used at property scope level and Hibernate Tools does not provide such <meta> attributes.
A possibly solution for this is to modify the freemarker templates responsable for generating the POJO's. If you look inside hibernate-tools.jar, you can find the template: pojo/PojoPropertyAccessor.ftl
This file is as the named indicates used to generate property accessors for pojo's.
Extract the PojoPropertyAccessor.ftl into a local folder i.e. ${hbm.template.path}, respecting the whole path, for example: ${hbm.template.path}/pojo/PojoPropertyAccessor.ftl
The contents of the file is something like this:
<#foreach property in pojo.getAllPropertiesIterator()> ${pojo.getPropertyGetModifiers(property)} ${pojo.getJavaTypeName(property, jdk5)} ${pojo.getGetterSignature(property)}() { return this.${property.name}; } ${pojo.getPropertySetModifiers(property)} void set${pojo.getPropertyName(property)}(${pojo.getJavaTypeName(property, jdk5)} ${property.name}) { this.${property.name} = ${property.name}; } </#foreach>
We can add conditionally pre/post-conditions on our
set
method generation just adding a little
Freemarker syntax to the above source code:
<#foreach property in pojo.getAllPropertiesIterator()> ${pojo.getPropertyGetModifiers(property)} ${pojo.getJavaTypeName(property, jdk5)} ${pojo.getGetterSignature(property)}() { return this.${property.name}; } ${pojo.getPropertySetModifiers(property)} void set${pojo.getPropertyName(property)}(${pojo.getJavaTypeName(property, jdk5)} ${property.name}) { <#if pojo.hasMetaAttribute(property, "pre-cond")> ${c2j.getMetaAsString(property, "pre-cond","\n")} </#if> this.${property.name} = ${property.name}; <#if pojo.hasMetaAttribute(property, "post-cond")> ${c2j.getMetaAsString(property, "post-cond","\n")} </#if> } </#foreach>
Now if in any
*hbm.xml file we define the
<meta> attributes:
pre-cond
or post-cond
, their
contents will be generated into the body of the relevant
set
method.
As an examlpe let us add a pre-condition for property
name
preventing no Person
can
have an empty name. So we have to modify the
Person.hbm.xml file like this:
<hibernate-mapping> <class name="Person"> <id name="id" type="long"> <generator class="increment"/> </id> <property name="firstName" type="string"> <meta attribute="pre-cond"><![CDATA[ if ((firstName != null) && (firstName.length() == 0) ) { throw new IllegalArgumentException("firstName can not be an empty String"); }]]> </meta> </property> </class> </hibernate-mapping>
i) If you don'' use <[[CDATA[]]>
you have to scape the & symbol, i.e.: &
ii). Note that we are referring to "firstName" directly and this is the parameter name not the actual field name. If you want to refer the field you have to use "this.firstName" instead.
Finally we have to generate the Person.java
class, for this we can use both Eclipse and Ant as long as you
remember to set or fill in the templatepath setting. For Ant we
configure
<hibernatetool> task via
the templatepath
attribute as in:
<target name="hbm2java"> <taskdef name="hibernatetool" classname="org.hibernate.tool.ant.HibernateToolTask" classpathref="lib.classpath"/> <hibernatetool destdir="${hbm2java.dest.dir}" templatepath="${hbm.template.path}"> <classpath> <path refid="pojo.classpath"/> </classpath> <configuration> <fileset dir="${hbm2java.src.dir}"> <include name="**/*.hbm.xml"/> </fileset> </configuration> <hbm2java/> </hibernatetool> </target>
Invoking the target <hbm2java> will generate on the ${hbm2java.dest.dir} the file: Person.java:
// default package import java.io.Serializable; public class Person implements Serializable { public Long id; public String name; public Person(java.lang.String name) { this.name = name; } public Person() { } public java.lang.Long getId() { return this.id; } public void setId(java.lang.Long id) { this.id = id; } public java.lang.String getName() { return this.name; } public void setName(java.lang.String name) { if ((name != null) && (name.length() == 0)) { throw new IllegalArgumentException("name can not be an empty String"); } this.name = name; } }