2.2 Generating Some Class

     

Our mapping contains information about both the database and the Java class between which it maps. We can use it to help us create both. Let's look at the class first.

2.2.1 How do I do that?

The Hibernate Extensions you installed in Chapter 1 included a tool that can write Java source matching the specifications in a mapping document, and an Ant task that makes it easy to invoke from within an Ant build file. Edit build.xml to add the portions shown in bold in Example 2-2.

Example 2-2. The Ant build file updated for code generation
 1    <project name="Harnessing Hibernate: The Developer's Notebook"  2            default="db" basedir=".">  3    <!-- Set up properties containing important project directories -->  4    <property name="source.root" value="src"/>  5    <property name="class.root" value="classes"/>  6    <property name="lib.dir" value="lib"/>  7    <property name="data.dir" value="data"/>  8  9    <!-- Set up the class path for compilation and execution --> 10    <path id="project.class.path"> 11        <!-- Include our own classes, of course --> 12        <pathelement location="${class.root}" /> 13        <!-- Include jars in the project library directory --> 14        <fileset dir="${lib.dir}"> 15          <include name="*.jar"/> 16        </fileset> 17    </path> 18 19    <target name="db" description="Runs HSQLDB database management UI 20   against the database file--use when application is not running"> 21        <java classname="org.hsqldb.util.DatabaseManager" 22              fork="yes"> 23           <classpath refid="project.class.path"/> 24           <arg value="-driver"/> 25           <arg value="org.hsqldb.jdbcDriver"/> 26           <arg value="-url"/> 27           <arg value="jdbc:hsqldb:${data.dir}/music"/> 28           <arg value="-user"/> 29           <arg value="sa"/> 30        </java> 31    </target> 32 33  <!-- Teach Ant how to use Hibernate's code generation tool -->  34  <taskdef name="hbm2java"  35  classname="net.sf.hibernate.tool.hbm2java.Hbm2JavaTask"  36  classpathref="project.class.path"/>  37 38  <!-- Generate the java code for all mapping files in our source tree -->  39  <target name="codegen"  40  description="Generate Java source from the O/R mapping files">  41  <hbm2java output="${source.root}">  42  <fileset dir="${source.root}">  43  <include name="**/*.hbm.xml"/>  44  </fileset>  45  </hbm2java>  46  </target>  47 48   </project> 

We added a taskdef (task definition) and a new target to the build file. The task definition at line 33 teaches Ant a new trick: it tells Ant how to use the hbm2java tool that is part of the Hibernate Extensions, with the help of a class provided for this purpose. Note that it also specifies the class path to be used when invoking this tool, using the project.class.path definition found earlier in the file.

The codegen target at line 38 uses the new hbm2java task to run Hibernate's code generator on any mapping documents found in the src tree, writing the corresponding Java source. The pattern ' **/*.hbm.xml ' means 'any file ending in . hbm.xml , within the specified directory, or any subdirectory, however deeply nested.'

Let's try it! From within your top-level project directory (the folder containing build.xml ), type the following command:

  ant codegen  

You should see output like this:

 Buildfile: build.xml 
codegen: [hbm2java] Processing 1 files. [hbm2java] Building hibernate objects [hbm2java] log4j:WARN No appenders could be found for logger (net.sf. hibernate.util.DTDEntityResolver). [hbm2java] log4j:WARN Please initialize the log4j system properly.

The warnings are griping about the fact that we haven't taken the trouble to set up the logging environment that Hibernate expects. We'll see how to do that in the next example. For now, if you look in the directory src/com/oreilly/hh , you'll see that a new file named Track.java has appeared, with the content shown in Example 2-3.

Example 2-3. Code generated from the Track mapping document
 1    package com.oreilly.hh;   2   3    import java.io.Serializable;   4    import java.util.Date;   5    import org.apache.commons.lang.builder.EqualsBuilder;   6    import org.apache.commons.lang.builder.HashCodeBuilder;   7    import org.apache.commons.lang.builder.ToStringBuilder;   8   9    /**  10     *       Represents a single playable track in the music database.  11     *       @author Jim Elliott (with help from Hibernate)  12     *  13    */  14    public class Track implements Serializable {  15  16         /** identifier field */  17         private Integer id;  18  19         /** persistent field */  20         private String title;  21  22         /** persistent field */  23         private String filePath;  24  25         /** nullable persistent field */  26         private Date playTime;  27  28         /** nullable persistent field */  29         private Date added;  30  31         /** nullable persistent field */  32         private short volume;  33  34         /** full constructor */  35         public Track(String title, String filePath, Date playTime,                         Date added, short volume) {  36              this.title = title;  37              this.filePath = filePath;  38              this.playTime = playTime;  39              this.added = added;  40              this.volume = volume;  41         }  42  43         /** default constructor */  44         public Track() {  45         }  46  47         /** minimal constructor */  48         public Track(String title, String filePath) {  49             this.title = title;  50             this.filePath = filePath;  51         }  52  53         public Integer getId() {  54             return this.id;  55         }  56  57         protected void setId(Integer id) {  58             this.id = id;  59         }  60  61         public String getTitle() {  62             return this.title;  63         }  64  65         public void setTitle(String title) {  66             this.title = title;  67         }  68  69         public String getFilePath() {  70             return this.filePath;  71         }  72  73         public void setFilePath(String filePath) {  74             this.filePath = filePath;  75         }  76  77         /**  78          * Playing time  79          */  80         public Date getPlayTime() {  81             return this.playTime;  82         }  83  84         public void setPlayTime(Date playTime) {  85             this.playTime = playTime;  86         }  87  88         /**  89          * When the track was created  90          */  91         public Date getAdded() {  92             return this.added;  93         }  94  95         public void setAdded(Date added) {  96             this.added = added;  97         }  98  99         /** 100          * How loud to play the track 101          */ 102         public short getVolume() { 103             return this.volume; 104         } 105 106         public void setVolume(short volume) { 107             this.volume = volume; 108         } 109  110         public String toString() { 111             return new ToStringBuilder(this) 112                 .append("id", getId()) 113                 .toString(); 114         } 115  116         public boolean equals(Object other) { 117             if ( !(other instanceof Track) ) return false; 118             Track castOther = (Track) other; 119             return new EqualsBuilder() 120                 .append(this.getId(), castOther.getId()) 121                 .isEquals(); 122         } 123  124         public int hashCode() { 125             return new HashCodeBuilder() 126                 .append(getId()) 127                 .toHashCode(); 128         } 129  130     } 

2.2.2 What just happened ?

Ant found all files in our source tree ending in . hbm.xml (just one, so far) and fed it to the Hibernate code generator, which analyzed it, and wrote a Java class meeting the specifications we provided for the Track mapping.

NOTE

That can save a lot of time and fairly repetitive activity. I could get used to it.

You may find it worthwhile to compare the generated Java source with the mapping specification from which it arose (Example 2-1). The source starts out with the proper package declaration, which is easy for hbm2java to figure out from the fully qualified class name required in the mapping file. There are a couple of imports to make the source more readable. The three potentially unfamiliar entries (lines 5-7) are utilities from the Jakarta Commons project that help in the creation of correctly implemented and useful toString() , equals() , and hashCode() methods .

The class-level JavaDoc at line 10 should look familiar, since it comes right from the 'class-description' meta tag in our mapping document. The field declarations are derived from the id (line 17) and property (lines 20-32) tags defined in the mapping. The Java types used are derived from the property types in the mapping document. We'll delve into the full set of value types supported by Hibernate later on. For now, the relationship between the types in the mapping document and the Java types used in the generated code should be fairly clear.

One curious detail is that an Integer wrapper has been used for id , while volume is declared as a simple, unwrapped short . Why the difference? It relates to the fact that the ID/key property has many important roles to play in the O/R mapping process (which is why it gets a special XML tag in the mapping document, rather than being just another property ). Although we left it out in our specification, one of the choices you need to make when setting up an ID is to pick a special value to indicate that a particular instance has not yet been saved into the database. Leaving out this unsaved-value attribute, as we did, tells Hibernate to use its default interpretation, which is that unsaved values are indicated by an ID of null . Since native int values can't be null , they must be wrapped in a java.lang.Integer , and Hibernate took care of this for us.

When it comes to the volume property, Hibernate has no special need or use for it, so it trusts us to know what we're doing. If we want to be able to store null values for volume , perhaps to indicate 'no change,' we need to explicitly use java.lang.Short rather than short in our mapping document. (Had we not been sneakily pointing out this difference, our example would be better off explicitly using java.lang.Integer in our ID mapping too, just for clarity.)

NOTE

I know, I'm a perfectionist. I only bother to pick nits because I think Hibernate is so useful!

Another thing you might notice about these field declarations is that their JavaDoc is quite generic ”you may be wondering what happened to the 'field-description' meta tags we put in the mapping document for playTime , added and volume . It turns out they appear only later, in the JavaDoc for the getter methods. They are not used in the setters, the actual field declarations, nor as @param entries for the constructor. As an avid user of a code-completing Java editor, I count on pop-up JavaDoc as I fill in arguments to method calls, so I'm a little disappointed by this limitation. Of course, since this is an open source project, any of us can get involved and propose or undertake this simple fix. Indeed, you may find this already remedied by the time you read this book. Once robust field and parameter documentation is in place, I'd definitely advocate always providing a brief but accurate field-description entry for your properties.

After the field declarations come a trio of constructors. The first (line 35) establishes values for all properties, the second (line 44) allows instantiation without any arguments (this is required if you want the class to be usable as a bean, such as on a Java Server Page, a very common use for data classes like this), and the last (line 48) fills in just the values we've indicated must not be null . Notice that none of the constructors set the value of id ; this is the responsibility of Hibernate when we get the object out of the database, or insert it for the first time.

Consistent with that, the setId() method on line 57 is protected , as requested in our id mapping. The rest of the getters and setters are not surprising; this is all pretty much boilerplate code (which we've all written too many times), which is why it's so nice to be able to have the Hibernate extensions generate it for us.

If you want to use Hibernate's generated code as a starting point and then add some business logic or other features to the generated class, be aware that all your changes will be silently discarded the next time you run the code generator. In such a project you will want to be sure the hand-tweaked classes are not regenerated by any Ant build target.


Even though we're having Hibernate generate our data classes in this example, it's important to point out that the getters and setters it creates are more than a nice touch. You need to put these in your persistent classes for any properties you want to persist, since Hibernate's fundamental persistence architecture is based on reflective access to Java- Beans -style properties. They don't need to be public if you don't want them to; Hibernate has ways of getting at even properties declared protected or private , but they do need accessor methods. Think of it as enforcing good object design; the Hibernate team wants to keep the implementation details of actual instance variables cleanly separated from the persistence mechanism.



Hibernate. A Developer's Notebook
Hibernate: A Developers Notebook
ISBN: 0596006969
EAN: 2147483647
Year: 2003
Pages: 65
Authors: James Elliott

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net