• No results found

Hibernate

N/A
N/A
Protected

Academic year: 2021

Share "Hibernate"

Copied!
184
0
0

Loading.... (view fulltext now)

Full text

(1)

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 {

(2)

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; }

(3)

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).

(4)

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

(5)

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(){

(6)

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 Documentation

2.

Blog User Type on i-Proving.ca

User 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.

(7)

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.

(8)

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.

(9)

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.

(10)

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} } }

(11)

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)

(12)

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.

(13)

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());

(14)

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:

(15)

<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();

(16)

} } }

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

(17)

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.

(18)

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) {

(19)

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.

(20)

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.

(21)

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.

(22)

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

(23)

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.

(24)

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.

(25)

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.

(26)

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

(27)

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.

(28)

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.

(29)

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">

(30)

<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

(31)

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

(32)

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

(33)

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

(34)

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.

(35)

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

(36)

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

(37)

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

(38)

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();

(39)

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");

(40)

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

(41)

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.

(42)

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());

(43)

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

(44)

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

(45)

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)

(46)

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();

(47)

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

(48)

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

(49)

(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);

(50)

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.

References

Related documents

Cooperative education and work-integrated learning programs are able to provide career clarification by way of student’s engagement in legitimate peripheral

This newly-developed technology has enabled endoscopists to collect real-time in vivo histological images or “virtual biopsies” of the gastrointestinal mucosa

The crises in Yemen, transformation in the Middle East and transitional balance of power demand a re- evaluation of our foreign policy which avoids a zero- sum game between

The Human Fertilisation and Embryology Authority commissioned Dr Sally Adams and Dr Jane Carthey to carry out a study comparing the relative risks of manual double

Scooting forward on seat Stem may be too long so you pull yourself forward as you ride; saddle nose may be tipped down too much Install a shorter stem; level saddle Scooting back

A control transfer is meant to retrieve and send information regarding device configuration and device status in class-specific and vendor-specific devices and is

Top panel: samples from a MC simulation (gray lines), mean computed over these samples (solid blue line) and zero-order PCE coefficient from the SG (dashed red line) and ST

öldinni sem hluta af leit múslíma að sjálfsmynd og bendir á að samsvörun sé milli þess að loka konur frá umheiminum með slæðu og að vernda samfélög