HibernateTemplate中HibernateCallback的事务
目的:使用HibernateTemplate执行execute(new HibernateCallback())方法,从HibernateCallback中得到session,在此session中做多个操作,并希望这些操作位于同一个事务中。
如果你这样写(1):
在代码2中,执行了result.begin(),其实也就是JDBCTransaction实例的begin()方法,来看看(5):
在直接使用Hibernate时,要在事务结束的时候,写上一句:tx.commit(),这个commit()的源码为:
上面代码中,commitAndResetAutoCommit()方法的源码如下:
上述代码的toggleAutoCommit()源代码如下:
但是,如果你用的是HibernateTemplate,如同源代码1一样,则不要指望spring在关闭session的时候为你提交事务(罪魁祸首就是在代码1中调用了session.flush())。因为在使用代码1时,spring中得到session的方式如下:
因此,如果你想在HibernateCallback中使用session的事务,需要如下写:
如果你这样写(1):
public
static
void
main(String ss[])
{
CtxUtil.getBaseManager().getHibernateTemplate().execute( new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
// 保存stu1
Student stu1 = new Student();
stu1.setName( " aaaa " ); // 在数据库中,name字段不允许为null
session.save(stu1);
session.flush();//实际上,如果不是程序员"手痒"来调用这个flush(),HibernateTemplate中session的事务处理还是很方便的
Student stu2 = new Student();
session.save(stu2); // 没有设置name字段,预期会报出例外
session.flush();
return null ;
}
} );
}
你期望spring在执行完execute回调后,在关闭session的时候提交事务,想法是很好的,但spring并不会这么做.让我们来看看在Hibernate的源代码中,session.beginTransation()做了什么事。看如下代码(2):
CtxUtil.getBaseManager().getHibernateTemplate().execute( new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
// 保存stu1
Student stu1 = new Student();
stu1.setName( " aaaa " ); // 在数据库中,name字段不允许为null
session.save(stu1);
session.flush();//实际上,如果不是程序员"手痒"来调用这个flush(),HibernateTemplate中session的事务处理还是很方便的
Student stu2 = new Student();
session.save(stu2); // 没有设置name字段,预期会报出例外
session.flush();
return null ;
}
} );
}
public
Transaction beginTransaction()
throws
HibernateException
{
errorIfClosed();
if ( rootSession != null ) {
// todo : should seriously consider not allowing a txn to begin from a child session
// can always route the request to the root session
log.warn( " Transaction started on non-root session " );
}
Transaction result = getTransaction();
result.begin();
return result;
}
这个方法中的result是一个org.hibernate.transaction.JDBCTransaction实例,而方法中的getTransaction()方法源代码为(3):
errorIfClosed();
if ( rootSession != null ) {
// todo : should seriously consider not allowing a txn to begin from a child session
// can always route the request to the root session
log.warn( " Transaction started on non-root session " );
}
Transaction result = getTransaction();
result.begin();
return result;
}
public
Transaction getTransaction()
throws
HibernateException
{
if (hibernateTransaction == null ) {
log.error(owner.getFactory().getSettings()
.getTransactionFactory().getClass());
hibernateTransaction = owner.getFactory().getSettings()
.getTransactionFactory()
.createTransaction( this , owner );
}
return hibernateTransaction;
}
再次追踪,owner.getFactory().getSettings() .getTransactionFactory()的createTransaction()方法源代码如下(4):
if (hibernateTransaction == null ) {
log.error(owner.getFactory().getSettings()
.getTransactionFactory().getClass());
hibernateTransaction = owner.getFactory().getSettings()
.getTransactionFactory()
.createTransaction( this , owner );
}
return hibernateTransaction;
}
public
Transaction createTransaction(JDBCContext jdbcContext, Context transactionContext)
throws HibernateException {
return new JDBCTransaction( jdbcContext, transactionContext );
}
它返回了一个JDBCTransaction,没什么特别的。
throws HibernateException {
return new JDBCTransaction( jdbcContext, transactionContext );
}
在代码2中,执行了result.begin(),其实也就是JDBCTransaction实例的begin()方法,来看看(5):
public
void
begin()
throws
HibernateException
{
if (begun) {
return ;
}
if (commitFailed) {
throw new TransactionException( " cannot re-start transaction after failed commit " );
}
log.debug( " begin " );
try {
toggleAutoCommit = jdbcContext.connection().getAutoCommit();
if (log.isDebugEnabled()) {
log.debug( " current autocommit status: " + toggleAutoCommit);
}
if (toggleAutoCommit) {
log.debug( " disabling autocommit " );
jdbcContext.connection().setAutoCommit( false ); // 把自动提交设为了false
}
} catch (SQLException e) {
log.error( " JDBC begin failed " , e);
throw new TransactionException( " JDBC begin failed: " , e);
}
callback = jdbcContext.registerCallbackIfNecessary();
begun = true ;
committed = false ;
rolledBack = false ;
if (timeout > 0 ) {
jdbcContext.getConnectionManager().getBatcher().setTransactionTimeout(timeout);
}
jdbcContext.afterTransactionBegin( this );
}
if (begun) {
return ;
}
if (commitFailed) {
throw new TransactionException( " cannot re-start transaction after failed commit " );
}
log.debug( " begin " );
try {
toggleAutoCommit = jdbcContext.connection().getAutoCommit();
if (log.isDebugEnabled()) {
log.debug( " current autocommit status: " + toggleAutoCommit);
}
if (toggleAutoCommit) {
log.debug( " disabling autocommit " );
jdbcContext.connection().setAutoCommit( false ); // 把自动提交设为了false
}
} catch (SQLException e) {
log.error( " JDBC begin failed " , e);
throw new TransactionException( " JDBC begin failed: " , e);
}
callback = jdbcContext.registerCallbackIfNecessary();
begun = true ;
committed = false ;
rolledBack = false ;
if (timeout > 0 ) {
jdbcContext.getConnectionManager().getBatcher().setTransactionTimeout(timeout);
}
jdbcContext.afterTransactionBegin( this );
}
在直接使用Hibernate时,要在事务结束的时候,写上一句:tx.commit(),这个commit()的源码为:
public
void
commit()
throws
HibernateException
{
if ( ! begun) {
throw new TransactionException( " Transaction not successfully started " );
}
log.debug( " commit " );
if ( ! transactionContext.isFlushModeNever() && callback) {
transactionContext.managedFlush(); // if an exception occurs during
// flush, user must call
// rollback()
}
notifyLocalSynchsBeforeTransactionCompletion();
if (callback) {
jdbcContext.beforeTransactionCompletion( this );
}
try {
commitAndResetAutoCommit();//重点代码,它的作用是提交事务,并把connection的autocommit属性恢复为true
log.debug( " committed JDBC Connection " );
committed = true ;
if (callback) {
jdbcContext.afterTransactionCompletion( true , this );
}
notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_COMMITTED);
} catch (SQLException e) {
log.error( " JDBC commit failed " , e);
commitFailed = true ;
if (callback) {
jdbcContext.afterTransactionCompletion( false , this );
}
notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_UNKNOWN);
throw new TransactionException( " JDBC commit failed " , e);
} finally {
closeIfRequired();
}
}
if ( ! begun) {
throw new TransactionException( " Transaction not successfully started " );
}
log.debug( " commit " );
if ( ! transactionContext.isFlushModeNever() && callback) {
transactionContext.managedFlush(); // if an exception occurs during
// flush, user must call
// rollback()
}
notifyLocalSynchsBeforeTransactionCompletion();
if (callback) {
jdbcContext.beforeTransactionCompletion( this );
}
try {
commitAndResetAutoCommit();//重点代码,它的作用是提交事务,并把connection的autocommit属性恢复为true
log.debug( " committed JDBC Connection " );
committed = true ;
if (callback) {
jdbcContext.afterTransactionCompletion( true , this );
}
notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_COMMITTED);
} catch (SQLException e) {
log.error( " JDBC commit failed " , e);
commitFailed = true ;
if (callback) {
jdbcContext.afterTransactionCompletion( false , this );
}
notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_UNKNOWN);
throw new TransactionException( " JDBC commit failed " , e);
} finally {
closeIfRequired();
}
}
上面代码中,commitAndResetAutoCommit()方法的源码如下:
private
void
commitAndResetAutoCommit()
throws
SQLException
{
try {
jdbcContext.connection().commit();//这段不用说也能理解了
} finally {
toggleAutoCommit();//这段的作用是恢复connection的autocommit属性为true
}
}
try {
jdbcContext.connection().commit();//这段不用说也能理解了
} finally {
toggleAutoCommit();//这段的作用是恢复connection的autocommit属性为true
}
}
上述代码的toggleAutoCommit()源代码如下:
private
void
toggleAutoCommit()
{
try {
if (toggleAutoCommit) {
log.debug( " re-enabling autocommit " );
jdbcContext.connection().setAutoCommit( true );//这行代码的意义很明白了吧
}
} catch (Exception sqle) {
log.error( " Could not toggle autocommit " , sqle);
}
}
因此,如果你是直接使用hibernate,并手动管理它的session,并手动开启事务关闭事务的话,完全可以保证你的事务(好像完全是废话).
try {
if (toggleAutoCommit) {
log.debug( " re-enabling autocommit " );
jdbcContext.connection().setAutoCommit( true );//这行代码的意义很明白了吧
}
} catch (Exception sqle) {
log.error( " Could not toggle autocommit " , sqle);
}
}
但是,如果你用的是HibernateTemplate,如同源代码1一样,则不要指望spring在关闭session的时候为你提交事务(罪魁祸首就是在代码1中调用了session.flush())。因为在使用代码1时,spring中得到session的方式如下:
Session session
=
(entityInterceptor
!=
null
?
sessionFactory.openSession(entityInterceptor) : sessionFactory
.openSession());
简单地说它就是得到了一个session,而没有对connection的autocommit()作任何操作,spring管理范围内的session所持有的connection是autocommit=true的,spring借助这个属性,在它关闭session时,提交数据库事务。,因此如果你在源代码1中加上一句话:
.openSession());
public
static
void
main(String ss[])
{
CtxUtil.getBaseManager().getHibernateTemplate().execute( new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
log.info(session.connection().getAutoCommit()); // 打印一下事务提交方式
// 保存stu1
Student stu1 = new Student();
stu1.setName( " aaaa " ); // 在数据库中,name字段不允许为null
session.save(stu1);
session.flush();
Student stu2 = new Student();
session.save(stu2); // 没有设置name字段,预期会报出例外
session.flush();
return null ;
}
} );
}
运行后,它打出的结果是true,也就是说,虽然保存stu2时会报出例外,但如果commit属性为true,则每一个到达数据库的sql语句会立即被提交。换句话说,在调用完session.save(stu1)后,调用session.flush(),会发送sql语句到数据库,再根据commit属性为true,则保存stu1的操作已经被持久到数据库了,尽管后面的一条insert语句出了问题。
CtxUtil.getBaseManager().getHibernateTemplate().execute( new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
log.info(session.connection().getAutoCommit()); // 打印一下事务提交方式
// 保存stu1
Student stu1 = new Student();
stu1.setName( " aaaa " ); // 在数据库中,name字段不允许为null
session.save(stu1);
session.flush();
Student stu2 = new Student();
session.save(stu2); // 没有设置name字段,预期会报出例外
session.flush();
return null ;
}
} );
}
因此,如果你想在HibernateCallback中使用session的事务,需要如下写:
public
static
void
main(String ss[])
{
CtxUtil.getBaseManager().getHibernateTemplate().execute( new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
session.connection().setAutoCommit( false );
// 保存stu1
Student stu1 = new Student();
stu1.setName( " aaaa " ); // 在数据库中,name字段不允许为null
session.save(stu1);
session.flush();
Student stu2 = new Student();
session.save(stu2); // 没有设置name字段,预期会报出例外
session.flush();
session.connection().commit();
// 至于session的关闭就不用我们操心了
return null ;
}
} );
}
运行上述代码,没问题了。至此,可能有些读者早就对代码1不满意了:为什么每次save()以后要调用flush()?这是有原因的。下面我们来看看把session.flush()去掉后会出什么问题。改掉后的代码如下:
CtxUtil.getBaseManager().getHibernateTemplate().execute( new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
session.connection().setAutoCommit( false );
// 保存stu1
Student stu1 = new Student();
stu1.setName( " aaaa " ); // 在数据库中,name字段不允许为null
session.save(stu1);
session.flush();
Student stu2 = new Student();
session.save(stu2); // 没有设置name字段,预期会报出例外
session.flush();
session.connection().commit();
// 至于session的关闭就不用我们操心了
return null ;
}
} );
}
public
static
void
main(String ss[])
(转载