|
|
|
|
Hibernate is an Open Source Object Relational mapping tool which provides transparent persistence for POJOs. Object-relational mapping is used to map object-oriented programming objects to relational databases managed by Oracle, DB2, Sybase, and other relational database managers (RDBMSs). Hibernate is evolved from the JBoss core persistence framework.
Other popular ORM solutions are iBatis, JDO and TopLink. Advantages of Hibernate:
Object/relational mappings are defined in an XML document. The mapping document is designed to be readable and hand-editable. The mapping language is Java-centric, meaning that mappings are constructed around persistent class declarations, not table declarations. Even though many Hibernate users choose to define XML mappings be hand, a number of tools exist to generate the mapping document, including XDoclet, Middlegen and AndroMDA. Before we move to hibernate mapping here is a basic JDBC connection configured in hibernate. <?xml version="1.0"> To use Hibernate-provided JDBC connections, the configuration file requires the following five properties:
A typical hibernate component mapping looks as this:
The mapping definition starts with the hibernate-mapping element. The package attribute sets the default package for unqualified class names in the mapping. With this attribute set, you need only give the class name for other persistent classes listed in the mapping file, such as the Speaker and Attendee classes. To refer to a persistent class outside the given package, you must provide the fully qualified class name within the mapping document. If Hibernate has trouble locating a class because of a missing package on, for instance, a many-to-one element, Hibernate throws a MappingException. This doesn't mean that Hibernate can't find the actual class file, but that it isn't able to navigate from one mapping definition to another. Immediately after the hibernate-mapping tag, you encounter the class tag. The class tag begins the mapping definition for a specific persistent class. The table attribute names the relational table used to store the state of the object. The class element has a number of attributes available, altering how Hibernate persists instances of the class. The id element describes the primary key for the persistent class as well as how the key value is generated. Each persistent class must have an id element declaring the primary key for the relational table. Let's look at the id element:
The name attribute defines the property in your persistent class that will be used to store the primary key value. The id element implies that the Event class has a property also named id:
If the column for the primary key has a different name than your object
property, the column attribute is used. For our example's purposes, this column
name is uid . The values of the type and unsaved-value attributes depend on the
generator used. The different types of Id generators supported by hibernate are:
The unsaved-value attribute describes the value of the id property for
transient instances of this class. The unsaved-value attribute affects how
objects are stored. We'll discuss the impact of this attribute later in the
article.
Each property element corresponds to a property in the Event object. The name
attribute contains the property name, whereas the type attribute specifies the
property object type. The column used to store the property value defaults to
the property name. The column attribute overrides this default behavior, as
shown in the startDate property. Hibernate supports different types of association and collection mappings. Download the sample source for each of the following mappings here.
Cascades If you've worked with relational databases, you've no doubt encountered cascades. Cascades propagate certain operations on a table (such as a delete) to associated tables. (Remember that tables are associated through the use of foreign keys.) Suppose that when you delete an Event, you also want to delete each of the Speaker instances associated with the Event. Instead of having the application code perform the deletion, Hibernate can manage it for you. Hibernate supports ten different types of cascades that can be applied to many-to-one associations as well as collections. The default cascade is none. Each cascade strategy specifies the operation or operations that should be propagated to child entities. The cascade types that you are most likely to use are the following:
The cascade element is added to the desired many-to-one or collection element. For example, the following configuration instructs Hibernate to delete the child Speaker elements when the parent Event is deleted: <set name="speakers"cascade="delete"> That's all there is to configuring cascades. It's important to note that Hibernate doesn't pass the cascade off to the database. Instead, the Hibernate service manages the cascades internally. This is necessary because Hibernate has to know exactly which objects are saved, updated, and deleted. Fetching associated objects When an object has one or more associated objects, it's important to consider how associated objects will be loaded. Hibernate 3 offers you two options. You can either retrieve associated objects using an outer join or by using a separate SELECT statement. The fetch attribute allows you to specify which method to use: <many-to-one name="location"class="Location"fetch="join"/> When an Event instance is loaded, the associated Location instance will be loaded using an outer join. If you wanted to use a separate select, the many-to-one element would look like this: <many-to-one name="location"class="Location"fetch="select"/> This also applies to child collections, but you can only fetch one collection using a join per persistent object. Additional collections must be fetched using the SELECT strategy. If you're using Hibernate 2, the fetch attribute is not available. Instead, you must use the outer-join attribute for many-to-one associations. (There is no support for retrieving collections using a SELECT in Hibernate 2.) The outer-join attribute takes either a true or false value. Building the SessionFactory Hibernate's SessionFactory interface provides instances of the Session class, which represent connections to the database. Instances of SessionFactory are thread-safe and typically shared throughout an application. Session instances, on the other hand, aren't thread-safe and should only be used for a single transaction or unit of work in an application. Configuring the SessionFactory The Configuration class kicks off the runtime portion of Hibernate. It's used to load the mapping files and create a SessionFactory for those mapping files. Once these two functions are complete, the Configuration class can be discarded. Creating a Configuration and SessionFactory instance is simple, but you have some options. There are three ways to create and initialize a Configuration object. This first snippet loads the properties and mapping files defined in the hibernate.cfg.xml file and creates the SessionFactory: Configuration cfg =new Configuration(); The configure()method tells Hibernate to load the hibernate.cfg.xml file. Without that, only hibernate.properties would be loaded from the classpath. The Configuration class can also load mapping documents programmatically: Configuration cfg =new Configuration(); Another alternative is to have Hibernate load the mapping document based on the persistent class. This has the advantage of eliminating hard-coded filenames in the source code. For instance, the following code causes Hibernate to look for a file named com/manning/hq/ Event.hbm.xml in the classpath and load the associated class: Configuration cfg =new Configuration(); Since applications can have tens or hundreds of mapping definitions, listing each definition can quickly become cumbersome. To get around this, the hibernate.cfg.xml file supports adding all mapping files in a JAR file. Suppose your build process creates a JAR file named application.jar, which contains all the classes and mapping definitions required. You then update the hibernate.cfg.xml file: <mapping jar="application.jar"/> Of course, you can also do this programmatically with the Configuration class: Configuration.addJar(new java.io.File("application.jar")); Keep in mind that the JAR file must be in the application classpath. If you're deploying a web application archive (WAR) file, your application JAR file should be in the /WEB-INF/lib directory in the WAR file. The four methods used to specify mapping definitions to the Hibernate runtime can be combined, depending the requirements for your project. However, once you create the SessionFactory from the Configuration instance, any additional mapping files added to the Configuration instance won't be reflected in the SessionFactory . This means you can't add new persistent classes dynamically. You can use the SessionFactory instance to create Session instances: Session session =factory.openSession(); Instances of the Session class represent the primary interface to the Hibernate framework. They let you persist objects, query persistent objects, and make persistent objects transient. Let's look at persisting objects with Hibernate. Persisting objects Persisting a transient object with Hibernate is as simple as saving it with the Session instance: Event event =new Event(); Calling save(...)for the Event instance assigns a generated ID value to the instance and persists the instance. (Keep in mind that Hibernate doesn't set the ID value if the generator type is assigned.) The flush() call forces persistent objects held in memory to be synchronized to the database. Session’s don't immediately write to the database when an object is saved. Instead, the Session queues a number of database writes to maximize performance. If you would like to update an object that is already persistent, the update(...)method is available. Other than the type of SQL operation executed, the difference between save(...)and update(...)is that update(...)doesn't assign an ID value to the object. Because of this minor difference, the Session interface provides the saveOrUpdate(...) methods, which determine the correct operation to execute on the object. How does Hibernate know which method to call on an object? When we described the mapping document, we mentioned the unsaved-value attribute. That attribute comes into play when you use the saveOrUpdate(...)method. Suppose you have a newly created Event instance. The id property is null until it's persisted by Hibernate. If the value is null, Hibernate assumes that the object is transient and assigns a new id value before saving the instance. A non-null id value indicates that the object is already persistent; the object is updated in the database, rather than inserted. You could also use a long primitive to store the primary key value. However, using a primitive type also means that you must update the unsaved-value attribute value to 0, since primitive values can't be null. Here's the necessary code to persist an Event instance: Configuration cfg =new Configuration(); The first two lines create the SessionFactory after loading the configuration file from the classpath. After the Event instance is created and populated, the Session instance, provided by the SessionFactory , persists the Event. The Session is then flushed and closed, which closes the JDBC connection and performs some internal cleanup. That's all there is to persisting objects. Once you've persisted a number of objects, you'll probably want to retrieve them from the database. Retrieving persistent objects is the topic of the next section. Retrieving objects Suppose you want to retrieve an Event instance from the database. If you have the Event ID, you can use a Session to return it: Event event =(Event)session.load(Event.class,eventId); This code tells Hibernate to return the instance of the Event class with an ID equal to eventId. Notice that you're careful to close the Session, returning the database connection to the pool. There is no need to flush the Session, since you're not persisting objects-only retrieving them. What if you don't know the ID of the object you want to retrieve? This is where HQL enters the picture. The Session interface allows you to create Query objects to retrieve persistent objects. (In Hibernate 2, the Session interface supported a number of overloaded find methods. They were deprecated in Hibernate 3.) HQL statements are object-oriented, meaning that you query on object properties instead of database table and column names. Let’s look at some examples using the Query interface. This example returns a collection of all Event instances. Notice that you don't need to provide a select ...clause when returning entire objects: Query query =session.createQuery("from Event"); This query is a little more interesting since we're querying on a property of the Event class: Query query =session.createQuery("from Event where name
="+ We've hardcoded the name value in the query, which isn't optimal. Let's rewrite it: Query query =session.createQuery("from Event where name
=?", The question mark in the query string represents the variable, which is similar to the JDBC PreparedStatement interface. The second method parameter is the value bound to the variable, and the third parameter tells Hibernate the type of the value. (The Hibernate class provides constants for the built-in types, such as STRING , INTEGER , and LONG , so they can be referenced programmatically.) One topic we haven't touched on yet is the cache maintained by the Session. The Session cache tends to cause problems for developers new to Hibernate, so we'll talk about it next. The Session cache One easy way to improve performance within the Hibernate service, as well as your applications, is to cache objects. By caching objects in memory, Hibernate avoids the overhead of retrieving them from the database each time. Other than saving overhead when retrieving objects, the Session cache also impacts saving and updating objects. Let's look at a short code listing: Session session =factory.openSession(); This code first retrieves an Event instance, which the Session caches internally. It then does the following: updates the Event name, saves or updates the Event instance, retrieves the same Event instance (which is stored in the Session cache), updates the duration of the Event,and saves or updates the Event instance. Finally, you flush the Session. All the updates made to the Event instance are combined into a single update when you flush the Session. This is made possible in part by the Session cache. The Session interface supports a simple instance cache for each object that is loaded or saved during the lifetime of a given Session. Each object placed into the cache is keyed on the class type, such as The Session cache com.manning.hq.ch03.Event, and the primary key value. However, this cache presents some interesting problems for unwary developers. A common problem new developers run into is associating two instances of the same object with the same Session instance, resulting in a NonUniqueObjectException. The following code generates this exception: Session session =factory.openSession(); This code opens the Session instance, loads an Event instance with a given ID, creates a second Event instance with the same ID, and then attempts to save the second Event instance, resulting in the Non-UniqueObjectException. Any time an object passes through the Session instance, it's added to the Session’s cache. By passes through, we're referring to saving or retrieving the object to and from the database. To see whether an object is contained in the cache, call the Session.contains()method. Objects can be evicted from the cache by calling the Session.evict() method. Let's revisit the previous code, this time evicting the first Event instance: Session session =factory.openSession(); The code first opens the Session instance and loads an Event instance with a given ID. Next, it determines whether the object is contained in the Session cache and evicts the object if necessary. The code then creates a second Event instance with the same ID and successfully saves the second Event instance. If you simply want to clear all the objects from the Session cache, you can call the aptly named Session.clear()method. Connection pools Connection pools are a common way to improve application performance. Rather than opening a separate connection to the database for each request, the connection pool maintains a collection of open database connections that are reused. Application servers often provide their own connection pools using a JNDI DataSource, which Hibernate can take advantage of when configured to use a DataSource. If you're running a standalone application or your application server doesn't support connection pools, Hibernate supports three connection pooling services: C3P0, Apache's DBCP library, and Proxool. C3P0 is distributed with Hibernate; the other two are available as separate distributions. When you choose a connection pooling service, you must configure it for your environment. Hibernate supports configuring connection pools from the hibernate.cfg.xml file. The connection.provider_class property sets the pooling implementation: <property name="connection.provider_class"> Once the provider class is set, the specific properties for the pooling service can also be configured from the hibernate.cfg.xml file: <property name="c3p0.minPoolSize"> As you can see, the prefix for the C3P0 configuration parameters is c3p0. Similarly, the prefixes for DBCP and Proxool are dbcp and proxool, respectively. Specific configuration parameters for each pooling service are available in the documentation with each service. Table 1 lists information for the supported connection pools. Hibernate ships with a basic connection pool suitable for development and testing purposes. However, it should not be used in production. You should always use one of the available connection pooling services, like C3P0, when deploying your application to production. If your preferred connection pool API isn't currently supported by Hibernate, you can add support for it by implementing the org.hibernate.connection.ConnectionProvider interface. Implementing the interface is straightforward.
There isn't much to using a connection pool, since Hibernate does most of the work behind the scenes. The next configuration topic we’ll look at deals with transaction management with the Hibernate Transaction API. Transactions Transactions group many operations into a single unit of work. If any operation in the batch fails, all of the previous operations are rolled back, and the unit of work stops. Hibernate can run in many different environments supporting various notions of transactions. Standalone applications and some application servers only support simple JDBC transactions, whereas others support the Java Transaction API (JTA). Hibernate needs a way to abstract the various transaction strategies from the environment. Hibernate has its own Transaction class that is accessible from the Session interface, demonstrated here: Session session =factory.openSession(); In this example, factory is an initialized SessionFactory instance. This code creates an instance of the org.hibernate.Transaction class and then commits the Transaction instance. Notice that you don't need to call session.flush(). Committing a transaction automatically flushes the Session object. The Event instance is persisted to the database when the transaction is committed. The transaction strategy you use (JDBC or JTA) doesn't matter to the application code-it's set in the Hibernate configuration file. The transaction.factory_class property defines the transaction strategy that Hibernate uses. The default setting is to use JDBC transactions since they're the most common. To use JTA transactions, you need to set the following properties in hibernate.cfg.xml: <property name="transaction.factory_class"> The transaction.factory_class property tells Hibernate that you'll be using JTA transactions. Currently, the only other option to JTA is JBDC transactions, which is the default. JTA transactions are retrieved from a JNDI URI, which is specified using the jta.User-Transaction property. If you don't know the URI for your specific application server, the default value is java:comp/UserTransaction. There is some confusion about another property related to JTA transactions: transaction.manager_lookup_class. You only need to specify the manager lookup class when you're using a transactional cache. (We discuss caches in the next section–don't worry.) However, if you don't define the jta.UserTransaction property and transaction.manager_lookup_class is defined, the user transaction name in the lookup factory class is used. If neither of the properties are used, Hibernate falls back to java:comp/UserTransaction. What's the benefit of using JTA transactions? JTA transactions are useful if you have multiple transactional resources, such as a database and a message queue. JTA allows you to treat the disparate transactions as a single transaction. Combining multiple transactions also applies within Hibernate. If you attempt to create multiple transactions from the same Session instance, all of the operations are batched into the first transaction. Let's look at an example that includes two transactions: Transaction tx0 =session.beginTransaction(); This example begins by creating a new transaction. The second use of session.beginTransaction()just returns the first transaction instance. session.saveOrUpdate(location)commits the first transaction, and tx0.commit()recommits the first transaction. Although you explicitly create two Transaction objects, only one is used. Of course, this creates a problem. Let's assume you have a Session object being used by two application threads. The first application thread begins the JTA transaction and starts adding objects. Meanwhile, the second thread, using the same transaction, deletes an object and commits the transaction. Where does this leave the first thread? The first thread won't be committed, which is what you'd expect. The problem is that this issue can be hard to debug, bringing up an important point: Sessions should be used by only one application thread at a time. This is a common concern in web applications, which are multithreaded by their very nature. Cache providers As we mentioned earlier, caching is a common method used to improve application performance. Caching can be as simple as having a class store frequently used data, or a cache can be distributed among multiple computers. The logic used by caches can also vary widely, but most use a simple least recently used (LRU) algorithm to determine which objects should be removed from the cache after a configurable amount of time. Before you get confused, let's clarify the difference between the Session–level cache, also called the first–level cache, and what this section covers. The Session–level cache stores object instances for the lifetime of a given Session instance. The caching services described in this section cache data outside of the lifetime of a given Session. Another way to think about the difference is that the Session cache is like a transactional cache that only caches the data needed for a given operation or set of operations, whereas a second–level cache is an application-wide cache. By default, Hibernate supports four different caching services. EHCache (Easy Hibernate Cache) is the default service. If you prefer to use an alternative cache, you need to set the cache.provider_class property in the hibernate.cfg.xml file: <property name="cache.provider_class"> This snippet sets the cache provider to the OSCache caching service.
The caching services support the caching of classes as well as collections belonging to persistent classes. For instance, suppose you have a large number of Attendee instances associated with a particular Event instance. Instead of repeatedly fetching the collection of Attendee s, you can cache it. Caching for classes and collections is configured in the mapping files, with the cache element: <class name="Event"table="events"> Collections can also be cached: <set name="attendees"> Once you've chosen a caching service, what do you, the developer, need to do differently to take advantage of cached objects? Thankfully, you don't have to do anything. Hibernate works with the cache behind the scenes, so concerns about retrieving an outdated object from the cache can be avoided. You only need to select the correct value for the usage attribute. The usage attribute specifies the caching concurrency strategy used by the underlying caching service. The previous configuration sets the usage to read–write , which is desirable if your application needs to update data. Alternatively, you may use the nonstrict–read–write strategy if it's unlikely two separate transaction threads could update the same object. If a persistent object is never updated, only read from the database, you may specify set usage to read-only. Some caching services, such as the JBoss TreeCache, use transactions to batch multiple operations and perform the batch as a single unit of work. If you choose to use a transactional cache, you may set the usage attribute to transactional to take advantage of this feature. If you happen to be using a transactional cache, you'll also need to set the transaction.manager_lookup_class mentioned in the previous section. The supported caching strategies differ based on the service used.
Clearly, the caching service you choose will depend on your application requirements and environment. Next, let's look at configuring EHCache. Configuring EHCache By now you're probably tired of reading about configuring Hibernate, but EHCache is pretty simple. It's a single XML file, placed in a directory listed in your classpath. You'll probably want to put the ehcache.xml file in the same directory as the hibernate.cfg.xml file. ehcache.xml file <ehcache> In this example, the diskStore property sets the location of the disk cache store. Then, the listing declares two caches. The defaultCache element contains the settings for all cached objects that don't have a specific cache element: the number of cached objects held in memory, whether objects in the cache expire (if eternal is true , then objects don't expire), the number of seconds an object should remain the cache after it was last accessed, the number of seconds an object should remain in the cache after it was created, and whether objects exceeding maxElementsInMemory should be spooled to the diskStore. Next, for custom settings based on the class, the code defines a cache element with the fully qualified class name listed in the name attribute. (This listing only demonstrates a subset of the available configuration for EHCache. Please refer to the documentation found at http:// ehcache.sf.net for more information.) Inheritance Inheritance is a fundamental concept of object-oriented languages. Through inheritance, objects can inherit the state and behavior of their ancestor, or superclass. The most common use of object inheritance in applications is to create a generic base type with one or more specialized subclasses. Persisting a class hierarchy can be difficult, since each hierarchy can have its own unique requirements. To address the problems found in hierarchy persistence, Hibernate supports three different inheritance persistence strategies:
Each mapping strategy is incrementally more complicated. In the following sections, we‘ll discuss the first two inheritance strategies. We've never needed to use the third, and most complicated, strategy. Table per class hierarchy This strategy is the most basic and easiest to use. All the classes in the hierarchy are stored in a single table. Suppose you have the base Event class, with ConferenceEvent and NetworkingEvent as subclasses. The mapping definition for this hierarchy is shown in listing 6. Listing 6. Table per class hierarchy mapping <class name="Event"table="events"discriminator-value="EVENT"> We've introduced a few new features in the mapping definition. The most important is the inclusion of the discriminator element. The discriminator column is what Hibernate uses to tell the different sub-classes apart when retrieving classes from the database. If you don't specify a discriminator value, Hibernate uses the object's class name. The discriminator element in the example mapping tells Hibernate to look in the event_type column for a string describing the class type. The discriminator is only a column in the relational table-you don't need to define it as a property in your Java object. The subclass element contains the properties and associations belonging to the subclass. Any association element is allowed between sub- class tags. You can't have an id element or a nested subclass element. The table per class hierarchy strategy requires a single table, events, to store the three types of Event instances. As you can see, one table contains the fields for all the objects in the hierarchy. The only obvious limitation is that your subclasses can‘t have columns declared as NOT NULL. Subclasses can't have non-null attributes because inserting the superclass, which doesn‘t even have the non-null attribute, will cause a null column violation when it‘s inserted into the database. The next inheritance strategy, table per sub-class, doesn't have this limitation. Table per subclass Instead of putting all the classes into a single table, you can choose to put each subclass into its own table. This approach eliminates the discriminator column and introduces a one-to-one mapping from the sub-class tables to the superclass table. The mapping definition for this strategy is shown in listing 7. Table-per-subclass mapping <class name="Event"table="events"> The joined-subclass element can contain the same elements as the subclass element. The key element contains the primary key associa-tion to the superclass, Event. <many-to-one class="Event"column="event"/> Table per concrete class Since this association can refer to any class in the Event hierarchy, the association is referred to as a polymorphic association. You can also create a concrete association by giving the name of the specific subclass: <many-to-one class="NetworkingEvent"column="event"/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Send mail to
webmaster@mydeveloperconnection.com with questions or comments about this
web site. |