Database (or system) transaction boundaries are always necessary. No communication with the database can occur outside of a database transaction (this seems to confuse many developers who are used to the auto-commit mode). Always use clear transaction boundaries, even for read-only operations. Depending on your isolation level and database capabilities this might not be required but there is no downside if you always demarcate transactions explicitly. Certainly, a single database transaction is going to perform better than many small transactions, even for reading data.
一个Hibernate应用程序可以运行在非托管环境中(也就是独立运行的应用程序,简单Web应用程序, 或者Swing图形桌面应用程序),也可以运行在托管的J2EE环境中。在一个非托管环境中,Hibernate 通常自己负责管理数据库连接池。应用程序开发人员必须手工设置事务声明,换句话说,就是手工启
动,提交,或者回滚数据库事务。一个托管的环境通常提供了容器管理事务(CMT),例如事务装配通过可声 明的方式定义在EJB session beans的部署描述符中。可编程式事务声明不再需要,即使是 Session
的同步也可以自动完成。
However, it is often desirable to keep your persistence layer portable between non-managed resource-local environments, and
systems that can rely on JTA but use BMT instead of CMT. In both cases you'd use programmatic transaction demarcation. Hibernate
offers a wrapper API called Transaction
that translates into the native transaction system of your deployment environment. This API is actually optional, but we
strongly encourage its use unless you are in a CMT session bean.
通常情况下,结束 Session
包含了四个不同的阶段:
同步session(flush,刷出到磁盘)
提交事务
关闭session
处理异常
session的同步(flush,刷出)前面已经讨论过了,我们现在进一步考察在托管和非托管环境下的事务声明和异常处理。
如果Hibernat持久层运行在一个非托管环境中,数据库连接通常由Hibernate的简单(即非DataSource)连接池机制 来处理。session/transaction处理方式如下所示:
// Non-managed environment idiom Session sess = factory.openSession(); Transaction tx = null; try { tx = sess.beginTransaction(); // do some work ... tx.commit(); } catch (RuntimeException e) { if (tx != null) tx.rollback(); throw e; // or display error message } finally { sess.close(); }
You don't have to flush()
the Session
explicitly - the call to commit()
automatically triggers the synchronization (depending upon the FlushMode for the session. A call to close()
marks the end of a session. The main implication of close()
is that the JDBC connection will be relinquished by the session. This Java code is portable and runs in both non-managed
and JTA environments.
更加灵活的方案是Hibernate内置的"current session"上下文管理,前文已经讲过:
// Non-managed environment idiom with getCurrentSession() try { factory.getCurrentSession().beginTransaction(); // do some work ... factory.getCurrentSession().getTransaction().commit(); } catch (RuntimeException e) { factory.getCurrentSession().getTransaction().rollback(); throw e; // or display error message }
你很可能从未在一个通常的应用程序的业务代码中见过这样的代码片断:致命的(系统)异常应该总是 在应用程序“顶层”被捕获。换句话说,执行Hibernate调用的代码(在持久层)和处理 RuntimeException
异常的代码(通常只能清理和退出应用程序)应该在不同 的应用程序逻辑层。Hibernate的当前上下文管理可以极大地简化这一设计,你所有的一切就是SessionFactory
。 异常处理将在本章稍后进行讨论。
请注意,你应该选择 org.hibernate.transaction.JDBCTransactionFactory
(这是默认选项),对第二个例子来说,hibernate.current_session_context_class
应该是"thread"
如果你的持久层运行在一个应用服务器中(例如,在EJB session beans的后面),Hibernate获取 的每个数据源连接将自动成为全局JTA事务的一部分。 你可以安装一个独立的JTA实现,使用它而不使用EJB。Hibernate提供了两种策略进行JTA集成。
如果你使用bean管理事务(BMT),可以通过使用Hibernate的 Transaction
API来告诉 应用服务器启动和结束BMT事务。因此,事务管理代码和在非托管环境下是一样的。
// BMT idiom Session sess = factory.openSession(); Transaction tx = null; try { tx = sess.beginTransaction(); // do some work ... tx.commit(); } catch (RuntimeException e) { if (tx != null) tx.rollback(); throw e; // or display error message } finally { sess.close(); }
如果你希望使用与事务绑定的Session
,也就是使用getCurrentSession()
来简化上下文管理,你将不得不直接使用JTA UserTransaction
API。
// BMT idiom with getCurrentSession() try { UserTransaction tx = (UserTransaction)new InitialContext() .lookup("java:comp/UserTransaction"); tx.begin(); // Do some work on Session bound to transaction factory.getCurrentSession().load(...); factory.getCurrentSession().persist(...); tx.commit(); } catch (RuntimeException e) { tx.rollback(); throw e; // or display error message }
With CMT, transaction demarcation is done in session bean deployment descriptors, not programmatically, hence, the code is reduced to:
// CMT idiom Session sess = factory.getCurrentSession(); // do some work ...
在CMT/EJB中甚至会自动rollback,因为假若有未捕获的RuntimeException
从session bean方法中抛出,这就会通知容器把全局事务回滚。这就意味着,在BMT或者CMT中,你根本就不需要使用Hibernate Transaction
API ,你自动得到了绑定到事务的“当前”Session。
Note that you should choose org.hibernate.transaction.JTATransactionFactory
if you use JTA directly (BMT), and org.hibernate.transaction.CMTTransactionFactory
in a CMT session bean, when you configure Hibernate's transaction factory. Remember to also set hibernate.transaction.manager_lookup_class
. Furthermore, make sure that your hibernate.current_session_context_class
is either unset (backwards compatibility), or set to "jta"
.
The getCurrentSession()
operation has one downside in a JTA environment. There is one caveat to the use of after_statement
connection release mode, which is then used by default. Due to a silly limitation of the JTA spec, it is not possible for
Hibernate to automatically clean up any unclosed ScrollableResults
or Iterator
instances returned by scroll()
or iterate()
. You must release the underlying database cursor by calling ScrollableResults.close()
or Hibernate.close(Iterator)
explicitly from a finally
block. (Of course, most applications can easily avoid using scroll()
or iterate()
at all from the JTA or CMT code.)
如果 Session
抛出异常 (包括任何SQLException
), 你应该立即回滚数据库事务,调用 Session.close()
,丢弃该 Session
实例。Session
的某些方法可能会导致session 处于不一致的状态。所有由Hibernate抛出的异常都视为不可以恢复的。确保在 finally
代码块中调用close()
方法,以关闭掉 Session
。
HibernateException
是一个非检查期异常(这不同于Hibernate老的版本), 它封装了Hibernate持久层可能出现的大多数错误。我们的观点是,不应该强迫应用程序开发人员 在底层捕获无法恢复的异常。在大多数软件系统中,非检查期异常和致命异常都是在相应方法调用
的堆栈的顶层被处理的(也就是说,在软件上面的逻辑层),并且提供一个错误信息给应用软件的用户 (或者采取其他某些相应的操作)。请注意,Hibernate也有可能抛出其他并不属于 HibernateException
的非检查期异常。这些异常同样也是无法恢复的,应该 采取某些相应的操作去处理。
Hibernate wraps SQLException
s thrown while interacting with the database in a JDBCException
. In fact, Hibernate will attempt to convert the exception into a more meaningful subclass of JDBCException
. The underlying SQLException
is always available via JDBCException.getCause()
. Hibernate converts the SQLException
into an appropriate JDBCException
subclass using the SQLExceptionConverter
attached to the SessionFactory
. By default, the SQLExceptionConverter
is defined by the configured dialect; however, it is also possible to plug in a custom implementation (see the javadocs for
the SQLExceptionConverterFactory
class for details). The standard JDBCException
subtypes are:
JDBCConnectionException
- 指明底层的JDBC通讯出现错误
SQLGrammarException
- 指明发送的SQL语句的语法或者格式错误
ConstraintViolationException
- 指明某种类型的约束违例错误
LockAcquisitionException
- 指明了在执行请求操作时,获取 所需的锁级别时出现的错误。
GenericJDBCException
- 不属于任何其他种类的原生异常
One extremely important feature provided by a managed environment like EJB that is never provided for non-managed code is
transaction timeout. Transaction timeouts ensure that no misbehaving transaction can indefinitely tie up resources while returning
no response to the user. Outside a managed (JTA) environment, Hibernate cannot fully provide this functionality. However,
Hibernate can at least control data access operations, ensuring that database level deadlocks and queries with huge result
sets are limited by a defined timeout. In a managed environment, Hibernate can delegate transaction timeout to JTA. This functionality
is abstracted by the Hibernate Transaction
object.
Session sess = factory.openSession(); try { //set transaction timeout to 3 seconds sess.getTransaction().setTimeout(3); sess.getTransaction().begin(); // do some work ... sess.getTransaction().commit() } catch (RuntimeException e) { sess.getTransaction().rollback(); throw e; // or display error message } finally { sess.close(); }
注意setTimeout()
不应该在CMT bean中调用,此时事务超时值应该是被声明式定义的。