Section 20.3. Configuration and Mapping


20.3. Configuration and Mapping

Hibernate applications need two separate sets of configuration information. The first is global metadata that connects the Hibernate framework to your specific persistence store and provides the framework with any information about finding, connecting to, and manipulating that store. The second is the mappings that connect your domain objects to the tables where they will be persisted, also defining the relationships between your classes and how those relationships will manifest in the database.

20.3.1. Global Configuration

Hibernate needs to be told where to find the database that your application will be using and how to connect to it. Additionally, you can configure a variety of services to be provided to your application from the Hibernate framework. You can do this programmatically using the Configuration and Hibernate classes provided in the Hibernate APIs (see "The Hibernate API" later in this chapter) or you can do it declaratively.

The declarative settings can be placed in one of two files:

  • hibernate.properties

  • hibernate.cfg.xml

Both formats offer the same features but with a different syntax. Creating either file and placing it within your application's classpath will automatically configure Hibernate with the settings therein. If both files are present on the classpath, both are read in, and the hibernate.cfg.xml settings are used to override the settings in the hibernate.properties file. And, of course, you can also override any of these settings programmatically.

The XML format has become popular among developers for Hibernate settings because of the familiarity and expressiveness of XML as well as the common use of XML configuration formats in a variety of contexts. J2EE deployment descriptors, application server configuration files, and the Hibernate mapping files all use XML file formats, for example. For the sake of simplicity, however, we use the properties file format for our examples here. Translating these to the XML format is fairly straightforward.

At a bare minimum, the configuration should specify the location and type of the database you are using as well as the default access credentials to use to establish a connection (see Example 20-1).

Example 20-1. Sample hibernate.properties file
 hibernate.dialect org.hibernate.dialect.MySQLDialect hibernate.connection.driver_class com.mysql.jdbc.Driver hibernate.connection.url jdbc:mysql://localhost/registration hibernate.connection.username registration hibernate.connection.password registration 

The first line in the preceding configuration is especially importantit tells Hibernate what flavor of database you are using so that the framework can generate acceptable SQL queries, using the correct types. See the Hibernate documentation for a full list of available dialects; the framework supports all major database vendors.

After the basics, Hibernate provides a wide array of further configuration choices that you can add to hibernate.properties or hibernate.cfg.xml. Following are several options, broken down by category. The list is not exhaustive, but we find these settings to be particularly helpful. Again, we present these for the properties file format onlyequivalent options are available in the XML format as well.

20.3.1.1. Debugging

hibernate.show_sql

Set this to true to have Hibernate dump each SQL statement it generates to stdout. This is invaluable if you are trying to find problems in the data being transferred or performance bottlenecks, but be aware that turning it on causes a performance degradation.


hibernate.generate_statistics (new in 3.0)

Set this to TRue to have Hibernate create and track a series of metrics about your application. You can access these metrics individually using the getStatistics( ) method of SessionFactory, but the easiest way to use this data is to call SessionFactory.getStatistics( ).logSummary( ), which outputs a summary of statistics for the application that looks like Example 20-2.

Example 20-2. Hibernate statistics output
 2005-03-16 17:07:33,950 INFO [org.hibernate.stat.StatisticsImpl] - Logging statistics.... 2005-03-16 17:07:33,953 INFO [org.hibernate.stat.StatisticsImpl] - start time: i 1111010849088 2005-03-16 17:07:33,958 INFO [org.hibernate.stat.StatisticsImpl] - sessions opened: 1 2005-03-16 17:07:33,961 INFO [org.hibernate.stat.StatisticsImpl] - sessions closed: 1 2005-03-16 17:07:33,964 INFO [org.hibernate.stat.StatisticsImpl] - flushes: 0 2005-03-16 17:07:33,967 INFO [org.hibernate.stat.StatisticsImpl]  - connections obtained: 1 2005-03-16 17:07:33,980 INFO [org.hibernate.stat.StatisticsImpl] - second level cache  puts: 0 2005-03-16 17:07:34,052 INFO [org.hibernate.stat.StatisticsImpl] -  second level cache hits: 0 2005-03-16 17:07:34,069 INFO [org.hibernate.stat.StatisticsImpl] -  second level cache misses: 0 2005-03-16 17:07:34,073 INFO [org.hibernate.stat.StatisticsImpl] - entities loaded: 2 2005-03-16 17:07:34,077 INFO [org.hibernate.stat.StatisticsImpl] - entities updated: 0 2005-03-16 17:07:34,083 INFO [org.hibernate.stat.StatisticsImpl] - entities inserted: 0 2005-03-16 17:07:34,086 INFO [org.hibernate.stat.StatisticsImpl] - entities deleted: 0 2005-03-16 17:07:34,089 INFO [org.hibernate.stat.StatisticsImpl] - entities fetched  (minimize this): 0 2005-03-16 17:07:34,092 INFO [org.hibernate.stat.StatisticsImpl] - collections loaded: 0 2005-03-16 17:07:34,099 INFO [org.hibernate.stat.StatisticsImpl] - collections updated: 0 2005-03-16 17:07:34,137 INFO [org.hibernate.stat.StatisticsImpl] - collections removed: 0 2005-03-16 17:07:34,141 INFO [org.hibernate.stat.StatisticsImpl] - collections  recreated: 0 2005-03-16 17:07:34,145 INFO [org.hibernate.stat.StatisticsImpl] - collections fetched  (minimize this): 0 2005-03-16 17:07:34,149 INFO [org.hibernate.stat.StatisticsImpl] - queries executed to  database: 1 2005-03-16 17:07:34,152 INFO [org.hibernate.stat.StatisticsImpl] - query cache puts: 0 2005-03-16 17:07:34,156 INFO [org.hibernate.stat.StatisticsImpl] - query cache hits: 0 2005-03-16 17:07:34,171 INFO [org.hibernate.stat.StatisticsImpl] - query cache misses: 0 2005-03-16 17:07:34,175 INFO [org.hibernate.stat.StatisticsImpl] - max query time:  635ms 

You can use the statistics to track down bottlenecks in your code and understand the effects of different configuration and API usage strategies on your system's performance.

20.3.1.2. Other JDBC properties

hibernate.connection.pool_size

Set the size of the connection pool, if using a poolable provider.


hibernate.connection.isolation

The default isolation level for transactions. The allowed values are those specified in the java.sql.Connection interface. See Chapter 16 for more details on transaction isolation levels.

20.3.1.3. Transaction management

hibernate.transaction.factory_class jta.UserTransaction, hibernate.transaction.manager_lookup_class

Set these to replace Hibernate's provided local transaction manager with the JTA implementation from your container.

20.3.1.4. Optimization

 hibernate.jdbc.fetch_size hibernate.jdbc.use_scrollable_resultset

Optimize Hibernate's use of JDBC and its connection-level properties.


hibernate.cglib.use_reflection_optimizer

Replaces as much use of reflection with bytecode injection as possible.

20.3.1.5. JNDI

 hibernate.connection.datasource hibernate.jndi.url hibernate.jndi.class

Configure Hibernate to use JNDI to find a container-managed datasource.

20.3.1.6. SQL

hibernate.query.substitutions

A collection of name/value pairs specifying token replacement options when creating Hibernate Query Language (HQL) queries. You can use this to replace SQL tokens with more convenient replacements (e.g., true=1, false=0).

20.3.2. The Hibernate Type System

When you begin writing mapping files, you can use either Java native types or Hibernate types to match your Java properties to their database field counterparts. It is usually preferable to use the Hibernate types since they are there to provide the bridge between the Java type system and your database vendor's type system. The Hibernate types provide mappings for every major Java native type, such as int, double, string, date, time, boolean, and so on.

If the types provided are not sufficient to suit your needs, you can extend (or even replace) the existing definitions by writing your own conversions. To add your own conversion type, you have to write a class that implements org.hibernate.UserType (if it is a single Java value mapping to a single database column) or org.hibernate.CompositeUserType (if it is a combination of one or more properties and/or one or more columns). You can then add this new conversion class to the type mappings available and use it for your domain objects. If you need even more specialized mappings, Hibernate provides a bevy of alternate implementations for you, like UserCollectionType and ParameterizedType.

20.3.3. Class Mappings

Once you configure Hibernate to locate your database, next you have to map your domain model onto the database schema. Bear in mind that Hibernate is Java-centric, not data-centric, meaning that you provide mappings for your classes, not your tables. Each persistable class in your model has to have a mapping file (or portion of a collected mapping file). Each class must map to one root table. If your class is represented in the data store by multiple tables, you will have to either decompose the class into multiple, smaller classes or create a database view that spans the physical tables. Hibernate supports database views transparently; some databases, however, do not provide this feature.

Class mappings are stored in files that fit the pattern <classname>.hbm.xml. Each Hibernate mapping file should specify the appropriate Hibernate DTD version that it is based on. The mapping must then specify the class name being mapped, including the full package name. You can use a convenience attribute on the <hibernate-mapping> element called package to set the default package name for the mapping so that you don't have to keep typing it everywhere, as shown in Example 20-3.

Example 20-3. Sample class mapping (Professor.hbm.xml)
 <hibernate-mapping package="com.oreilly.jent">    <class name="Professor" table="professors">    <!-- ... -->    </class> </hibernate-mapping> 

Mapping files are often placed in the same directory as the classes they map. However, you can place them anywhere on the classpath. If you prefer a separate mapping folder, by all means configure your application that way. Just make sure that Hibernate has access to the folder where the mapping files reside.

20.3.3.1. Unique IDs

Every persistable class must have one property or a combination of properties that can be used to uniquely identify the row in the database where the instance lives. The mapping file uses the <id> element to specify this property or combination and tell Hibernate how to manage its value. The ID value can either be controlled by your application or you can let the database handle it.

Example 20-4 shows a simple class from our application, Professor.

Example 20-4. Professor.java
 public class Professor {     private Long id;     private String firstName;     private String lastName;     // getters/setters elided for brevity... } 

Example 20-5 is the mapping file for it, showing the application-assigned ID strategy.

Example 20-5. Class mapping for Professor (Professor.hbm.xml)
 <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">   <hibernate-mapping package="com.oreilly.jent.hibernate">     <class name="Professor" table="professors">     <id name="id" column="professor_id"         type="long">         <generator />     </id> </class> 

When using application-assigned IDs, you are in charge of the logic that generates those IDs and manages their uniqueness. Attempting to persist an object whose ID is either unpopulated or a duplicate of an existing record causes Hibernate to throw exceptions.

If you would rather allow the database to manage IDs, you can choose from a variety of methods, depending on what your database supports. You can choose sequence tables (for example, if you are using Oracle), HiLo algorithms, guids and uuids, or plain old autoincrementing fields. You can specify the particular strategy you want, or you can let Hibernate choose for you by specifying the "native" generator class. Our demo app uses autoincrementing fields in a MySQL database, so our ID mapping in Professor.hbm.xml actually looks like this:

 ... <id name="id" column="professor_id" type="long">     <generator /> </id> ... 

20.3.3.2. Properties

The mapping file then provides metadata for each persistable property of your class. Not every property needs to be persistable; if the property doesn't appear in your mapping file, Hibernate ignores its existence entirely. This can be useful for volatile or calculated properties or properties whose values must be looked up from a nonrelational data store (like an XML file).

When mapping persistent properties, at a bare minimum, you have to specify the name of the property and the name of the column it maps to. Optionally, you can specify the type, but Hibernate can also discern the type via reflection on the class itself, so unless the standard Hibernate mapping is insufficient for your needs, you can just omit the type from your mapping. The demo application supplies types everywhere for clarity. Extending our Professor mapping to add the firstName and lastName properties, the full mapping file now becomes:

 <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC     "-//Hibernate/Hibernate Mapping DTD//EN"     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">   <hibernate-mapping package="com.oreilly.jent.hibernate">   <class name="Professor" table="professors">     <id name="id" column="professor_id"         type="long">         <generator />     </id>     <property name="firstName" column="first_name"            type="string"/>     <property name="lastName" column="last_name"            type="string"/> </class> 

20.3.4. Collection Types

So far, we've been working with what Hibernate calls "first-class" objects. Any instance of Professor is unique, and Hibernate manages the identity constraints of each instance of Professor to ensure that you access only a single instance per record at any given time. Quite often, though, your domain classes will have a collection property of simple value types that needs to be persisted. For example, every Professor might have one or more email addresses, which are just represented as simple Strings. You don't want to create a first-class persistent object from them, but you do need to map them to a table in the database.

The schema is shown in Example 20-6.

Example 20-6. SQL for creating the Professor database
 CREATE TABLE 'professors' (     'professor_id' int(11) NOT NULL auto_increment,     'first_name' varchar(25) NOT NULL,     'last_name' varchar(50) NOT NULL,     PRIMARY KEY ('professor_id') ) CREATE TABLE 'emails' (     'professor_id' int(11) NOT NULL,     'email' varchar(255) NOT NULL ) 

Professors might have 0, 1, or many email addresses in the corresponding table. The domain will represent the collection of email addresses as a java.util.Set:

 public class Professor {     private Long id;     private String firstName;     private String lastName;     private Set emails;     // getters/setters elided for brevity... } 

The last step is the mapping. When creating the mapping, you have to tell Hibernate which property is the collection of values, what table to go to when looking up the values, and what the foreign key relationship is. In our case, the property is called emails, the table is also called emails, and we've mimicked the primary key field from our professors table in the new emails table (professor_id). Here's the relevant excerpt from Professor.hbm.xml:

 ... <set name="emails" table="emails">     <key column="professor_id"/>     <element column="email" type="string"/> </set> ... 

When mapping collections, you can choose Set, Map, List, Bag, or Array as the collection type. Each has its own distinct syntax for the mapping file.

20.3.5. Relationship Mappings

The biggest challenge for an ORM framework is mapping the relationships between persistent classes and defining the rules for loading and saving those relationships. Hibernate provides mapping tools for even the most complex domain models, with relationship mappings for one-to-one , one-to-many, and many-to-many relationships both unidirectionally and bidirectionally. In addition, Hibernate allows for lazy associations between objects, which means Hibernate postpones loading the related classes until they are needed.

20.3.5.1. One-to-one

In our University application, each UniversityClass has a single Syllabus, and each Syllabus is unique to that class. Example 20-7 and Example 20-8 show a partial listing of the domain classes.

Example 20-7. UniversityClass.java
 public class UniversityClass {     private Long id;     private String name;     private Syllabus syllabus;     //getters/setters elided for clarity... } 

Example 20-8. Syllabus.java
 public class Syllabus {     private Long id;     private String abstract;     private String fulltext;     private UniversityClass uclass;     //getters/setters elided for clarity... } 

In our example, UniversityClass is the primary object in the relationship, and we'll consider it the parent. Syllabus is the dependent, or child, object. The relational schema we'll generate will look like Example 20-9.

Example 20-9. SQL for creating the University database
 CREATE TABLE 'university_classes' (     'university_class_id' int(11) NOT NULL auto-increment,     'name' varchar(255) NOT NULL,     'syllabus_id' int(11),     PRIMARY KEY ('university_class_id') ) CREATE TABLE 'syllabus' (     'syllabus_id' int(11) NOT NULL auto-increment,     'abstract' varchar(255) NOT NULL,     'fulltext' text,     PRIMARY KEY ('syllabus_id') ) 

Notice the foreign key relationship: each record in university_classes points to a record in syllabus. According to our domain classes shown earlier, the relationship is bidirectional (each endpoint has a public property exposing the other endpoint). Therefore, the mapping has to exist in both class's mapping files. The mapping of the class whose primary key is the foreign key to the other table (in this case, Syllabus) is fairly straightforward. We'd simply add this entry to the Syllabus.hbm.xml mapping file:

 ... <one-to-one name="uClass" property-ref="syllabus"/> ... 

This entry tells Hibernate that the Syllabus class has a property called uClass that is at one end of a one-to-one relationship and that the other end can be found in a property called syllabus on whatever type the uClass property is. The mapping for UniversityClass is slightly more complex, as shown in this excerpt from the UniversityClass.hbm.xml file:

 ... <many-to-one name="syllabus" column="syllabus_id"       unique="true"/> ... 

Right away, you'll notice the many-to-one designation, even though we are mapping a one-to-one relationship. That's because the physical representation of the one-to-one looks just like the physical relationship of a many-to-one (a foreign key lookup in the database), but in this case we know we should find only a single match, hence the unique=true attribute. Hibernate will now manage this relationship, throwing exceptions if you ever try to create two records in one table that map to a single record in the other. The column attribute tells Hibernate which field in the record maps to the primary key column of the associated table. We've used the same name (syllabus_id) on both sides, but that is not a requirement.

20.3.5.2. One-to-many and many-to-one

One-to-one relationships are interesting, but the most common form of relationship mapping in most object domains is the bidirectional one-to-many . In this relationship type, one object is the parent, and it has multiple children. The children are bound to their parent and only their parent; if they are moved to another parent, the association with the original parent is severed. In our demo application, there is a one-to-many relationship between several classes. Let's look at Professor and UniversityClass.

Each Professor teaches one or more classes, but each class is taught by a single Professor. If you switch Professors for a class, the original Professor no longer has to show up on Tuesdays at 9:30, so the original relationship must be severed. Here's the updated SQL for the relevant tables:

 CREATE TABLE 'professors' (     'professor_id' int(11) NOT NULL auto_increment,     'first_name' varchar(25) NOT NULL,     'last_name' varchar(50) NOT NULL,     PRIMARY KEY ('professor_id') ) CREATE TABLE 'university_classes' (     'university_class_id' int(11) NOT NULL auto-increment,     'name' varchar(255) NOT NULL,     'syllabus_id' int(11),     'professor_id' int(11) NOT NULL,     PRIMARY KEY ('university_class_id') ) 

The professors table is the same as in the earlier example, but the university_classes table now has a column to hold the ID of the professor teaching it.

The changes to the domain classes, UniversityClass and Professor, are simple. Here's UniversityClass:

 public class UniversityClass {     private Long id;     private String name;     private Syllabus syllabus;     private Professor professor; } 

And here is Professor:

 public class Professor {     private Long id;     private String firstName;     private String lastName;     private Set emails;     private Set classes; } 

Once again, you have to provide mapping support in the files for both domain classes. The Professor mapping has to specify the collection type while the UniversityClass has to specify merely the foreign key mapping. Here's the change to the mapping in Professor.hbm.xml:

 ... <set name="classes">     <key column="professor_id"/>     <one-to-many /> </set> ... 

And here's the change to UniversityClass.hbm.xml:

 ... <many-to-one name="professor" column="professor_id" not-null="true"/> ... 

Whenever a Professor instance is loaded, Hibernate will now know how to find and instantiate all the UniversityClasses that the Professor teaches. Likewise, loading any UniversityClass prompts Hibernate to find the associated Professor.

20.3.5.3. Many-to-many

Many-to-many relationships are more difficult to represent in a database. These types of relationships require a join table, an intermediate entity that has a foreign key relationship to each of the two primary tables. You could map such a relationship by creating a first-class object that maps to the join table and then creating one-to-many relationships from each primary class to the join class, or you just perform the many-to-many mapping directly without adding the third domain model. Though the Hibernate team recommends the former approach, experience shows that many people choose the latter.

In our application, the relationship between Students and UniversityClasses is a classic many-to-many relationship. Students are supposed to enroll in (and attend) multiple classes per semester, and classes are supposed to have multiple students attending. The SQL to create the relevant tables is listed next. The university_classes table is the same as in our earlier example, but the new students and students_classes tables hold basic student information and links each student to one or more classes:

 CREATE TABLE 'university_classes' (     'university_class_id' int(11) NOT NULL auto-increment,     'name' varchar(255) NOT NULL,     'syllabus_id' int(11),     'professor_id' int(11) NOT NULL,     PRIMARY KEY ('university_class_id') ) CREATE TABLE 'students' (     'student_id' int(11) NOT NULL auto-increment,     'firstName' varchar(25) NOT NULL,     'lastName' varchar(50) NOT NULL,     PRIMARY KEY ('student_id') ) CREATE TABLE 'students_classes' (     'student_id' int(11) NOT NULL,     'university_class_id' int(11) NOT NULL ) 

Each end of the domain relationship now has to have a property that is a collection of the other end of the relationship. Here's the updated UniversityClass class:

 public class UniversityClass {     private Long id;     private String name;     private Syllabus syllabus;     private Professor professor;     private Set students; } 

Here is the new Student class to represent student data:

 public class Student {     private Long id;     private String firstName;     private String lastName;     private Set classes; } 

Finally, each class's mapping has to specify the collection properties and join table specifics to create the full, bidirectional relationship. We'd have the following mapping in the Student.hbm.xml file:

 ... <set name="classes" table="students_classes">     <key column="student_id"/>     <many-to-many column="university_class_id" /> </set> ... 

The following mapping entry would go in the UniversityClass.hbm.xml file:

 ... <set name="students" table="students_classes" inverse="true">     <key column="university_class_id"/>     <many-to-many column="student_id" /> </set> ... 

The relationship in UniversityClass is marked inverse=true. This makes the Student the "parent" in the relationship, and Hibernate will not try to insert or modify the properties of the relationship if the changes are made only from the UniversityClass side.

20.3.5.4. Proxies

All of the relationship mapping that we have described up until now is very powerful but has one large downside. Without due care and consideration, a single load of any single record in the database could cause a cascading load effect that eventually loads the entire database. This is an unacceptable scenario in any but the tiniest applications; not only would the load time be staggering, but the resource consumption would be unmanageable.

Instead, using lazy loading strategies, you should carefully manage what gets loaded and when. There are two different kinds of lazy initialization: proxies, for one-to-one mappings, and lazy collections, for one-to-many and many-to-many.

Declaring that a class can be proxied means that whenever that class appears on the "to-one" end of a relationship, instead of returning the object itself, Hibernate creates and returns an impersonator. This impersonator implements an interface or derives from a concrete class, as specified in the mapping file. However, it does not read any data from the database. Instead, whenever any of its properties are accessed for the first time, it asks Hibernate to load the values from the database at that moment. This delays the resource consumption and network round-trips until absolutely necessary, and, if that proxy is never utilized, the read never happens.

To declare a class to be "proxy-able," you merely append the proxy attribute to the class mapping definition. For example, the Professor class will be proxied by a dynamically derived subclass of Professor. The updated Professor.hbm.xml file would look like the following:

 <class name="Professor" proxy="Professor">     <!-- other mapping information --> </class> 

Later in this chapter, we'll introduce polymorphic mapping strategies. At that time, we'll formally introduce a new member of the domain, the Person interface. If you use such a domain model, you can choose to have the proxy be an implementation of any interface your domain class implements, so the mapping could become:

 <class name="Professor" proxy="Person">     <!-- other mapping information --> </class> 

With this mapping in place, the following scenario could play out:

  1. The application loads an instance of UniversityClass.

  2. Hibernate finds and loads the record for that UniversityClass instance.

  3. Hibernate traverses the many-to-many relationship between UniversityClass and Student, loading each Student and placing her in the students set of UniversityClass.

  4. Hibernate sees the relationship between UniversityClass and Professor, but does not load the associated Professor, instead creating a dynamic instance of a class that implements Person (as seen in the earlier mapping).

  5. Hibernate returns the UniversityClass instance to the application.

  6. The application accesses the students collection of the UniversityClass; no further database access is required since the collection was already loaded.

  7. The application accesses the Professor property of the UniversityClass; Hibernate performs a read on the database and populates all the underlying property data.

You should be aware of several caveats when using proxies. First, if you are using classes that exist in a complex inheritance hierarchy, the proxy you receive from Hibernate cannot be cast to other members of that hierarchy. Second, if a class designated with the proxy attribute appears on the "to-many" side of a relationship, the collection will be populated by actual instances of the persistent class, not instances of the proxy. Proxies are returned only when on the "to-one" side of an association.

20.3.5.5. Lazy loading

When you want to perform lazy loading on the "to-many" side of a relationship, the mapping strategy is very different. The proxy designation is an attribute of the class itself; whenever that class appears as a "to-one" association, the proxy will be used. When declaring lazily loaded collections, the mapping is an attribute of the relationship itself. So, if you want to declare that the students property of UniversityClass be lazily initialized, you would add lazy="true" to the relationship mapping in UniversityClass.hbm.xml:

 ... <set name="students" table="students_classes" lazy="true">     <key column="university_class_id"/>     <many-to-many column="student_id"  inverse="true"/> </set> ... 

Instead of returning proxies of the objects in the collection, Hibernate returns a Hibernate-specific implementation of the collection interface itself. If you are using a Set, Hibernate returns an instance of org.hibernate.collection.PersistentSet. If you are using a Map, you'll get an org.hibernate.collection.PersistentMap and so forth.

If your application never makes use of the collection, the values of the objects in the collection are never loaded from the database. However, any access to the collection causes the entire collection to be loaded (barring advanced optimizations like scrollable result sets). This means any access of any individual element of the collection or any summary information (like the size of the collection). To determine the size of a collection, Hibernate would have to perform the actual select in order to determine the full result set of the join; if Hibernate must execute the full select anyway, it might as well return the results to the collection to prevent executing it again later.

20.3.5.6. Special note about lazy initialization

As we have not, as yet, covered the Hibernate API and usage patterns, we cannot speak adequately about the problem of disconnected objects, lazy loading, and the LazyInitializationException. For now, it is enough to say that anytime Hibernate needs to access the database, there must be an open JDBC connection to the database and the object for whom access is being made must be associated with that connection. Any attempt to access the database to populate lazily loaded associations without an open connection will result in a LazyInitializationException. We'll learn how to avoid this problem in "The Hibernate API" later in this chapter.

20.3.6. Polymorphic Mappings

Hibernate is built to supply relational mappings for Java, an advanced object-oriented programming language. Java provides full object-oriented design capabilities, which includes polymorphic inheritance. Hibernate must therefore support this feature. The lack of high-quality support for polymorphism was a major drawback in the EJB 1.x and 2.x specifications.

When creating an inheritance hierarchy, you can choose between concrete inheritance (base class/derived class) or interface inheritance (interface/implementation). Hibernate supports both methods with the same tools and techniques. In our demo application, we'll be using interface inheritance to model the persons in our university. Both the Professor and Student classes represent people and they share some common properties, namely an ID, first name, and last name, and a Set of UniversityClasses. Our Person domain object is modified to become:

 public interface Person {     Long id;     String firstName;     String lastName; } 

Let's also add a couple of properties to Professor and Student that are specific to each:

 public class Professor implements Person {     private Long id;     private String firstName;     private String lastName;     private Set classes;     private Set emails;     private String degree; } public class Student implements Person {     private Long id;     private String firstName;     private String lastName;     private Set classes;     private String ssn; } 

Hibernate provides three strategies for mapping such an inheritance chain, which are discussed next. The simplest version (table per class hierarchy) is easiest to map and set up in the database, but it is also the least data-normalized schema.

20.3.6.1. One table per class hierarchy

In this strategy, you create a single table that holds all instances of all classes in a single inheritance chain. This necessarily means that you will have some fields on some rows with null values. It also means that the table has to have a special field (called the discriminator field) to determine which class each row is supposed to map to.

Example 20-10 shows the schema for our combined person table.

Example 20-10. SQL for the combined person table
 CREATE TABLE 'person' (     'person_id' int(11) NOT NULL auto-increment,     'person_type' varchar(15) NOT NULL,     'first_name' varchar(25) NOT NULL,     'last_name' varchar(50) NOT NULL,     'ssn' varchar(11) NULL,     'degree' varchar(11) NULL,     PRIMARY KEY ('person_id') ) 

The person_type field is our discriminator value. It is not mapped as a property on any of our domain classes but rather is used as metadata for the mapping itself. The ssn and degree fields are members of our derived types, and since there will be rows in the table that map to the other type (and hence these fields will not have values), the fields themselves must be marked so that they can contain NULL values.

When mapping any of the inheritance hierarchies, we move away from having a single mapping file per class to creating one mapping file for the entire hierarchy. The <class> element maps to the root of the hierarchy and then you use special subclass elements to represent the children. To map a tree that goes deeper than two levels, the subclasses can have their own subclasses. We'll create a mapping file starting with our Person interface, as shown in Example 20-11.

Example 20-11. Mapping for the Person interface
 <hibernate-mapping package="com.oreilly.jent.hibernate"> <class name="Person" table="person" polymorphism="explicit">     <id name="id" column="id" type="long">         <generator />     </id>     <!-- Tells Hibernate which column is used to determine the type of a given     row -->     <discriminator column="person_type" type="string"/>     <property name="firstName" column="first_name" type="string"/>     <property name="lastName" column="last_name" type="string"/>     <!-- Defines a specific implementation of the interface -->     <subclass name="Professor"         discriminator-value="professor">         <property name="degree" column="degree"                type="string"/>         <set name="classes" lazy="true">             <key column="person_id"/>             <one-to-many />         </set>     </subclass>     <!-- Defines a second implementation of the interface -->     <subclass name="Student"         discriminator-value="student">         <property name="ssn" column="identifier"                type="string"/>         <set name="classes" table="students_classes" lazy="true">             <key column="person_id"/>             <many-to-many                      column="university_class_id"/>         </set>      </subclass> </class> </hibernate-mapping> 

With this mapping, Hibernate can populate all properties of any instance loaded from the Person table. Hibernate also provides full type fidelity for any loaded instance; when you load an instance of a Student, even if you ask for it as a Person, you can cast the result down to a Student.

20.3.6.2. One table per class

With this strategy, you create a separate table for every class in the hierarchy (whether or not it is a concrete class). This provides the highest degree of data normalization but makes the actual mapping somewhat more complex. Our schema would become:

 CREATE TABLE 'person' (     'person_id' int(11) NOT NULL auto-increment,     'first_name' varchar(25) NOT NULL,     'last_name' varchar(50) NOT NULL,     PRIMARY KEY ('person_id') ) CREATE TABLE 'students' (     'person_id' int(11) NOT NULL,     'ssn' varchar(11) NOT NULL,     PRIMARY KEY ('person_id') ) CREATE TABLE 'professors' (     'person_id' int(11) NOT NULL,     'degree' varchar(25) NOT NULL,     PRIMARY KEY ('person_id') ) 

The person table is now the primary table where the autoincrementing ID is created. The dependent tables, students and professors, utilize the person_id as the primary key but do not mark it as autoincrementing since the value will always be supplied by the root table. Notice also that the ssn and degree fields no longer have to accept NULL values since rows will be added to the dependent tables only for actual instances of that class.

As you'll see in Example 20-12, there are not many changes to the mapping except that you no longer need the discriminator column, the subclasses become joined subclasses, and you have to specify the table they live in.

Example 20-12. Updated Person mapping file using a table per class
 <hibernate-mapping package="com.oreilly.jent.hibernate"> <class name="Person" table="person" polymorphism="explicit">     <id name="id" column="id" type="long">         <generator />     </id>     <property name="firstName" column="first_name" type="string"/>     <property name="lastName" column="last_name" type="string"/>     <!-- Defines a specific implementation of the interface -->     <joined-subclass name="Professor" table="professors">         <key column="person_id"/>         <property name="degree" column="degree"                type="string"/>         <set name="classes" lazy="true">             <key column="person_id"/>             <one-to-many />         </set>     </joined-subclass>     <!-- Defines a second implementation of the interface -->     <joined-subclass name="Student" table="students">         <key column="person_id"/>         <property name="ssn" column="identifier"                type="string"/>         <set name="classes" table="students_classes" lazy="true">             <key column="person_id"/>             <many-to-many                      column="university_class_id"/>         </set>     </joined-subclass> </class> </hibernate-mapping> 

Hibernate can now determine the actual type of an instance by whether or not it matches a row in the specific subclass table.

20.3.6.3. One table per concrete class

This mapping would require only two tables in our application, one each for Professor and Student. This method is also not optimally normalized since you have repeated columns defined in the two tables (each has first_name and last_name, for example). Furthermore, those columns have to have identical names so that Hibernate can map them correctly. Otherwise, this strategy looks very similar to the one-table-per-class strategy.

20.3.7. Mapping Tools

Creating appropriate mappings is the heart and soul of a good Hibernate application. Making sure that your relationships are efficiently modeled, that lazy initialization is established when necessary, and that the right type mappings are used is a time-consuming process. Beyond that, you then have to actually create the domain classes and the data schema.

The Hibernate team ships a variety of tools that you can use to ease the burden on your development team. There are command-line tools that can generate one or more of the artifacts (Java code, mapping files, or database schema) from the others. You can use existing tools like XDoclet or Middlegen to turn code decoration into mapping files (see Chapter 21 for general details on XDoclet). There's a set of plug-ins available for Eclipse, IntelliJ Idea, and most other IDEs. Finally, there's a standalone product available from the Hibernate team called Hibernate Console (formerly Hibern8IDE) that allows you to put your mappings to the test.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

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