What Is Hibernate?

There are two basic kinds of ORM frameworks. The first allows you to fully control the SQL statements, and you have to make sure that you are selecting the correct columns. Hibernate represents the second kind of ORM tools, the kind where the framework generates the SQL statements for you. This speeds up development incredibly because you only have to write the mapping files and the framework takes care of the database operations and correctly processes the results.

Hibernate also allows you to specify the SQL dialect it should use for the queries. As a result, Hibernate generates almost ideal queries that are tailored to the specific database you are using. The dialect controls the syntax for joins, limit/top clauses, and many other database- specific SQL features.

One of the most useful aspects of Hibernate is that it allows you to define your own persisted enum types—regular Java classes that implement a specific interface. This helps you implement object canonicalization, which may give your application quite a performance boost.

Hibernate Versions

The current production version of Hibernate is 2.1.6, and therefore we focus on the version 2 line in this chapter. However, by the time this book is published, version 3.0 may have reached the production stage. In fact, the Spring community is already making all the necessary preparations for adding support to Hibernate 3 when it is finally released.

Configuring Hibernate in a Spring Application

If you want to use Hibernate in your application, you must start by configuring a dataSource (as shown in Listing 9-1), which defines the connection to a database.

Listing 9-1: dataSource Bean Definition

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>          <!-- Data source bean -->     <bean            destroy-method="close">         <property name="driverClassName">             <value>org.postgresql.Driver</value></property>         <property name="url">              <value>jdbc:postgresql://localhost/prospring</value></property>         <property name="username"><value>janm</value></property>         <property name="password"><value>Gen0me64</value></property>     </bean> </beans>
image from book

The dataSource bean defines a connection to the prospering PostgreSQL database that is running on the local machine using the specified user name and password. You also need to create a sessionFactory bean (see Listing 9-2) that all the Hibernate DAO implementations are going to use.

Listing 9-2: sessionFactory Bean Definition

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean  …/>          <bean           bold">org.springframework.orm.hibernate.LocalSessionFactoryBean">         <property name="dataSource"><ref local="dataSource"/></property>         <property name="mappingResources">             <value>sample.hbm.xml</value></property>         <property name="hibernateProperties">             <props>                 <prop key="hibernate.dialect">                     net.sf.hibernate.dialect.PostgresSQLDialect</prop>             </props>         </property>     </bean> </beans>
image from book

As you can see in Listing 9-2, there are several notable settings in the sessionFactory bean. First of all, you must set a reference to the dataSource bean. You then need to set the mappingResources, which is a Spring Resource reference, to at least one Hibernate mapping file. The location of these files is specific to the application type you are developing. It is important to realize that the configuration files are resources, and as such, they are handled by ResourceLoaders, usually the default ResourceLoader. (More information on resources is covered in Chapter 4.)

Finally, you need to set the hibernate.dialect property on the session factory, which allows Hibernate to generate the SQL queries effectively. Each dialect also specifies additional features the database supports. For example, MySQLDialect supports identity key generation, whereas PostgreSQLDialect supports sequence key generation, not identity. You have no way of finding out what the next primary key value is going to be in MySQL, because MySQL only generates the primary key value on insert. For this reason as well as others, it is vital that you set this property according to the database you are using.

In a larger-scale application, you do not usually use a single Hibernate mapping file. Instead, you usually split the mapping files for each class. If this is the case, you have to modify the mappingResources property of the sessionFactory bean as we have done in Listing 9-3.

Listing 9-3: sessionFactory Bean Definition with mappingResources Specified

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean  …/>          <bean           bold">org.springframework.orm.hibernate.LocalSessionFactoryBean">         <property name="dataSource"><ref local="dataSource"/></property>         <property name="mappingResources">            <list>                 <value>sample.hbm.xml</value>            </list>         </property>         <property name="hibernateProperties">             <props>                 <prop key="hibernate.dialect">                     net.sf.hibernate.dialect.PostgresSQLDialect</prop>             </props>         </property>     </bean> </beans>
image from book

Because Hibernate needs a lot of configuration files to function properly, it is important to place the mapping files in appropriate source folders and then use the build script to place them correctly in the distribution archive. The location of these files is shown in Table 9-1.

Table 9-1: Locations of the Configuration Files

File

Source Location

Build/Run Location

sample.hbm.xml

src/hibernate

build

applicationContex

src/conf

build

The build script, build.xml, takes care of building the application and makes sure that the configuration files are copied from their source location to the appropriate destination to run the application. But before you can move ahead to complete the configuration files and make the application work, we need to take a closer look at the Hibernate mapping files.

Mapping Files

Put simply, the mapping files define classes, properties of these classes, and how these classes and their properties map to the database tables and columns so that Hibernate can persist the classes to the database. These mapping files can get quite complex (especially when you are using many-to-many inverse mappings), but in most cases, they are very straightforward.

Simple Mappings

Let us begin with the script for a simple table that has just three columns and a matching domain object (see Listing 9-4).

Listing 9-4: SQL Script for the Test Table

image from book
create table Test (     TestId serial not null,     Name varchar(50) not null,      RunDate timestamp not null,          constraint PK_TestId primary key (TestId) );      insert into Test (Name, RunDate) values ('foobar', '2004-01-01');
image from book

The domain object (shown in Listing 9-5) is going to have properties for all three columns and is going to override the toString() method to return a human-readable representation of the object's data.

Listing 9-5: Test Domain Object

image from book
package com.apress.prospring.ch9.domain;      import java.util.Date;      public class Test {          private int testId;     private String name;     private Date runDate;          public String toString() {         // implementation     }     // Getters and Setters } 
image from book

Now let's take a look at our sample mapping file (see Listing 9-6). It maps the Test domain object's properties to columns in the Test table.

Listing 9-6: Hibernate Mapping File for the Test Object and Table

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC         "-//Hibernate/Hibernate Mapping DTD//EN"         "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">      <hibernate-mapping auto-import="true">          <class name="com.apress.prospring.ch9.domain.Test" table="Test">         <id name="testId" column="TestId" type="int" unsaved-value="0">             <generator bold">sequence">                 <param name=" sequence"> Test_TestId_Seq</param>             </generator>         </id>         <property name="name" column="Name"/>         <property name="runDate" column="RunDate"/>     </class>      </hibernate-mapping>
image from book

The code in bold instructs Hibernate to create an instance of the Test domain object for the rows selected from the Test table. It also sets the value of property testId to the value in column TestId, name to the value of the Name column, and runDate to the value of the RunDate column.

This code also specifies that on insert, Hibernate uses a sequence to set the primary key value. This is the only database-specific setting because not all databases support sequences or identities. For instance, if you want to design the application to support different databases, you have to create separate mapping files for each database that you want to use. Alternatively, you can use a database-independent generator for the primary key value, such as hilo. This approach works well if the primary key is a numeric type; however, if the primary key is GUID, the hilo generator is not going to work and you have to rely on the database to provide the correct primary key value.

One-to-Many Mappings

Hibernate allows you to map a one-to-many relationship very easily. To demonstrate this, we create a fairly typical Customer to Customer Address relationship. The SQL demonstrates this 1:N relationship in an obvious way, as shown in Listing 9-7.

Listing 9-7: SQL Script for 1:N Example

image from book
create table Customers (     CustomerId serial not null,     FirstName varchar(50) not null,     LastName varchar(50) not null,          constraint PK_CustomerId primary key (CustomerId) );      create table CustomerAddresses (     CustomerAddressId serial not null,     Customer int not null,     Line1 varchar(50) not null,     Line2 varchar(50) not null,     City varchar(50) not null,     PostCode varchar(50) not null,          constraint PK_CustomerAddressId primary key (CustomerAddressId),     constraint FK_Customer foreign key (Customer)          references Customers (CustomerId)          on delete cascade );
image from book

The domain objects (shown in Listing 9-8) are going to reflect the structure shown in Listing 9-7. However, the Customer object is going to have a Set of CustomerAddresses.

Listing 9-8: Customer and CustomerAddress Domain Objects

image from book
package com.apress.prospring.ch9.domain;      // in Customer.java import java.util.Set;      public class Customer {          private int customerId;     private String firstName;     private String lastName;     private Set addresses;     public String toString() {         // implementation     }     // Getters and setters }      // in CustomerAddress.java public class CustomerAddress {          private int customerAddressId;     private int customer;     private String line1;     private String line2;     private String city;     private String postCode;          public String toString() {         // implementation     }     // Getters and setters }
image from book

These domain classes are a box-standard representation of the data, except that the Customer object has a Set of addresses. Traditionally, we would not include the addresses property in the Customer domain object. In Listing 9-9, we tell Hibernate that the addresses property should be populated by data selected from the CustomerAddresses table where Customer = CustomerId.

Listing 9-9: The customer.hbm.xml Mapping File

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC         "-//Hibernate/Hibernate Mapping DTD//EN"         "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">      <hibernate-mapping auto-import="true">          <class name="com.apress.prospring.ch9.domain.Customer" table="Customers">         <id name="customerId" column="CustomerId" type="int" unsaved-value="0">             <generator >                 <param name="sequence">Customers_CustomerId_Seq</param>             </generator>         </id>         <property name="firstName" column="FirstName"/>         <property name="lastName" column="LastName"/>         <set name="addresses">             <key column="Customer"/>             <one-to-many                  bold">com.apress.prospring.ch9.domain.CustomerAddress"/>         </set>     </class>          <class name="com.apress.prospring.ch9.domain.CustomerAddress"          table="CustomerAddresses">         <id name="customerAddressId" column="CustomerAddressId"              type="int" unsaved-value="0">             <generator >                 <param name="sequence">CustomerAddresses_CustomerId_Seq</param>             </generator>         </id>              <property name="customer" column="Customer"/>         <property name="line1" column="Line1"/>         <property name="line2" column="Line2"/>         <property name="city" column="City"/>         <property name="postCode" column="PostCode"/>     </class>      </hibernate-mapping>
image from book

The mapping states that the Customer object has a Set property named addresses. The elements in this set should be CustomerAddress objects selected from the CustomerAddresses table where the ID of the Customer object is equal to the value in the Customer column.

Hibernate suffers from a 1+N complexity. For example, assume that we have two rows in the Customers table; if this is the case, Hibernate performs two queries to the CustomerAddresses table to get the addresses for each Customer object returned from the first query. Hence the number of queries is 1 (to get the Customer objects) + N (to select N CustomerAddresses objects for the N Customers selected in the first query).

The log from the sample application shown in Listing 9-10, which we are going to finish later in this chapter, demonstrates this.

Listing 9-10: Log Messages Showing 1 + N Complexity in Select

image from book
(1) select customer0_.CustomerId as CustomerId, customer0_.FirstName as      FirstName, customer0_.LastName as LastName from Customers customer0_ (2) select addresses0_.Customer as Customer__,      addresses0_.CustomerAddressId as Customer1___,      addresses0_.CustomerAddressId as Customer1_0_,      addresses0_.Customer as Customer0_,      addresses0_.Line1 as Line10_,      addresses0_.Line2 as Line20_, addresses0_.City as City0_,      addresses0_.PostCode as PostCode0_ from      CustomerAddresses addresses0_ where addresses0_.Customer=? (3) select addresses0_.Customer as Customer__,      addresses0_.CustomerAddressId as Customer1___,      addresses0_.CustomerAddressId as Customer1_0_,      addresses0_.Customer as Customer0_,      addresses0_.Line1 as Line10_,      addresses0_.Line2 as Line20_, addresses0_.City as City0_,      addresses0_.PostCode as PostCode0_ from      CustomerAddresses addresses0_ where addresses0_.Customer=?
image from book

Many-to-Many Mappings

The most complex mapping type (and also the type that requires the most work in updates and inserts) is M:N. This relationship is modeled by a join table, which creates two 1:N relationships.

In Listing 9-11, we add two more tables to the schema from Listing 9-7.

Listing 9-11: Many-to-Many SQL Schema

image from book
create table Permissions (     PermissionId int not null,     Name varchar(50) not null,          constraint PK_PermissionId primary key (PermissionId) );      create table CustomerPermissions (     CustomerPermissionId serial not null,     Permission int not null,     Customer int not null,          constraint PK_CustomerPermissionId primary key (CustomerPermissionId),     constraint PK_Permission foreign key (Permission)          references Permissions(PermissionId) on delete cascade,     constraint PK_Customer foreign key (Customer) references Customers(CustomerId)          on delete cascade );
image from book

The domain object for Permission is simply going to map the columns from the table to its properties. As well as the mapping for the Permission domain object, Listing 9-12 shows mapping for the roles property to the Customer domain object. Most importantly, the listing also shows the many-to-many relationship between the Customer and Permission domain objects.

Listing 9-12: The customer.hbm.xml Mapping File

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC         "-//Hibernate/Hibernate Mapping DTD//EN"         "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">      <hibernate-mapping auto-import="true">          <class name="com.apress.prospring.ch9.domain.Customer" table="Customers">         <id name="customerId" column="CustomerId" type="int" unsaved-value="0">             <generator >                 <param name="sequence">Customers_CustomerId_Seq</param>             </generator>         </id>         <property name="firstName" column="FirstName"/>         <property name="lastName" column="LastName"/>         <set name="addresses" outer-join="true">             <key column="Customer"/>             <one-to-many />         </set>              <set name="permissions" outer-join="true" inverse="true"              table="CustomerPermissions">             <key column="Customer"/>             <many-to-many bold">com.apress.prospring.ch9.domain.Permission"                    column="Permission"/>         </set>     </class>          <class name="com.apress.prospring.ch9.domain.CustomerAddress"          table="CustomerAddresses">         <id name="customerAddressId" column="CustomerAddressId"              type="int" unsaved-value="0">             <generator >                 <param name="sequence">CustomerAddresses_CustomerAddressId_Seq                 </param>             </generator>         </id>         <property name="customer" column="Customer"/>         <property name="line1" column="Line1"/>         <property name="line2" column="Line2"/>         <property name="city" column="City"/>         <property name="postCode" column="PostCode"/>     </class>          <class name="com.apress.prospring.ch9.domain.Permission" table="Permissions">         <id name="permissionId" column="PermissionId" type="int">             <generator />         </id>         <property name="name" column="Name"/>     </class>      </hibernate-mapping>
image from book

The many-to-many mapping is represented here by the Set of Permission objects. In this code, we instruct Hibernate to select all rows from the Permissions that have a record in CustomerPermissions where Customer = CustomerId. In SQL, Hibernate runs the query shown in Listing 9-13.

Listing 9-13: Generated SQL Statement for Many-to-Many Mapping

image from book
select permission0_.Customer as Customer__,      permission0_.Permission as Permission__,      permission1_.PermissionId as Permissi1_0_,      permission1_.Name as Name0_  from CustomerPermissions permission0_ inner join Permissions permission1_ on      permission0_.Permission=permission1_.PermissionId  where permission0_.Customer=?  
image from book

As you can see, M:N selects also suffer from the 1 + N performance problem, which is magnified by the inner join operation.

You can reduce the complexity of the select operation[1] by setting the hibernate.max_fetch_depth property in the hibernateProperties of the sessionFactory (see Listing 9-14). This tells Hibernate to attempt to perform a single outer join select rather than multiple separate select statements.

Listing 9-14: max_fetch_depth Setting

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿  "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>          <!-- Data source bean -->     <bean  …/>               <bean bold">sessionFactory"          >         <!-- etc -->         <property name="hibernateProperties">             <props>                 <prop                      key="hibernate.dialect">                         net.sf.hibernate.dialect.PostgreSQLDialect                 </prop>                 <prop key="hibernate.max_fetch_depth">3</prop>             </props>         </property>     </bean>      </beans>
image from book

The hibernate.max_fetch_depth property sets the maximum number of tables to be joined for single-ended associations (one-to-one, many-to-one). Generally, you should set this to values between 1 and 3 because 0 disables outer joins completely and outer joining more than three tables may result in SQL statements that are too complex and too much work for the database.

Without setting the hibernate.max_fetch_depth property, if you select all customers from the sample database, you get eight select statements. When we added this property to the sessionFactory, the number of select statements dropped to 5.

[1] You are not actually reducing the complexity of the operation; you are merely delegating some work to the database. After all, databases are designed and optimized particularly for joins.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

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