This chapter covers
3.3 Object/relational mapping metadata
3.3.4 Handling global metadata
Consider the following situation: All of your domain model persistent classes are in the same package. However, you have to specify class names fully qualified, including the package, in every XML mapping file. It would be a lot easier to declare the package name once and then use only the short persistent class name.
Or, instead of enabling direct field access for every single property through the access="field" mapping attribute, you’d rather use a single switch to enable field access for all properties. Class- or package-scoped metadata would be much more convenient.
Some metadata is valid for the whole application. For example, query strings can be externalized to metadata and called by a globally unique name in the application code. Similarly, a query usually isn’t related to a particular class, and sometimes not even to a particular package. Other application-scoped metadata includes user-defined mapping types (converters) and data filter (dynamic view) definitions.
Let’s walk through some examples of global metadata in Hibernate XML map-pings and JDK 5.0 annotations.
Global XML mapping metadata
If you check the XML mapping DTD, you’ll see that the <hibernate-mapping>
root element has global options that are applied to the class mapping(s) inside it—some of these options are shown in the following example:
<hibernate-mapping schema="AUCTION"
default-lazy="false"
default-access="field"
auto-import="false">
<class ...>
...
</class>
</hibernate-mapping>
The schema attribute enables a database schema prefix, AUCTION, used by Hiber-nate for all SQL statements generated for the mapped classes. By setting default-lazy to false, you enable default outer-join fetching for some class associations, a
topic we’ll discuss in chapter 13, section 13.1, “Defining the global fetch plan.”
(This default-lazy="true" switch has an interesting side effect: It switches to Hibernate 2.x default fetching behavior—useful if you migrate to Hibernate 3.x but don’t want to update all fetching settings.) With default-access, you enable direct field access by Hibernate for all persistent properties of all classes mapped in this file. Finally, the auto-import setting is turned off for all classes in this file.
We’ll talk about importing and naming of entities in chapter 4, section 4.3, “Class mapping options.”
TIP Mapping files with no class declarations—Global metadata is required and present in any sophisticated application. For example, you may easily import a dozen interfaces, or externalize a hundred query strings. In large-scale applications, you often create mapping files without actual class mappings, and only imports, external queries, or global filter and type definitions. If you look at the DTD, you can see that <class> map-pings are optional inside the <hibernate-mapping> root element. Split up and organize your global metadata into separate files, such as AuctionTypes.hbm.xml, AuctionQueries.hbm.xml, and so on, and load them in Hibernate’s configuration just like regular mapping files.
However, make sure that all custom types and filters are loaded before any other mapping metadata that applies these types and filters to class mappings.
Let’s look at global metadata with JDK 5.0 annotations.
Global annotation metadata
Annotations are by nature woven into the Java source code for a particular class.
Although it’s possible to place global annotations in the source file of a class (at the top), we’d rather keep global metadata in a separate file. This is called pack-age metadata, and it’s enabled with a file named package-info.java in a particu-lar package directory:
@org.hibernate.annotations.TypeDefs({
@org.hibernate.annotations.TypeDef(
name="monetary_amount_usd",
typeClass = MonetaryAmountType.class,
parameters = { @Parameter(name="convertTo", value="USD") } ),
@org.hibernate.annotations.TypeDef(
name="monetary_amount_eur",
typeClass = MonetaryAmountType.class,
parameters = { @Parameter(name="convertTo", value="EUR") } )
})
@org.hibernate.annotations.NamedQueries({
@org.hibernate.annotations.NamedQuery(
name = "findItemsOrderByPrice",
query = "select i from Item i order by i.initialPrice)"
) })
package auction.persistence.types;
This example of a package metadata file, in the package auction.persis-tence.types, declares two Hibernate type converters. We’ll discuss the Hiber-nate type system in chapter 5, section 5.2, “The HiberHiber-nate type system.” You can now refer to the user-defined types in class mappings by their names. The same mechanism can be used to externalize queries and to define global identi-fier generators (not shown in the last example).
There is a reason the previous code example only includes annotations from the Hibernate package and no Java Persistence annotations. One of the (last-minute) changes made to the JPA specification was the removal of package visibil-ity of JPA annotations. As a result, no Java Persistence annotations can be placed in a package-info.java file. If you need portable global Java Persistence metadata, put it in an orm.xml file.
Note that you have to name a package that contains a metadata file in your Hibernate or JPA persistence unit configuration if you aren’t using automatic detection—see chapter 2, section 2.2.1, “Using Hibernate Annotations.”
Global annotations (Hibernate and JPA) can also be placed in the source code of a particular class, right after the import section. The syntax for the annotations is the same as in the package-info.java file, so we won’t repeat it here.
You now know how to write local and global mapping metadata. Another issue in large-scale applications is the portability of metadata.
Using placeholders
In any larger Hibernate application, you’ll face the problem of native code in your mapping metadata—code that effectively binds your mapping to a particular database product. For example, SQL statements, such as in formula, constraint, or filter mappings, aren’t parsed by Hibernate but are passed directly through to the database management system. The advantage is flexibility—you can call any native SQL function or keyword your database system supports. The disadvantage of put-ting native SQL in your mapping metadata is lost database portability, because your mappings, and hence your application, will work only for a particular DBMS (or even DBMS version).
Even simple things, such as primary key generation strategies, usually aren’t portable across all database systems. In the next chapter, we discuss a special iden-tifier generator called native, which is a built-in smart primary key generator. On Oracle, it uses a database sequence to generate primary key values for rows in a table; on IBMDB2, it uses a special identity primary key column by default. This is how you map it in XML:
<class name="Category" table="CATEGORY">
<id name="id" column="CATEGORY_ID" type="long">
<generator class="native"/>
</id>
...
</class>
We’ll discuss the details of this mapping later. The interesting part is the declara-tion class="native" as the identifier generator. Let’s assume that the portability this generator provides isn’t what you need, perhaps because you use a custom identifier generator, a class you wrote that implements the Hibernate IdentifierGenerator interface:
<id name="id" column="CATEGORY_ID" type="long">
<generator class="auction.custom.MyOracleGenerator"/>
</id>
The XML mapping file is now bound to a particular database product, and you lose the database portability of the Hibernate application. One way to deal with this issue is to use a placeholder in your XML file that is replaced during build when the mapping files are copied to the target directory (Ant supports this).
This mechanism is recommended only if you have experience with Ant or already need build-time substitution for other parts of your application.
A much more elegant variation is to use custom XML entities (not related to our application’s business entities). Let’s assume you need to externalize an ele-ment or attribute value in your XML files to keep it portable:
<id name="id" column="CATEGORY_ID" type="long">
<generator class="&idgenerator;"/>
</id>
The &idgenerator; value is called an entity placeholder. You can define its value at the top of the XML file as an entity declaration, as part of the document type definition:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"
[
<!ENTITY idgenerator "auction.custom.MyOracleGenerator">
]>
The XML parser will now substitute the placeholder on Hibernate startup, when mapping files are read.
You can take this one step further and externalize this addition to the DTD in a separate file and include the global options in all other mapping files:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"
[
<!ENTITY % globals SYSTEM "classpath://persistence/globals.dtd">
%globals;
]>
This example shows the inclusion of an external file as part of the DTD. The syn-tax, as often in XML, is rather crude, but the purpose of each line should be clear.
All global settings are added to the globals.dtd file in the persistence package on the classpath:
<!ENTITY idgenerator "auction.custom.MyOracleGenerator">
<!-- Add more options if needed... -->
To switch from Oracle to a different database system, just deploy a different glo-bals.dtd file.
Often, you need not only substitute an XML element or attribute value but also to include whole blocks of mapping metadata in all files, such as when many of your classes share some common properties, and you can’t use inheritance to cap-ture them in a single location. With XML entity replacement, you can externalize an XML snippet to a separate file and include it in other XML files.
Let’s assume all the persistent classes have a dateModified property. The first step is to put this mapping in its own file, say, DateModified.hbm.xml:
<property name="dateModified"
column=”DATE_MOD”
type="timestamp"/>
This file needs no XML header or any other tags. Now you include it in the map-ping file for a persistent class:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"
[
<!ENTITY datemodified SYSTEM "classpath://model/DateModified.hbm.xml">
]>
<hibernate-mapping>
<class name="Item" table="ITEM"
<id ...>
&datemodified;
...
</class>
The content of DateModified.hbm.xml will be included and be substituted for the
&datemodified; placeholder. This, of course, also works with larger XML snippets.
When Hibernate starts up and reads mapping files, XML DTDs have to be resolved by the XML parser. The built-in Hibernate entity resolver looks for the hibernate-mapping-3.0.dtd on the classpath; it should find the DTD in the hibernate3.jar file before it tries to look it up on the Internet, which happens automatically whenever an entity URL is prefixed with http://hibernate.source-forge.net/. The Hibernate entity resolver can also detect the classpath:// pre-fix, and the resource is then searched for in the classpath, where you can copy it on deployment. We have to repeat this FAQ: Hibernate never looks up the DTD on the Internet if you have a correct DTD reference in your mapping and the right JAR on the classpath.
The approaches we have described so far—XML, JDK 5.0 annotations, and XDoclet attributes—assume that all mapping information is known at develop-ment (or deploydevelop-ment) time. Suppose, however, that some information isn’t known before the application starts. Can you programmatically manipulate the mapping metadata at runtime?