2.3. Reverse engineering a legacy database

Your first step when mapping a legacy database likely involves an automatic reverse-engineering procedure. After all, an entity schema already exists in your database system. To make this easier, Hibernate has a set of tools that can read a schema and produce various artifacts from this metadata, including XML mapping files and Java source code. All of this is template-based, so many customizations are possible.

You can control the reverse-engineering process with tools and tasks in your Ant build. The HibernateToolTask you used earlier to export SQL DDL from Hibernate mapping metadata has many more options, most of which are related to reverse engineering, as to how XML mapping files, Java code, or even whole application skeletons can be generated automatically from an existing database schema.

We'll first show you how to write an Ant target that can load an existing database into a Hibernate metadata model. Next, you'll apply various exporters and produce XML files, Java code, and other useful artifacts from the database tables and columns.

2.3.1. Creating a database configuration

Let's assume that you have a new WORKDIR with nothing but the lib directory (and its usual contents) and an empty src directory. To generate mappings and code from an existing database, you first need to create a configuration file that contains your database connection settings:

hibernate.dialect = org.hibernate.dialect.HSQLDialect
hibernate.connection.driver_class = org.hsqldb.jdbcDriver
hibernate.connection.url = jdbc:hsqldb:hsql://localhost
hibernate.connection.username = sa

Store this file directly in WORKDIR, and name it helloworld.db.properties. The four lines shown here are the minimum that is required to connect to the database and read the metadata of all tables and columns. You could have created a Hibernate XML configuration file instead of hibernate.properties, but there is no reason to make this more complex than necessary.

Write the Ant target next. In a build.xml file in your project, add the following code:

<taskdef name="hibernatetool"
     classname="org.hibernate.tool.ant.HibernateToolTask"
     classpathref="project.classpath"/>

<target name="reveng.hbmxml"
        description="Produces XML mapping files in src directory">

    <hibernatetool destdir="${basedir}/src">

        <jdbcconfiguration
            propertyfile="${basedir}/helloworld.db.properties"
            revengfile="${basedir}/helloworld.reveng.xml"/>

        <hbm2hbmxml/> <!-- Export Hibernate XML files -->
        <hbm2cfgxml/> <!-- Export a hibernate.cfg.xml file -->

    </hibernatetool>

</target>

The HibernateToolTask definition for Ant is the same as before. We assume that you'll reuse most of the build file introduced in previous sections, and that references such as project.classpath are the same. The <hibernatetool> task is set with WORKDIR/src as the default destination directory for all generated artifacts.

A <jdbconfiguration> is a Hibernate tool configuration that can connect to a database via JDBC and read the JDBC metadata from the database catalog. You usually configure it with two options: database connection settings (the properties file) and an optional reverse-engineering customization file.

The metadata produced by the tool configuration is then fed to exporters. The example Ant target names two such exporters: the hbm2hbmxml exporter, as you can guess from its name, takes Hibernate metadata (hbm) from a configuration, and generates Hibernate XML mapping files; the second exporter can prepare a hibernate.cfg.xml file that lists all the generated XML mapping files.

Before we talk about these and various other exporters, let's spend a minute on the reverse-engineering customization file and what you can do with it.

2.3.2. Customizing reverse engineering

JDBC metadata—that is, the information you can read from a database about itself via JDBC—often isn't sufficient to create a perfect XML mapping file, let alone Java application code. The opposite may also be true: Your database may contain information that you want to ignore (such as particular tables or columns) or that you wish to transform with nondefault strategies. You can customize the reverse-engineering procedure with a reverse-engineering configuration file, which uses an XML syntax.

Let's assume that you're reverse-engineering the "Hello World" database you created earlier in this chapter, with its single MESSAGES table and only a few columns. With a helloworld.reveng.xml file, as shown in listing 2.17, you can customize this reverse engineering.

Listing 2-17. Configuration for customized reverse engineering
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-reverse-engineering SYSTEM 
"http://hibernate.sourceforge.net/
hibernate-reverse-engineering-3.0.dtd"> <hibernate-reverse-engineering> <table-filter match-name=".*" package="hello"/>
<table name="MESSAGES" schema="PUBLIC" class="Message">
<primary-key>
<generator class="increment"/> <key-column name="MESSAGE_ID" property="id" type="long"/> </primary-key> <column name="MESSAGE_TEXT" property="text"/>
<foreign-key constraint-name="FK_NEXT_MESSAGE">
<many-to-one property="nextMessage"/> <set exclude="true"/> </foreign-key> </table> </hibernate-reverse-engineering>

❶ This XML file has its own DTD for validation and autocompletion.

❷ A table filter can exclude tables by name with a regular expression. However, in this example, you define a a default package for all classes produced for the tables matching the regular expression.

❸ You can customize individual tables by name. The schema name is usually optional, but HSQLDB assigns the PUBLIC schema to all tables by default so this setting is needed to identify the table when the JDBC metadata is retrieved. You can also set a custom class name for the generated entity here.

❹ The primary key column generates a property named id, the default would be messageId. You also explicitly declare which Hibernate identifier generator should be used.

❺ An individual column can be excluded or, in this case, the name of the generated property can be specified—the default would be messageText.

❻ If the foreign key constraint FK_NEXT_MESSAGE is retrieved from JDBC metadata, a many-to-one association is created by default to the target entity of that class. By matching the foreign key constraint by name, you can specify whether an inverse collection (one-to-many) should also be generated (the example excludes this) and what the name of the many-to-one property should be.

If you now run the Ant target with this customization, it generates a Message.hbm.xml file in the hello package in your source directory. (You need to copy the Freemarker and jTidy JAR files into your library directory first.) The customizations you made result in the same Hibernate mapping file you wrote earlier by hand, shown in listing 2.2.

In addition to the XML mapping file, the Ant target also generates a Hibernate XML configuration file in the source directory:

<hibernate-configuration>
  <session-factory>
    <property name="hibernate.connection.driver_class">
        org.hsqldb.jdbcDriver
    </property>
    <property name="hibernate.connection.url">
        jdbc:hsqldb:hsql://localhost
    </property>
    <property name="hibernate.connection.username">
        sa
    </property>
    <property name="hibernate.dialect">
        org.hibernate.dialect.HSQLDialect
    </property>

    <mapping resource="hello/Message.hbm.xml" />

  </session-factory>
</hibernate-configuration>

The exporter writes all the database connection settings you used for reverse engineering into this file, assuming that this is the database you want to connect to when you run the application. It also adds all generated XML mapping files to the configuration.

What is your next step? You can start writing the source code for the Message Java class. Or you can let the Hibernate Tools generate the classes of the domain model for you.

2.3.3. Generating Java source code

Let's assume you have an existing Hibernate XML mapping file for the Message class, and you'd like to generate the source for the class. As discussed in chapter 3, a plain Java entity class ideally implements Serializable, has a no-arguments constructor, has getters and setters for all properties, and has an encapsulated implementation.

Source code for entity classes can be generated with the Hibernate Tools and the hbm2java exporter in your Ant build. The source artifact can be anything that can be read into a Hibernate metadata model—Hibernate XML mapping files are best if you want to customize the Java code generation.

Add the following target to your Ant build:

<target name="reveng.pojos"
        description="Produces Java classes from XML mappings">

    <hibernatetool destdir="${basedir}/src">

        <configuration>

            <fileset dir="${basedir}/src">
                <include name="**/*.hbm.xml"/>
            </fileset>
        </configuration>

        <hbm2java/> <!-- Generate entity class source -->

    </hibernatetool>

</target>

The <configuration> reads all Hibernate XML mapping files, and the <hbm2java> exporter produces Java source code with the default strategy.

Customizing entity class generation

By default, hbm2java generates a simple entity class for each mapped entity. The class implements the Serializable marker interface, and it has accessor methods for all properties and the required constructor. All attributes of the class have private visibility for fields, although you can change that behavior with the <meta> element and attributes in the XML mapping files.

The first change to the default reverse engineering behavior you make is to restrict the visibility scope for the Message's attributes. By default, all accessor methods are generated with public visibility. Let's say that Message objects are immutable; you wouldn't expose the setter methods on the public interface, but only the getter methods. Instead of enhancing the mapping of each property with a <meta> element, you can declare a meta-attribute at the class level, thus applying the setting to all properties in that class:

<class name="Message"
   table="MESSAGES">

       <meta attribute="scope-set">private</meta>
        ...

</class>

The scope-set attribute defines the visibility of property setter methods.

The hbm2java exporter also accepts meta-attributes on the next higher-level, in the root <hibernate-mapping> element, which are then applied to all classes mapped in the XML file. You can also add fine-grained meta-attributes to single property, collection, or component mappings.

One (albeit small) improvement of the generated entity class is the inclusion of the text of the Message in the output of the generated toString() method. The text is a good visual control element in the log output of the application. You can change the mapping of Message to include it in the generated code:

<property name="text" type="string">
    <meta attribute="use-in-tostring">true</meta>
    <column name="MESSAGE_TEXT" />
</property>

The generated code of the toString() method in Message.java looks like this:

public String toString() {
    StringBuffer buffer = new StringBuffer();
    buffer.append(getClass().getName())
        .append("@")
        .append( Integer.toHexString(hashCode()) )
        .append(" [");
        .append("text").append("='").append(getText()).append("' ");
        .append("]");

    return buffer.toString();
}

Meta-attributes can be inherited; that is, if you declare a use-in-tostring at the level of a <class> element, all properties of that class are included in the toString() method. This inheritance mechanism works for all hbm2java meta-attributes, but you can turn it off selectively:

<meta attribute="scope-class" inherit="false">public abstract</meta>

Setting inherit to false in the scope-class meta-attribute creates only the parent class of this <meta> element as public abstract, but not any of the (possibly) nested subclasses.

The hbm2java exporter supports, at the time of writing, 17 meta-attributes for fine-tuning code generation. Most are related to visibility, interface implementation, class extension, and predefined Javadoc comments. Refer to the Hibernate Tools documentation for a complete list.

If you use JDK 5.0, you can switch to automatically generated static imports and generics with the jdk5="true" setting on the <hbm2java> task. Or, you can produce EJB 3.0 entity classes with annotations.

Generating Java Persistence entity classes

Normally, you use either Hibernate XML mapping files or JPA annotations in your entity class source code to define your mapping metadata, so generating Java Persistence entity classes with annotations from XML mapping files doesn't seem reasonable. However, you can create entity class source code with annotations directly from JDBC metadata, and skip the XML mapping step. Look at the following Ant target:

<target name="reveng.entities"
        description="Produces Java entity classes in src directory">

    <hibernatetool destdir="${basedir}/src">

        <jdbcconfiguration
            propertyfile="${basedir}/helloworld.db.properties"
            revengfile="${basedir}/helloworld.reveng.xml"/>

        <hbm2java jdk5="true" ejb3="true"/>
        <hbm2cfgxml ejb3="true"/>

    </hibernatetool>

</target>

This target generates entity class source code with mapping annotations and a hibernate.cfg.xml file that lists these mapped classes. You can edit the Java source directly to customize the mapping, if the customization in helloworld.reveng.xml is too limited.

Also note that all exporters rely on templates written in the FreeMarker template language. You can customize the templates in whatever way you like, or even write your own. Even programmatic customization of code generation is possible. The Hibernate Tools reference documentation shows you how these options are used.

Other exporters and configurations are available with the Hibernate Tools:

  • An <annotationconfiguration> replaces the regular <configuration> if you want to read mapping metadata from annotated Java classes, instead of XML mapping files. Its only argument is the location and name of a hibernate.cfg.xml file that contains a list of annotated classes. Use this approach to export a database schema from annotated classes.

  • An <ejb3configuration> is equivalent to an <annotationconfiguration>, except that it can scan for annotated Java classes automatically on the classpath; it doesn't need a hibernate.cfg.xml file.

  • The <hbm2dao> exporter can create additional Java source for a persistence layer, based on the data access object pattern. At the time of writing, the templates for this exporter are old and need updating. We expect that the finalized templates will be similar to the DAO code shown in chapter 16, section 16.2, "Creating a persistence layer."

  • The <hbm2doc> exporter generates HTML files that document the tables and Java entities.

  • The <hbmtemplate> exporter can be parameterized with a set of custom FreeMarker templates, and you can generate anything you want with this approach. Templates that produce a complete runable skeleton application with the JBoss Seam framework are bundled in the Hibernate Tools.

You can get creative with the import and export functionality of the tools. For example, you can read annotated Java classes with <annotationconfiguration> and export them with <hbm2hbmxml>. This allows you to develop with JDK 5.0 and the more convenient annotations but deploy Hibernate XML mapping files in production (on JDK 1.4).

Let's finish this chapter with some more advanced configuration options and integrate Hibernate with Java EE services.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.148.4