Recently, I ran in to a series of vexing problems that each had the same solution: User Types in Hibernate. All of these problems originated because I was dealing with a mainframe database which I was trying to access using Hibernate.
Consider the following situations. Example 1: Integer Date Formats
Let's start with one of the most straight-forward oddities. Our database had a number of dates — year, month and day information. Rather than create a column type of DATE, the database designers instead decided to use a column type of Integer and stored values like 19950927 (to represent Sept. 27th, 1995).
Now, Hibernate can easily map an Integer column, but ultimately we wanted to be able to work with the type as a Java Date object. The ugly way of dealing with the problem is to have two different sets of getters/setters, like this:
public Integer getCreationDateAsInteger() { return this.creationDate;
}
public Date getCreationDate() {
return DateUtil.convertIntegerToDate(this.creationDate); }
Using this approach, we'd map the creationDateAsInteger property to the database column, and the other property would not be mapped to a column. That works, but it has the minor disadvantage of cluttering up our code. Instead, we created an IntegerDateUserType class like this:
public class IntegerDateUserType implements UserType { private static final int[] SQL_TYPES = { Types.NUMERIC };
public int[] sqlTypes() { return SQL_TYPES; }
public Class returnedClass() { return Date.class;
}
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) { return true;
} else if (x == null || y == null) { return false;
} else {
return x.equals(y); }
}
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
throws HibernateException, SQLException { Date result = null;
int dateAsInt = resultSet.getInt(names[0]); if (!resultSet.wasNull()) { result = dateAsInt == 0 ? null : DateUtil.convertIntegerToDate(new Integer(dateAsInt)); } return result; }
public void nullSafeSet(PreparedStatement statement, Object value, int index)
throws HibernateException, SQLException { if (value == null) {
statement.setInt(index, 0); } else {
Integer dateAsInteger =
DateUtil.convertDateToInteger((Date) value); statement.setInt(index, dateAsInteger); }
}
public Object deepCopy(Object value) throws HibernateException { return value;
}
public boolean isMutable() { return false;
} }
The two most important methods on this class are the nullSafeGet and the nullSafeSet. The nullSafeGet is used whenever a record is being read from the database — Hibernate would call the nullSafeGet with the Integer value, and we'd convert it into a Date. The nullSafeSet is used whenever we are trying to persist a record, or if we're trying to construct a query using our custom date type. In these cases, we'd have a Date, and we'd want to convert it into an Integer.
To make use of this User Type, we'd configure a property on our mapping file like this: <property name="creationDate" type="ca.intelliware.example.hibernate.IntegerDateUserType" update="true" insert="true" access="property" column="CREATION_DATE" />
Once all that was in place, Hibernate would perform automatic conversion of our dates. By the time the data was in our Java object, we'd have a Java Date class. When we'd persist the object, we'd get a database Integer type. Example 2: Encoding Data User Type
Another case for which you might want to use a User Type is to encode certain pieces of data on the database — say, credit card numbers or something like that.
We discovered that the legacy database was encoding certain key values, not for privacy reasons, but to prevent page-level locking in the database. We were using a version of DB2 on the mainframe, and it didn't support row-level locking. Suppose three different users were trying to insert new records into the same table. And suppose we gave these records simple, incremental ids like 31, 32, and 33. Chances are good that the ids would end up on the same DB page, and there'd be some contention. One user would have locked the page and the other users would get contention errors, even though they're working with completely different records.
The solution is to make the ids non-adjacent, using some kind of randomizer routine or encoding algorithm. That way, we lessen the likelihood that the ids are on the same page, and lower the likelihood of contention.
In our legacy database, the randomized key for the table would be created by scrambling the numbers in an account number. If someone wanted to look up an account, they would type in an account number, and we'd have to encode the number by scrambling the digits, and then look for a record with the scrambled number as a key. Like this:
public Account getAccount(Integer accountNumber) throws ... {
Integer encodedAccountNumber = EncodingAlgorithm.scramble(accountNumber); return getRecordById(encodedAccountNumber);
}
As in Example 1, that worked just fine. But we found ourselves making mistakes by sometimes passing around scrambled or unscrambled versions of the account number, and that caused us some grief. Hibernate User Types, though make the process a lot more transparent.
First we'd create our user type class:
public class EncodableNumberUserType implements UserType { private static final int[] SQL_TYPES = { Types.NUMERIC };
public int[] sqlTypes() { return SQL_TYPES; }
public Class returnedClass() { return Integer.class; }
public boolean equals(Object x, Object y) throws HibernateException { if (x == y) {
return true;
} else if (x == null || y == null) { return false;
} else {
return x.equals(y); }
}
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
throws HibernateException, SQLException { Integer result = null;
int encodedNumber = resultSet.getInt(names[0]); if (!resultSet.wasNull()) {
result = new Integer(EncodingAlgorithm.unscramble(encodedNumber))); }
return result; }
public void nullSafeSet(PreparedStatement statement, Object value, int index)
throws HibernateException, SQLException { Integer unencodedNumber = (Integer) value;
statement.setInt(index, EncodingAlgorithm.scramble(unencodedNumber)); }
public Object deepCopy(Object value) throws HibernateException { return value;
}
public boolean isMutable() { return false;
} }
As before, we'd configure this user type in our Hibernate mapping file, and when we'd read an object from the database, our Java representation would already have unscrambled the account number. Every time we looked at the account number on our Account object, it'd be unscrambled. Every time we'd write it to the database, it'd be scrambled.
One special case we should mention is this, though: when we'd write a query, we'd have to remember that we were using a special type. Here's an example:
Query query = session.createQuery(
"from Account as account where account.number = :accountNumber"); query.setParameter("accountNumber", accountNumber,
new CustomType(EncodableNumberUserType.class)); return query.uniqueResult();
Example 3: Null Types
In the final example, our legacy database decided to avoid nulls in records. All of the existing (COBOL) code that read the database recognized that certain default values indicated that a value hadn't been set. For example, a CHAR column might be set to spaces to indicate that no value had been set.
While this strategy was mostly ignorable, it did present some special problems when we were dealing with relationships. The legacy database did not use referential integrity, and so it was completely possible for them to set a foreign key to 0 or to blanks to indicate that the relationship didn't exist.
Using Hibernate, we'd run into errors because Hibernate wouldn't be able to find a corresponding entry on the foreign table with that default key and would throw an exception. (Hibernate 3 has a configuration option that allows it to ignore this situation, but Hibernate 2, which we were using, does not).
Once again, User Types came to our rescue. This case was a bit less intuitive for us because it wasn't quite clear where to put the type. Imagine this case. We have one class, the WorkItem, and it has a relationship
("assignedWorker") to another persistent class, Worker. On the WORK_ITEM table, there's a column, WORKER_ID, which indicates the assigend worker. If the WORKER_ID is blank, then WorkItem is unassigned.
First we created our UserType:
public class NullableStringUserType implements UserType { public int[] sqlTypes() {
return new int[] { Types.CHAR }; }
public Class returnedClass() { return String.class;
}
public boolean equals(Object x, Object y) throws HibernateException { if (x == y) { return true; } else if (x == null) { return false; } else { return x.equals(y); } }
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
throws HibernateException, SQLException { String result = resultSet.getString(names[0]); return result == null || result.trim().length() == 0 ? null : result;
}
public void nullSafeSet(PreparedStatement statement, Object value, int index)
throws HibernateException, SQLException {
statement.setString(index, value == null ? " " :(String) value); }
public Object deepCopy(Object value) throws HibernateException { return value;
}
public boolean isMutable() { return false;
} }
What wasn't obvious was how to configure this type. In our WorkItem.hbm.xml file, we'd only ever refer to the WORKER_ID column in the context of a many-to-one relationship:
<many-to-one name="assignedWorker" class="ca.intelliware.example.hibernate.Worker" cascade="none" outer-join="auto" update="false" insert="false" access="property" > <column name="WORKER_ID"/> </many-to-one>
How do we fit the custom user type in there? Answer: we didn't. We went over to the Worker.hbm.xml file: <id name="id" column="WORKER_ID"
type="ca.intelliware.example.hibernate.NullableStringUserType" >
Under the covers, when Hibernate instantiates a WorkItem, it uses the key type of the Worker object. Since that type is a Hibernate UserType, then it processes the data that it gets out of a WORK_ITEM table through the
NullableStringUserType. The NullableStringUserType ends up returning a null to Hibernate, and Hibernate understands that to mean that the relationship does not exist.
Database tables that include blobs (and clobs) require special attention in Hibernate. The Java API for blobs allows for read/write access to blob data using streams, but that API can only be invoked while the original
connection/statement is still open. In Hibernate terms, the original Hibernate Session must still be open. In a lot of web architectures, this approach is problematic because developers often use Hibernate persistent classes outside of the original Hibernate session.
I've used two primary strategies for handling blobs in Hibernate: 1. read the blob from the database and hold it as a byte array 2. lazily fetch the blob contents as required.
Both of these approaches have down-sides.
The primary down-side of the first approach relates to extremely large blob values. Consider, for example, a 1 Gigabyte movie file. Holding a 1 Gigabyte movie file as a byte array is going to take about a Gigabyte of RAM. That's a lot of RAM; more than the typical memory allocation of a standard Java application.
The primay down-side of the second approach relates to updateability of the blob records. It becomes harder to issue updates against a database record that includes a blob.
Let's try to describe these two approaches in more detail. Blob to Byte Array
I've seen two common ways of handling the blob to byte array conversion: 1. use a pseudo-Blob property; and
2. use a blob user type. Pseudo-Blob Property
The first strategy is fairly simple. Create a persistent bean, like so: import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.sql.Blob; import java.sql.SQLException; import org.hibernate.Hibernate; public class LabelledPhoto { private String label; private byte[] image; public String getLabel() { return this.label; }
public void setLabel(String label) { this.label = label;
}
public byte[] getImage() { return this.image; }
public void setImage(byte[] image) { this.image = image;
}
@SuppressWarnings("unused") private void setBlob(Blob imageBlob) { this.image = toByteArray(imageBlob); }
@SuppressWarnings("unused") private Blob getBlob(){
return Hibernate.createBlob(this.image); }
private byte[] toByteArray(Blob fromImageBlob) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); try {
return toByteArrayImpl(fromImageBlob, baos); } catch (Exception e) {
}
return null; }
private byte[] toByteArrayImpl(Blob fromImageBlob,
ByteArrayOutputStream baos) throws SQLException, IOException { byte buf[] = new byte[4000];
int dataSize; InputStream is = fromImageBlob.getBinaryStream(); try { while((dataSize = is.read(buf)) != -1) { baos.write(buf, 0, dataSize); } } finally { if(is != null) { is.close(); } } return baos.toByteArray(); } }
Part of the trick to this class is that Hibernate is able to invoke private getters and setters to set properties. As a result, the "label" and "blob" properties will be mapped to columns on the database table, while the "image" property will not.
Instances of this class do not keep any reference to Blob object. It is only referenced during invocations of the setBlob/getBlob calls. And because those methods are private, we're pretty much guaranteed that they'll only be called by Hibernate in the context of a Session.
Create a Blob User Type
Blob user types are slightly more appealing than the above approach, as you can create a user type class that encapsulates all the Hibernate ugliness and end up with a much cleaner-looking persistent class.
Here are two links for Blob User Types:
1.
Blob User Type in the Hibernate Documentation2.
Blog User Type on i-Proving.caUser types allow you to add additional functionality at the time the blob is read or saved. For example, because blobs are often used to store files, you can save some disk space by performing a compression on the bytestream as you write it to the database. The persistent class and all of its clients remain unaware of the compression; compression is a "dirty little secret" of the Hibernate configuration and user type.
Caution using SQL Queries with Hibernate
As someone fairly new to Hibernate, I was pleasantly surprised to see that it supported the execution of an SQL query through the createSQLQuery() method within the Hibernate Session.
Initially, it seemed beneficial to use plain old SQL to generate my DAO queries. After all I already knew SQL, why waste my time learning HQL (Hibernate's query language) or Hibernate's Criteria API. In addition, with SQL I could easily test my queries in DB editing tool like DBVisualizer and in the ?rare? event that I might need it, a DBA unfamiliar with Hibernate could easily enhance and maintain my queries.
It seemed like the solution.
However, on further analysis I have changed my tune.
There are many reasons from a purely OO point of view to avoid straight SQL. However, many other people make a far more compelling argument on this point than I can here, so I'll leave that to them.
The main point I would like to focus on here is:
The Hibernate SQLQuery bypasses the Hibernate Session cache and queries ONLY against the database. This means that if you perform a SQL query on the database in the same transaction that you've just performed a save and/or update, the saved / updated objects in the Hibernate Session Cache will not be included in the SQL result.
By the same token, HQL and the Hibernate Criteria queries check the Session cache before executing the query. If there are objects that the HQL query may execute against hibernate will flush the cache to the database.
This essentially means that unlike the SQLQuery, HQL and Hibernate Criteria queries will ALWAYS include the objects in the Session Cache automatically.
NOTES
1) As noted on the SQLQuery diagram, you can actually manually force Hibernate to flush the cache by executing session.flush(). This would require you to execute this line before each SQLQuery you execute. For instance: session.flush();
List result = session.createSqlQuery("select name from user where name = :userName") .setParameter("userName", user.getName()) .list();
However, this has two major disadvantages:
• You might accidently forget to do this, leaving your application in a poor state.
• session.flush() is an expensive operation. Using HQL and Criterias Hibernate can decide whether or not it needs to flush. Thereby avoiding unnecessary flushing.
2) Junit testing.
It's worth mentioning that the code in the example diagrams are Junit tests. By utilizing hibernate's transaction mechanism and the session cache we are able to test our dao code without actually changing the state of our database. At the end of the test we can just rollback the transaction and undo any updates that we made.
Hibernate comes with three different caching mechanisms - first level, second level and query cache. Truly
understanding how the Hibernate caches work and interact with each other is important when you need to increase performance - just enabling caching in your entity with an annotation (or in classic .hbm.xml mapping file) is easy. But understanding what and how things happens behind the scenes is not. You might even end up with a less performing system if you do not know what you are doing.
SessionFactory and Session
The purpose of the Hibernate SessionFactory (called EntityManager in JEE) is to create Sessions, initialize JDBC connections and pool them (using a pluggable provider like C3P0). A SessionFactory is immutable and built from a Configuration holding mapping information, cache information and a lot of other information usually provided by means of a hibernate.cfg.cml file or through a Spring bean configuration.
A Session is a unit of work at its lowest level - representing a transaction in database lingua. When a Session is created and operations are done on Hibernate entities, e.g. setting an attribute of an entity, Hibernate does not go of and update the underlying table immediately. Instead Hibernate keeps track of the state of an entity, whether it is dirty or not, and flushes (commits) updates at the end at the end of a unit of work. This is what Hibernate calls the first level cache.
The 1st level cache
Definition: The first level cache is where Hibernate keeps track of the possible dirty states of the ongoing Session's loaded and touched entities. The ongoing Session represents a unit of work and is always used and can not be turned of. The purpose of the first level cache is to hinder to many SQL queries or updates beeing made to the database, and instead batch them together at the end of the Session. When you think about the 1st level cache think Session.
The 2nd level cache
The 2nd level cache is a process scoped cache that is associated with one SessionFactory. It will survive Sessions and can be reused in new Session by same SessionFactory (which usually is one per application). By default the 2nd level cache is not enabled.
The hibernate cache does not store instances of an entity - instead Hibernate uses something called dehydrated state. A dehydrated state can be thought of as a deserialized entity where the dehydrated state is like an array of strings, integers etc and the id of the entity is the pointer to the dehydrated entity. Conceptually you can think of it as a Map which contains the id as key and an array as value. Or something like below for a cache region:
{ id -> { atribute1, attribute2, attribute3 } } { 1 -> { "a name", 20, null } }
{ 2 -> { "another name", 30, 4 } }
If the entity holds a collection of other entities then the other entity also needs to be cached. In this case it could look something like:
{ id -> { atribute1, attribute2, attribute3, Set{item1..n} } } { 1 -> { "a name", 20, null , {1,2,5} } }
The actual implementation of the 2nd level cache is not done by Hibernate (there is a simple Hashtable cache available, not aimed for production though). Hibernate instead has a plugin concept for caching providers which is used by e.g. EHCache.
Enabling the 2nd level cache and EHCache
To get the 2nd level cache working you need to do 2 things:
1 Cache Strategy. Enable a cache strategy for your Hibernate entity - either in the class with an annotation or in
the hibernate mapping xml file if you are stuck with pre java5. This can be done for an entity by providing this little snippet into your hbm.xml file (a better place is to store the cache setting strategy in hibernate.cg.xml file )
<class name="org.grouter.domain.entities.Router" table="ROUTER"> <cache usage="transactional|read-write|nonstrict-read-write|read-only" /> <id ...
</class>
or using an annotation for your entity (if you are on java5 or greater)
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) public class Router { ... }
And as mentioned above if you want to cache collections of an entity you need to specify caching on collection level:
<class name="org.grouter.domain.entities.Router" table="ROUTER"> <cache usage="transactional|read-write|nonstrict-read-write|read-only"/> <id ... <set name="nodes"> <cache usage="transactional|read-write|nonstrict-read-write|read-only"/> ... </set> </class>
Hibernate has something called a cache region which by default will be the full qualified name of your Java class. And if you like me are a fan of convention over configuration you will use the default region for an entity. A cache region will also be needed for the collection using the full qualified name of the Java class plus the name of the collection name (i.e. org.grouter.domain.entities.Router.nodes)
2 Cache provider. Setting up the physical caching for a cache provider. If you are using EHCache - which is the
most common choice i dear to say - then you will need to specify some settings for the cache regions of your entities in a file called ehcache.xml. The EHCache will look for this file in the classpath and if not found it will fallback to ehcache-failsafe.xml which resides in the ehcache.jar library A typical sample for an EHCache configuration could look like (see mind map below for explanations):
<cache name="org.grouter.domain.entities.Router" maxElementsInMemory="1000" eternal="false" timeToLiveSeconds="600" overflowToDisk="false"/>
and
<cache name="org.grouter.domain.entities.Router.nodes" maxElementsInMemory="1000" eternal="false" timeToLiveSeconds="600" overflowToDisk="false"/>
The name maps to the name of the cache region of your entity. The attribute maxelementsInMemory needs to be set so that Hibernate does not have to swap in and out elements from the cache. A good choice for a read only cache would be as many entities there are in the database table the entity represents. The attribute eternal, if set to true means that any time outs specified will be ignored and entities put into the cache from Hibernate will live for ever.
The Query cache
The Query cache of Hibernate is not on by default. It uses two cache regions called
org.hibernate.cache.StandardQueryCache and org.hibernate.cache.UpdateTimestampsCache. The first one stores the query along with the parameters to the query as a key into the cache and the last one keeps track of stale query results. If an entity part of a cached query is updated the the query cache evicts the query and its cached result from the query cache. Of course to utilize the Query cache the returned and used entities must be set using a cache strategy as discussed previously. A simple load( id ) will not use the query cache but instead if you have a query like:
Query query = session.createQuery("from Router as r where r.created = :creationDate"); query.setParameter("creationDate", new Date());
query.setCacheable(true);
List l = query.list(); // will return one instance with id 4321
Hibernate will cache using as key the query and the parameters the value of the if of the entity. { query,{parameters}} ---> {id of cached entity}
{"from Router as r where r.id= :id and r.created = :creationDate", [ new Date() ] } ----> [ 4321 ] ]
Pragmatic approach to the 2nd level cache
How do you now if you are hitting the cache or not? One way is using Hibernates SessionFactory to get statistics for cache hits. In your SessionFactory configuration you can enable the cache statistics by:
<prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.use_sql_comments">true</prop> <prop key="hibernate.cache.use_query_cache">true</prop> <prop key="hibernate.cache.use_second_level_cache">true</prop> <prop key="hibernate.generate_statistics">true</prop> <prop key="hibernate.cache.use_structured_entries">true</prop>
The you might want to write a unit test to verify that you indeed are hitting the cache. Below is some sample code where the unit test is extending Springs excellent AbstractTransactionalDataSourceSpringContextTests
public class MessageDAOTest extends AbstractDAOTests // which extends AbstractTransactionalDataSourceSpringContextTests {
public void testCache() {
long numberOfMessages = jdbcTemplate.queryForInt("SELECT count(*) FROM message "); System.out.println("Number of rows :" + numberOfMessages);
final String cacheRegion = Message.class.getCanonicalName();
SecondLevelCacheStatistics settingsStatistics = sessionFactory.getStatistics(). getSecondLevelCacheStatistics(cacheRegion);
StopWatch stopWatch = new StopWatch(); for (int i = 0; i < 10; i++)
{
stopWatch.start();
messageDAO.findAllMessages(); stopWatch.stop();
System.out.println("Query time : " + stopWatch.getTime()); assertEquals(0, settingsStatistics.getMissCount());
assertEquals(numberOfMessages * i, settingsStatistics.getHitCount()); stopWatch.reset();
System.out.println(settingsStatistics); endTransaction();
// spring creates a transaction when test starts - so we first end it then start a new in the loop startNewTransaction();
} } }
The output could looke something like:
30 Jan 08 23:37:14 INFO org.springframework.test.AbstractTransactionalSpringContextTests:323 - Began transaction (1):
transaction manager [org.springframework.orm.hibernate3.HibernateTransactionManager@ced32d]; default rollback = true
Number of rows :6 Query time : 562
SecondLevelCacheStatistics[hitCount=0,missCount=0,putCount=6,elementCountInMemory=6,elementCountOnDisk =0,sizeInMemory=8814]
30 Jan 08 23:37:15 INFO org.springframework.test.AbstractTransactionalSpringContextTests:290 - Rolled back transaction
after test execution
30 Jan 08 23:37:15 INFO org.springframework.test.AbstractTransactionalSpringContextTests:323 - Began transaction (2):
transaction manager [org.springframework.orm.hibernate3.HibernateTransactionManager@ced32d]; default rollback = true
Query time : 8
SecondLevelCacheStatistics[hitCount=6,missCount=0,putCount=6,elementCountInMemory=6,elementCountOnDisk =0,sizeInMemory=8814]
30 Jan 08 23:37:15 INFO org.springframework.test.AbstractTransactionalSpringContextTests:290 - Rolled back transaction
after test execution
30 Jan 08 23:37:15 INFO org.springframework.test.AbstractTransactionalSpringContextTests:323 - Began transaction (3):
transaction manager [org.springframework.orm.hibernate3.HibernateTransactionManager@ced32d]; default rollback = true
Query time : 11
one I use is p6spy which will show you exactly what is issued over a JDBC connection to the actual backend database. For other tips have a look below in the mindmap.
TRANSACTIONS, CONCURRENCY AND CACHING
Transactions allow multiple users to work concurrently with the
same data without compromising the integrity and correctness of the data
Atomicity, consistency, isolation,
and durability are together known as the ACID criteria.
In an online application, database transactions must have extremely short
lifespans. A database transaction should span a single batch of database operations, interleaved with business logic.
Databases implement the notion of a unit of work as a database transaction. A database transaction groups data-access operations. A transaction is guaranteed to end in one of two ways: it’s either committed or rolled back.
JDBC and JTA transactions
You begin a transaction by calling setAutoCommit(false) on a JDBC connection and end it by calling commit(). You may, at any time, force an immediate rollback by calling rollback()
Hibernate automatically
disables auto commit mode as soon as it fetches a connection
In a system that stores data in multiple databases, you can’t achieve atomicity using JDBC alone. JTA is also for declarative container managed transactions (CMT).
CMT allows you to avoid
explicit transaction demarcation calls in your application source code;
Hibernate provides its own abstraction layer
Transaction management is exposed to the application developer via the Hibernate Transaction interface.
Session session = sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); concludeAuction(); tx.commit(); } catch (Exception e) { if (tx != null) {
try {
tx.rollback();
} catch (HibernateException he) { //log he and rethrow e
} } throw e; } finally { try { session.close();
} catch (HibernateException he) { throw he;
} }
The call to tx.commit() synchronizes the Session state with the database. Hibernate then commits the underlying transaction if and only if beginTransaction()
started a new transaction (in both managed and non-managed cases).
If beginTransaction() did not start an underlying database transaction, commit() only synchronizes the Session state with the database; it’s left to the responsible party (the
code that started the transaction in the first place) to end the transaction.
If concludeAuction() threw an exception, we must force the transaction to roll back by calling tx.rollback(). This method either rolls back the transaction immediately or marks the transaction for “rollback only”
It’s critically important to close the Session in a finally block in order to ensure that the JDBC connection is released and returned to the connection pool.
The Hibernate Session implements transparent write behind. Changes to the domain model made in the scope of a Session aren’t immediately propagated to the database.
Hibernate flushes occur only at the following times: When a Transaction is committed
Sometimes before a query is executed
When the application calls Session.flush() explicitly
FlushMode.AUTO
FlushMode.COMMIT :Specifies that the session won’t be flushed before query execution
ISOLATION ISSUES
Lost update—Two transactions both update a row and then the second transaction aborts, causing both changes to be lost.
Dirty read—One transaction reads changes made by another transaction that hasn’t yet been committed.
Unrepeatable read—A transaction reads a row twice and reads different state each time.
Second lost updates problem—A special case of an unrepeatable read. Imagine that two concurrent transactions both read a row, one writes to it and commits, and then the second writes to it and commits.
Phantom read—A transaction executes a query twice, and the second result set includes rows that weren’t visible in the first result set.
ISOLATION LEVELS
Read uncommitted—Permits dirty reads but not lost updates. exclusive write locks.
Read committed—Permits unrepeatable reads but not dirty reads. momentary shared read locks and exclusive write locks.
Repeatable read—Permits neither unrepeatable reads nor dirty reads.Phantom reads may occur. shared read locks and exclusive
write locks.
Serializable—Provides the strictest transaction isolation.
CHOOSING AN ISOLATION LEVEL
The combination of the (mandatory) Hibernate first-level session
cache and versioning already gives you most of the features of repeatable read isolation.
Hibernate will then set this isolation level on every JDBC connection obtained from a connection pool before starting a transaction.
USING PESSIMISTIC LOCKING
A pessimistic lock is a lock that is acquired when an item of data is read and that is held until transaction completion.
The Hibernate LockMode class lets you request a pessimistic lock on a particular item. In addition, you can use the LockMode to force Hibernate to bypass the cache layer or to execute a simple version check.
Category cat =
(Category) session.get(Category.class, catId, LockMode.UPGRADE); cat.setName("New Name");
tx.commit();
With this mode, Hibernate will load the Category using a SELECT...FOR UPDATE, thus locking the retrieved rows in the database until they’re released when the transaction end
LockMode.NONE: Don’t go to the database LockMode.READ : Bypass both levels of the cache
LockMode.UPDGRADE:Bypass both levels of the cache, do a version check , and obtain a database-level pessimistic upgrade lock
LockMode.UPDGRADE_NOWAIT : The same as UPGRADE, but use a SELECT...FOR UPDATE NOWAIT on Oracle. his disables waiting for concurrent lock releases, thus throwing a locking exception immediately if the lock can’t be obtained. LockMode.WRITE : Is obtained automatically when Hibernate has written to a row in the current transaction
Locking is a mechanism that prevents concurrent access to a particular item of data.
By specifying an explicit LockMode other than LockMode.NONE, you force Hibernate to bypass both levels of the cache and go all the way to the database.
Our coarse-grained transactions will correspond to what the user of the application considers a single unit of work.
The database isolates the effects of concurrent database transactions. It should appear to the application that each transaction is the only transaction currently
accessing the database
You shouldn’t hold the database transaction open while waiting for user input. Business processes, which might be considered a single unit of work from the point of view of the user, necessarily span multiple user client requests.
Last commit wins First commit wins Merge conflicting updates
Hibernate can help you implement the second and third strategies, using managed versioning for optimistic locking.
USING MANAGED VERSIONING
Managed versioning relies on either a version number that is incremented or a timestamp that is updated to the current time, every time an object is modified modified.
Hibernate managed versioning, we must add a new property to our Comment class and map it as a version number using the <version> tag.
The version number is just a counter value <class name="Comment" table="COMMENTS"> <id ...
<version name="version" column="VERSION"/> ...
</class>
Hibernate will increment the version number
whenever an object is dirty. This includes all dirty properties, whether they’re single-valued or collections.
public class Comment { ...
private Date lastUpdatedDatetime; ...
void setLastUpdatedDatetime(Date lastUpdatedDatetime) { this.lastUpdatedDatetime = lastUpdatedDatetime; }
public Date getLastUpdatedDatetime() { return lastUpdatedDatetime;
} }
<class name="Comment" table="COMMENTS"> <id .../>
<timestamp name="lastUpdatedDatetime" column="LAST_UPDATED"/> ...
</class>
update COMMENTS set COMMENT_TEXT='New comment text', VERSION=3 where COMMENT_ID=123 and VERSION=2
If another application transaction would have updated the same item since it was read by the current application transaction, the VERSION column would not contain the value 2, and the row would not be updated. Hibernate would check the row count returned by the JDBC driver—which in this case would be the number of rows updated, zero—and throw a StaleObjectStateException.
You can’t use an exclusive lock to block concurrent access longer than a single database transaction.
TRANSACTION SUMMARY
Usually, you use read committed
isolation for database transactions, together with optimistic concurrency control (version and timestamp checking) for long application transactions. Hibernate greatly simplifies the implementation of application transactions because it manages version numbers and timestamps for you.
GRANULARITY OF A SESSION
Usually, we open a new Session for each client request (for example, a web browser request) and begin a new Transaction. After executing the business logic, we commit the database transaction and close the Session, before sending the response to the client
The session (S1) and the database transaction (T1) therefore have the same granularity.
If you need a long-running application transaction, you might, thanks to detached objects and Hibernate’s support for optimistic locking
implement it using the same approach
Suppose your application transaction spans two client request/response
cycles—for example, two HTTP requests in a web application. You could load the interesting objects in a first Session and later reattach them to a new Session after they’ve been modified by the user. Hibernate will automatically perform a version check.
Alternatively, you might prefer to use a single Session that spans multiple requests to implement your application transaction.
A Session is serializable and may be safely stored in the servlet HttpSession, for example. The underlying JDBC connection has to be closed, of course, and a new connection must be obtained on a subsequent request. You use the disconnect() and reconnect() methods of the Session interface to release the connection and
later obtain a new connection. The longer the session remains open, the greater the chance that it holds stale data in its cache of persistent objects
<class name="Comment" table="COMMENT" optimistic-lock="all"> <id .../>
... </class>
Now, Hibernate will include all properties in the WHERE clause:
Hibernate will include only the modified properties if you set optimistic-lock="dirty"
it’s slower, more complex, and less reliable
than version numbers and doesn’t work if your application transaction spans multiple sessions
CACHING THEORY AND PRACTICE
The cache may be used to avoid a database hit whenever ¦ The application performs a lookup by identifier (primary key) ¦ The persistence layer resolves an association lazily
Transaction scope+ Process scope
Cluster scope
CACHING AND OBJECT IDENTITY
Any ORM implementation that allows multiple units of work to share the same persistent instances must provide some form of object-level locking to ensure synchronization of concurrent access. Usually this is implemented using read and write
locks (held in memory) together with deadlock detection.
You shouldn’t use any kind of cache beyond a transaction scope cache for legacy data
USING THE FIRST LEVEL CACHE
The first-level cache is
always active—it’s used to resolve circular references in your object graph and to optimize performance in a single unit of work.
The session cache ensures that when the application requests the same persistent object twice in a particular session, it gets back the same Java instance.
Changes made in a particular unit of work are always immediately visible to all other code executed inside that unit of work.
Whenever you pass an object to save(), update(), or saveOrUpdate(), and whenever you retrieve an object using load(), find(), list(), iterate(), or filter(),
that object is added to the session cache. When flush() is subsequently called, the state of that object will be synchronized with the database.
you can use the
evict() method of the Session to remove the object and its collections from the first-level cache.
USING SECOND LEVEL CACHE
The (process or cluster scope)
second-level cache on the other hand is optional and works best for read-mostly candidate classes.
If you have data that is updated more often than it’s read, don’t
enable the second-level cache, even if all other conditions for caching are true! A concurrency strategy is a mediator; it’s responsible for storing items of data in the cache and retrieving them from the cache.
It’s possible to define your own concurrency strategy by implementing net.sf.hibernate.cache.CacheConcurrencyStrategy,
transactional—Available in a managed environment only. It guarantees full transactional isolation up to repeatable read, if required.
read-write—Maintains read committed isolation, using a timestamping mechanism. nonstrict-read-write—Makes no guarantee of consistency between the cache and the database.
read-only—A concurrency strategy suitable for data which never changes.
You might be better off disabling
the second-level cache for a particular class if stale data isn’t an option.
CHOOSING A CACHE PROVIDER
Hibernate forces you to choose a single cache provider for the whole application.
EHCache
OpenSymphony OSCache SwarmCache s.
JBossCache CACHING IN PRACTICE <class name="Category" table="CATEGORY"> <cache usage="read-write"/> <id .... </class> <class name="Category" table="CATEGORY"> <cache usage="read-write"/> <id ....
<set name="items" lazy="true"> <cache usage="read-write"/> <key ....
</set> </class>
A collection cache holds only the identifiers of the associated item
instances. So, if we require the instances themselves to be cached, we must enable caching of the Item class.
<class name="Item" table="ITEM">
<id ....
<set name="bids" lazy="true"> <cache usage="read-write"/> <key .... </set> </class> <class name="Bid" table="BID"> <cache usage="read-only"/> <id .... </class>
Cached Bid data is valid indefinitely, because bids are never updated. No cache invalidation is required.
Hibernate keeps different classes/collections in different cache regions. A region is a named cache: a handle by which you can reference classes and collections in the cache provider configuration and set the expiration policies applicable to that region.
The name of the region is the class name, in the case of a class cache; or the class name together with the property name, in the case of a collection cache. Category instances are cached in a region named org.hibernate.auction.Category, and the items collection is cached in a region named org.hibernate.auction.Category. items.
SETTING UP A LOCAL CACHE PROVIDER
regions.
EHCache has its own configuration file, ehcache.xml, in the classpath of the application.
<cache name="org.hibernate.auction.model.Category" maxElementsInMemory="500" eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" />
We therefore disable eviction by timeout by choosing
a cache size limit greater than the number of categories in our system and setting eternal="true". <cache name="org.hibernate.auction.model.Bid" maxElementsInMemory="5000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="100000" overflowToDisk="false" />
SETTING UP A REPLICATED CACHE
Hybernate aims to be a complete solution to the problem of managing persistent data in Java It mediates the application's interaction with the database,
leaving the developer free to concentrate on the business problem at hand. Hybernate is not intrusive
RELATIONAL TECHNOLOGY
Relational Technology allows to share data among different applications or among different technologies in the same application
OBJECT-ORIENTED PERSISTENCE
Persistence allows an object to outlive the process that created it
An object or a graph of interconnected objects may be stored to disk and retrieved later in a new process
A transient object has a limited life that is bounded by the process that instantiated it.
Modern relational databases provide a structured representation of persistent data, enabling
sorting, searching and aggregation of data. Database management systems are responsible for managing concurrency and data integrity, and share data between multiple users and applications.
So Persistence means:
Storage, organization and retrieval of structured data Concurrency and data integrity
Data sharing
We think of these problems in the context of an object-oriented application that uses a domain model. The business logic interacts with the object-oriented domain model and its runtime realization as a graph of interconnected objects.
Mismatch : SQL operation result in a tabular representation
PARADIGM MISMATCH
User has many BillingDetails
GRANULARITY
Adding a UDT (User Defined Column-Type) to a SQL Table, like Address in the Table User, is not portable and obscure
We might add several columns to the table Address
Classes come in the database in a range of different levels of granularity, coarse-grained like User, finer-grained like Address, simple String-valued like zipcode. Just two levels of granularity in the database : Table and Column
Some persistence mechanisms force the less flexible mechanism, like having the property zipcode in the class User
INHERITANCE
CreditCard, DebitCard, Cheque derived from BillingDetails
POLYMORPHISM
A User may be associated with an instance of any of the subclasses of BillingDetails Polymorphic query which returns the appropriate instance of BillingDetails
SQL Database don't provide a notion of inheritance. A foreign key refers to a single table
OBJECT IDENTITY
Java equalness : object identity and equality by value.
SQL identity : identity of a row is expressed as the primary value
Two (non-identical) objects may represent the same row on the database Surrogate key : primary key with no meaning to the user.
E.G. if username was key and I had to change
ASSOCIATIONS
OO languages represent associations using object references and collections of object references. For SQL , an association is represented as a foreign key column, with copies of key in several tables. OO reference are directional, the association must be defined twice if it needs be navigate in both directions. FK are not directional since you can create arbitrary data associations with joins and projections. Java associations may be many-to-many (Set in both objects). Table associations are always one-to-many or one-to-one
Link table to represent a many-to-many associations in a relational database, which does not appear anywhere in the object model
OBJECT GRAPH NAVIGATIONS AND TABLE JOINS
Walking the object graph : navigate from one object to another, following associations between instances To minimize the number of requests to the database, you need to join tables using SQL.
We need to know the portion of the object graph we plan to access before we start navigating the object graph
N+1 selects problem : execute a select statement for every node in the graph. Any object persistence solution provides functionality for accessing the data only when the object is first accessed.
Mismatch cost :
30% of the application code is written to handle the SQL / JDBC mismatch Modeling problem : OO and relational model in a different way
JDBC's Statement -oriented approach to move data to and from the database. A structural relationship must be specified three times (insert / update / delete)
PERSISTENCE LAYER
Organize classes by concern. Persistence is one concern. Cross-cutting concerns like logging, authorization, transaction.
Group all classes and components responsible for persistence in a separate persistence layer
Layered architecture
It defines interfaces between code that implements the various concerns, allowing a change to the way one concern is implemented without disruption to the code in the other layers
Layer communicate top to bottom. The only layer one layer is aware of is the one below it
Presentation --> Business--> Persistence
All three uses the utility and helper classes.
Business model may use its domain model or reuse the one defined by the persistence model.
HandCoding SQL
Use the DAO pattern to hide JDBC code IBATIS let you handcraft SQL
Serialization
Serialization used by RMI and to replication application states
Setialized graph of interconnected object can only be accessed as a whole, so it is unusable for arbitrary search or aggregation. Loading and overwriting and entire object in each transaction is no option for systems designed to support high concurrency.
Niche : persistence for desktop applications.
EJB
BMP don't perform efficiently CMP are not acceptable solutions
they force your domain model in a normal form CMP too fine-grained to realize software components . A reusable component should be coarse-grained EJBS don'T support polymorphic associations or queries Entity beans are not portable in practice
Entity beans are not serializable, so we need to define a parallel domain model (DTOs) EJB is intrusive and mandates an unnatural Java style
OODBMS
is an extension to the application environment
Back-end data store, object cache and client application coupled tightly and communicating via a proprietary network protocol
object oriented database development begins with the top-down definition of host language bindings that add persistence capabilities to the programming languages
ODMG defined an API.
JDO opened up new possibilities . Niche markets : CAD / CAM and scientific computing.
XML persistence Stored procedures
OBJECT RELATIONAL MAPPING
ORM is the automated persistence of objects in a Java application to the table in a relational database, using metadata that describes the mapping between the objects and the database.
ORM Solution has 4 pieces :
API for performing CRUD operations
API for specifying queries that refer to classes and properties of classes Facility to specify metadata
and other optimization techniques
SQL is generated from a metadata-based description With ORM the application interacts with the ORM APIs and the domain model is abstracted from the underlying
SQL / JDBC. ORM may take responsibility for optimistic locking and caching.
Pure relational : application designed around the relational model
Light object mapping : Entities are represented as classes that are mapped manually to the relational tables Medium object mapping : Application is designed around an object model. SQL is generated using code generation or at runtime by framework code. Associations between object are supporte dby the persistence mechanism and queries may be specified using an object oriented expression language. Objects are cached by the persistence layer.
Full object mapping : supports sophisticated object modelling : composition, inheritance, polymorphism. The persistent layer implements transparent persistence: persistent classes do not inherit any special object or implement a special interface. Efficient fetching strategies are implemented transparently to the application.
Orm Problems :
What do persistent class look like ? Do we have to adopt any convention ? How is mapping metadata defined ?
How should we map class inheritance hierarchies ?
How do object identity relate to database primary identity ?
How does the persistence logic interact at runtime with the object of business domain ? What is the lifecycle of a persistent object ?
What facilities are providing for sorting , searching , aggregating ? How do we efficiently retrieve data with associations ?
(Avoid n+1 selects problem and Cartesian product fetch problem)
Cache management, Transaction and Concurrency
Persistence : Persistence related code may be the most tedious code in a Java application Maintanability : LOC is reduced
ORM provides a buffer between the two models (object orientation and relational database) Performance : Optimizing Hybernate queries may be easier.
ORM programmers had more time to investigate database quirks. Vendor Independence :
ORM abstracts your application from the underlying SQL database
HELLO WORLD WITH HYBERNATE
Persistent classes that are mapped to database tables. Message class is a Java Bean which has the identifier attribute.
No container is need to use the classes which are going to be persisted.
Session session = getSessionFactory().openSession(); Transaction tx = session.beginTransaction();
Message message = new Message("Hello World"); session.save(message);
tx.commit(); session.close();
It will result in the execution of an INSERT sql statement . The id is in identifier property in Message, it will hold a generated unique value.
To retrieve all messages from the database
Session session = getSessionFactory().openSession(); Transaction tx = session.beginTransaction();
List messages = session.find("from Message as m order by m.text asc"); tx.commit();
Hybernate Query Language XML MAPPING DOCUMENT <hibernate-mapping> <class name="hello.Message" table="MESSAGES" > <id name="id" column="MESSAGE_ID"> <generator class="increment"/> </id> <property name="text" column="MESSAGE_TEXT"> </property> <many-to-one name="nextMessage" cascade="all" column="NEXT_MESSAGE_ID"/> </class> </hibernate-mapping>
Hybernate has all the information that is needed to insert / update / delete instances of the Message class.
Session session = getSessionFactory().openSession(); Transaction tx = session.beginTransaction();
Message message = (Message)session.load(Message.class, new Long(1)); message.setText("Bla Bla");
Message nextMessage = new Message("Take me to your leader"); message.setNextMessage(nextMessage);
tx.commit(); session.close();
Updates message 1 and 2.
Cascading save : it saves us the effort of making an object persistent as long as it is reachable by an already persistent instance.
Transactional write-behind : efficient ordering that avoid database foreign key violations
HYBERNATE INTERFACES
HYBERNATE INTERFACES
For CRUD an querying operations : Session, Transaction, Query
To configure Hybernate : called by the application infrastructure to configure Hybernate : Configurations
Callback allow the application to react to events occurring inside Hybernate : Interceptor, Lifecycle, Validatable Interfaces that allow extension of Hybernate's powerful mapping functionality :
UserType, CompositeUserType, IdentifierGenerator
Existing APIS : JTA, JNDI
Business Layer : Lifecycle, validatable, Persistent classes
Persistence Layer : Session, Transaction, Query, SessionFactory, Configuration, Interceptor, UserType JNDI, JDBC, JTA
Store and retrieve persistent objects
Session : primary interface, lightweight, not threadsafe
Cache or collection of objects related to a single unit of work. Hybernate can detect changes and persist them
Session is a persistence manager because is the interface for storing and retrieving HttpSession is the user session
SessionFactory : used to retrieve a Session supposed to be shared by many threads
caches SQL statements and other mapping metadata which is used at runtime
May hold cached data which has been used in a unit of work and may be reused in another unit of work
Configuration : used to configure and bootstrap Hybernate
the application uses the configuration interface to specify the location of mapping documents and then create the SessionFactory
Transaction : abstract application code from the underlying transaction implementation
Query and Criteria : Query perform queries against the database and control how the query is executed. Criteria is similar, allows to perform queries in an object oriented way
Session has some lightweight methods to perform queries without using the interface Query Query is lightweight and cannot be used outside the Session that created it
Callback : LifeCycle and Validatable allow a persistent object to react to events relating to its own persistent lifecycle, which is encompassed by an object's crud operations. They should be implemented by the objects whose lifecycle we want to monitor
Interceptor allows the application to process callbacks without forcing the persistent classes to implement some Hybernate-specific APIs.
associations, have a corresponding Hybernate type. All built-in types have its corresponding Type
Hibernate supports user-defined types as well : UserType and CompositeUserType interfaces You may add commonly used new application classe like Address, Name, MoneyAmount
EXTENSION INTERFACES : IdentifierGenerator Dialect
Cache / CacheProvider ConnectionProvider
TransactionFactory, Transaction, TransactionManagerLookup ClassPersister
PropertyAccessor ProxyFactory
BASIC CONFIGURATION
MANAGED ENVIRONMENT : Pools Resources such as database connections and allows transactions boundaries and security to be specified declaratively
NON MANAGED : Basic concurrency management via thread pooling. Tomcat or stand-alone application Application manages database connections and demarcates transaction boundaries
In a managed environment, hibernate integrates with container-managed transactions and datasources
Creating a Session Factory
Configuration cfg = new Configuration(); cfg.addResource("hello/Message.hbm.xml"); cfg.setProperties(System.getProperties());
Message.hbm.xml is relative to the application classpath
Method Chaining
Configuration cfg = new Configuration(). .addResource("hello/Message.hbm.xml") .setProperties(System.getProperties()) .buildSessionFactory();
Mapping file for each class should be placed in the same directory as the class.
If files are named as the Class plus .hbm.xml you can use the addClass method instead
SessionFactory sessions = new Configuration() .addClass(org.hybernate.auction.model.Item.class) .setProperties(System.getProperties()
.buildSessionFactory();
Each Session Factory is available for one database and ready to produce Sessions to work with that particular database and a set of class mappings.
You also need to specify how database connections are to be obtained
SPECIFY CONFIGURATION PROPERTIES
Pass an instance of java.util.Properties to Configuration.setProperties Set system properties using -Dproperty=value
Place a file called hibernate.properties
Rarely : provide a Connection to the application when it opens a Hibernate Session from the SessionFactory
Of all the configuration options, database settings are the most important. Configuration in non-managed environments :
The application is responsible for obtaining JDBC connections You tell Hybernate how to get JDBC connections
Java applications should use connection pools because : Acquiring a new connection is too expensive
Maintaining many idle connections is expensive Creating prepared statements is expensive
Open source Connection Pools like C3P0
The application code usually calls the
connection pool to obtain JDBC connections and execute SQL statements
You can use any connection pool with JDBC,
we show how to integrate it with C3P0. It supports it by default
hibernate.connection.driver_class = org.postgresql.Driver hibernate.connection.url = jdbc:postgresql://localhost/auctiondb hibernate.connection.username = auctionuser hibernate.connection.password = secret hibernate.dialect = net.sf.hibernate.dialect.PostgreSQLDialect hibernate.c3p0.min_size=5 hibernate.c3p0.max_size=20 hibernate.c3p0.timeout=300 hibernate.c3p0.max_statements=50 hibernate.c3p0.idle_test_period=3000
cfg.Environment documents every Hibernate configuration property,
Configuration in managed environments
EJB (Session, Message) access Hibernate Hibernate Interfce : Session , Transaction, Query
Hibernate's Back End: Transaction Manager / Resource Manager
Application exposes a connection pool as a JNDI's bound DataSource
hibernate.connection.datasource = java:/comp/env/jdbc/AuctionDB hibernate.transaction.factory_class = \ net.sf.hibernate.transaction.JTATransactionFactory hibernate.transaction.manager_lookup_class = \ net.sf.hibernate.transaction.JBossTransactionManagerLookup hibernate.dialect = net.sf.hibernate.dialect.PostgreSQLDialect
Hybernate needs to know where to find the Application Server's TransactionManager in order to integrate fully
with the container's transaction
In a managed environment where JTA is used, Transaction abstracts the underlying JTA or JDBC transaction
The transaction strategy is set with the property hibernate.
connection.factory_class and can take one of the two following values :
net.sf.hibernate.transaction.JDBCTransactionFactory (best with connection pool in a non-managed environment) net.sf.hibernate.transaction.JTATransactionFactory (correct strategy in CMT where connections are enlisted with JTA)
ADVANCED CONFIGURATION SETTINGS
hibernate.show_sql enables logging of all generated SQL to the console. hibernate.cfg.xml configuration file
<hibernate-configuration> <session-factory name="java:/hibernate/HibernateFactory"> <property name="show_sql">true</property> <property name="connection.datasource"> java:/comp/env/jdbc/AuctionDB </property> <property name="dialect"> net.sf.hibernate.dialect.PostgreSQLDialect </property> <property name="transaction.manager_lookup_class"> net.sf.hibernate.transaction.JBossTransactionManagerLookup </property> <mapping resource="auction/Item.hbm.xml"/> <mapping resource="auction/Category.hbm.xml"/> <mapping resource="auction/Bid.hbm.xml"/> </session-factory> </hibernate-configuration>
Use different sets of mapping files and switch them programmatically
SessionFactory sessions = new Configuration() .configure("/hibernate-config/auction.cfg.xml") .buildSessionFactory();
The Java Naming and Directory Interface (JNDI) API allows objects to be stored to and retrieved from a hierarchical structure (directory tree). JNDI implements the Registry pattern. Infrastructural objects (transaction contexts, datasources), configuration settings (environment settings,
user registries), and even application objects (EJB references, object factories) may all be bound to JNDI.
The SessionFactory will automatically bind itself to JNDI if the property hibernate. session_factory_name is set to the name of the directory node. If your runtime environment doesn’t provide a default JNDI context (or if the default JNDI implementation doesn’t support instances of Referenceable), you need to specify a JNDI initial context using the properties hibernate.jndi.url and hibernate. jndi.class.
LOGGING
Hibernate (and many other ORM implementations) executes SQL statements asynchronously. Instead, the SQL statements are usually issued at the end of a transaction. This behavior is called write-behind, as we mentioned earlier.
JMX
JMX is about the management of systems components
The JMX specification defines the following components:
¦ The JMX MBean—A reusable component (usually infrastructural) that exposes an interface for management (administration)
¦ The JMX container—Mediates generic access (local or remote) to the MBean ¦ The (usually generic) JMX client—May be used to administer any MBean via the JMX container
DOMAIN MODEL
The domain model implementation is such an important piece of code that it shouldn’t depend on other Java APIs.
You shouldn’t put code that addresses these
cross-cutting concerns in the classes that implement the domain model. When these concerns start to appear in the domain model classes, we call this an example of leakage of concerns.
Hibernate is commonly used together with the well-known session façade J2EE pattern.
TRANSPARENT AND AUTOMATED PERSISTENCE
In a system with transparent persistence, objects aren’t aware of the underlying data store; they need not even be aware that they are being persisted or
retrieved. Persistence concerns are externalized to a generic persistence manager interface —in the case of Hibernate, the Session and Query interfaces.
Persistent classes may be reused outside the context of persistence, in unit tests or in the user interface (UI) tier, for example.
Hibernate, imposes some requirements on the persistent classes.
Hibernate requires that collection-valued properties be typed to an interface such as java.util.Set or java.util.List and not to an actual implementation such as java.util.HashSet
MAPPING CLASS INHERITANCE
¦ Table per concrete class—Discard polymorphism and inheritance relationships completely from the relational model
¦ Table per class hierarchy—Enable polymorphism by denormalizing the relational model and using a type discriminator column to hold type information
(foreign key) relationships
POJOS
Hibernate doesn’t require that persistent classes implement Serializable. constructor, Hibernate
requires a constructor with no arguments for every persistent class.
public class Category implements Serializable { private String name;
private Category parentCategory;
private Set childCategories = new HashSet(); public Category() { }
... }
Category aParent = new Category(); Category aChild = new Category(); aChild.setParentCategory(aParent); aParent.getChildCategories().add(aChild);
It’s a good idea to add a convenience method to the Category class that groups these operations, allowing reuse and helping ensure correctness:
public void addChildCategory(Category childCategory) { if (childCategory == null)
throw new IllegalArgumentException("Null child category!"); if (childCategory.getParentCategory() != null)
childCategory.getParentCategory().getChildCategories() .remove(childCategory);
childCategory.setParentCategory(this); childCategories.add(childCategory); }
In the case of a many-to-many association, both sides are implemented with collection-valued attributes.
Adding logic to accessor methods
Hibernate will later use our accessor methods to populate the state of
an object when loading the object from the database. Sometimes we would prefer that this validation not occur when Hibernate is initializing a newly loaded object. In that case, it might make sense to tell Hibernate to directly access the instance variables (we map the property with access="field" in Hibernate metadata), forcing Hibernate to bypass the setter method and access the instance variable directly.
Another issue to consider is dirty checking. Hibernate automatically detects object state changes in order to synchronize the updated state with the database. It’s usually completely safe to return a different object from the getter method to the object passed by Hibernate to the setter. Hibernate will compare the objects by value—not by object identity—to determine if the property’s persistent state needs to be updated.
METADATA IN HYBERNATE
The metadata format is extremely readable and defines useful default values. When attribute values are missing, Hibernate uses reflection on the mapped class to help determine the defaults.