Update to Hibernate 3 Instructions
This is now committed to grouper HEAD. Here are the steps to start using Hibernate3:
1. Sync and get the new jars: hibernate3.2.6, i2micommon, asms, cglib, c3p0, ehcache, backport-util-concurrent. Remove old jars (e.g. hibernate.3.2.5)
2. Change the grouper.properties to use hib3 dao factory
3. Change the grouper.hibernate.properties to use the hib3 ehcache, and hib3 dialect (in this example, mysql, but change this to whatever DB driver type you are using)
#hibernate.dialect = net.sf.hibernate.dialect.MySQLDialect
hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
#hibernate.cache.provider_class = net.sf.hibernate.cache.EhCacheProvider
hibernate.cache.provider_class = org.hibernate.cache.EhCacheProvider
4. You must start using c3p0 database pooling (this is the only one we unit test with grouper with). This means changing the grouper.hibernate.properties (feel free to set the c3p0 pool settings as you see fit. Below is a safe version which should perform fine, but you can tune it to err on the side of performance if you like:
# Use DBCP connection pooling
#hibernate.dbcp.maxActive = 16
#hibernate.dbcp.maxIdle = 16
#hibernate.dbcp.maxWait = -1
#hibernate.dbcp.whenExhaustedAction = 1
# Use c3p0 connection pooling (since dbcp not supported in hibernate anymore)
# http://www.hibernate.org/214.html, http://www.hibernate.org/hib_docs/reference/en/html/session-configuration.html
5. Check your log4j.properties, if you have TRACE log on hibernate, change to ERROR. If you have net.sf.hibernate, might want to change to org.hibernate. Otherwise ignore.
log4j.logger.org.hibernate = ERROR, grouper_error
- Upgrade to the latest version of hibernate, which is now 3.2.5
- Add consistency to how hibernate and db resources are handled
- Add support for transactions
- Blair has already added support for hibernate. When I ran the unit tests with hib3, and half of them failed, but maybe after a little work, many would be fixed
- In order to take advantage of hib3, the properties files needs to be changed regarding: dao.factory, hibernate.cache.provider_class, hibernate.dialect
- Hib2 will be experimental after the switch to hib3, but it is not recommended. It will not support transactions. (GrouperTransaction)
- Any add-ons which use hibernate might need to be tweaked
Hibernate / DB resources
- This goes hand in hand with the transaction support, so let's discuss this too
- Error handling in JDBC and hibernate is repetitive, tedious, and easy to get wrong. There are cases in grouper where the error handling is not done optimally or consistently
- Here is an example
- If there is an exception thrown before the hs.close(), then the session will not be closed, which could leak DB connections or leave resources locked on the database
- If we use inverse of control (like how Spring might do it), then we can handle this centrally. Here is another block we can change:
Here is how it would look with inverse of control
- Note that the session does not need to be opened or closed, it is in a callback, which is provided in an anonymous inner class. So the proper exception handling and resource handling will be done everywhere
- Here is a readonly example:
Here is how the code would look with inverse of control (notice how data is passed in and out of the anonymous block (final params, and a return object)
For simple database actions (mainly single actions), there is a helper framework in HibernateSession to remove the need for inverse of control:
Here is the same block (note, transaction level can be configured, but there is an intelligent default):
Similarly for object based queries, there are helper classes/methods. Here is an example:
This code will look like this:
- I suggest that this is the only way to get a Session object so that we ensure it is done correctly
- The inputs to the Hib3SessionHandler describe the session returned. If it is readonly the performance can be dramatically increased (Penn has seen this, I can measure to make sure Grouper will reap the benefits also)
- The syntax and tricks to anonymous inner classes can be new and different to programmers who have not used them, but I think with proper documentation and examples they are easy to get the hang of (all the Penn Java developers use them now just fine)
Add support for transactions
- Lets take the use case of web services in batch mode, where the caller wants to add a few new groups, and if one fails, they should all fail. This is not currently possible
- To support transactions we can use the inverse of control described above
- We can support four modes: GrouperTransactionType.READONLY_OR_USE_EXISTING, READONLY_NEW, READ_WRITE_OR_USE_EXISTING, READ_WRITE_NEW
- READONLY_OR_USE_EXISTING: even if in the middle of a transaction, create a new read/write autonomous nested transaction. If this block is exited normally it will always commit. If exception is thrown, it will always rollback.
- READONLY_NEW: even if in the middle of a transaction, create a new readonly autonomous nested transaction. Code in this state cannot commit or rollback.
- READ_WRITE_OR_USE_EXISTING: use the current transaction if one exists. If there is a current transaction, it MUST be read/write or there will be an exception. If there isnt a transaction in scope, then create a new read/write one. If you do not commit at the end, and there is a normal return (no exception), then the transaction will be committed if new, and not if reusing an existing one. If there is an exception, and the tx is new, it will be rolledback. If there is an exception and the tx is reused, the tx will not be touched, and the exception will propagate.
- READ_WRITE_NEW: even if in the middle of a transaction, create a new read/write autonomous nested transaction. If this block is exited normally it will always commit. If exception is thrown, it will always rollback.
- Note that you can nest transactions to any level (actually there is a hard stop at 15 since I cant picture actually wanting to nest that deep, and I want to try to detect if the ThreadLocals are acting up)
- When we know the ThreadLocals should be empty, we should clear them. e.g. at the start of an HTTPRequest in web services or UI: HibernateSession.resetAllThreadLocals()
- To code the above use case, it would look something like this (note there is an implicit complete if no exception, and rollback if there is an exception, but you can control this if you like with GrouperTransaction methods):
- In this case it is not readonly (defaults to READ_WRITE_OR_USE_EXISTING, but can be controlled), and a transaction wrapper is passed to the callback (GrouperTransaction). There is a method called "GrouperTransaction.commit()" (takes param which can be only if new or always) which will determine if the transaction originated in this callback, or is from a ThreadLocal from an outer transaction. If it originated here, it will commit. Else not.
- If you can picture the code in the addGroup() method, it will look similar... read/write transaction, with USE_EXISTING, and "commitIfNewTx()" which when called from here will not commit.
- Since we are supporting nested transactions, there can be a ThreadLocal stack of hibernate sessions which can be accessed from anywhere and not need to be passed around to all method signatures.
- All 87 places that use Hib3DAO.getSession() were refactored. It will be tedious but should be low impact and should improve the quality of the code
- The higher impact changes to the code relate to how hibernate is handled:
- Since the Session (from Hibernate) can only deal with one object by key at a time, each time an object is retrieved from hibernate it must be "evicted" from the session. The ByObjectStatic and ByHqlStatic helper methods will do this, and if "Query.list()" for example is called outside of the helpers, then the results must be evicted. The side effect is that no hibernate dirty checking will be available. This is no problem since that is more pain than it is worth
- If a transaction type is a NEW transaction type, then at the end of the HibernateSesssion callback, it will be committed or rolled back (and Session object is discarded, and state is synced with the DB). However, if it is a "USE_EXISTING" transaction type, then at the end of the HibernateSession, the Session (from Hibernate) will start bulking up. So HibernateSession will flush() (write all queries to the wire), and clear() (take all objects in the Session out). This will make the transactions work