目的使用HibernateTemplate执行execute(new HibernateCallback())方法从HibernateCallback中得到session在此session中做多个操作并希望这些操作位于同一个事务中
如果你这样写()
public static void main(String ss[]) {
CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException SQLException {
// 保存stu
Student stu = new Student();
stusetName(aaaa);// 在数据库中name字段不允许为null
sessionsave(stu);
sessionflush();//实际上如果不是程序员手痒来调用这个flush()HibernateTemplate中session的事务处理还是很方便的
Student stu = new Student();
sessionsave(stu);// 没有设置name字段预期会报出例外
sessionflush();
return null;
}
});
}
你期望spring在执行完execute回调后在关闭session的时候提交事务想法是很好的但spring并不会这么做让我们来看看在Hibernate的源代码中sessionbeginTransation()做了什么事看如下代码()
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
logwarn( Transaction started on nonroot session );
}
Transaction result = getTransaction();
resultbegin();
return result;
}
这个方法中的result是一个orghibernatetransactionJDBCTransaction实例而方法中的getTransaction()方法源代码为()
public Transaction getTransaction() throws HibernateException {
if (hibernateTransaction==null) {
logerror(ownergetFactory()getSettings()
getTransactionFactory()getClass());
hibernateTransaction = ownergetFactory()getSettings()
getTransactionFactory()
createTransaction( this owner );
}
return hibernateTransaction;
}
再次追蹤ownergetFactory()getSettings() getTransactionFactory()的createTransaction()方法源代码如下()
public Transaction createTransaction(JDBCContext jdbcContext Context transactionContext)
throws HibernateException {
return new JDBCTransaction( jdbcContext transactionContext );
}
它返回了一个JDBCTransaction没什么特别的
在代码中执行了resultbegin()其实也就是JDBCTransaction实例的begin()方法来看看()
public void begin() throws HibernateException {
if (begun) {
return;
}
if (commitFailed) {
throw new TransactionException(cannot restart transaction after failed commit);
}
logdebug(begin);
try {
toggleAutoCommit = nnection()getAutoCommit();
if (logisDebugEnabled()) {
logdebug(current autocommit status: + toggleAutoCommit);
}
if (toggleAutoCommit) {
logdebug(disabling autocommit);
nnection()setAutoCommit(false);//把自动提交设为了false
}
} catch (SQLException e) {
logerror(JDBC begin failed e);
throw new TransactionException(JDBC begin failed: e);
}
callback = jdbcContextregisterCallbackIfNecessary();
begun = true;
committed = false;
rolledBack = false;
if (timeout > ) {
jdbcContextgetConnectionManager()getBatcher()setTransactionTimeout(timeout);
}
jdbcContextafterTransactionBegin(this);
}
在直接使用Hibernate时要在事务结束的时候写上一句mit()这个commit()的源码为
public void commit() throws HibernateException {
if (!begun) {
throw new TransactionException(Transaction not successfully started);
}
logdebug(commit);
if (!transactionContextisFlushModeNever() && callback) {
transactionContextmanagedFlush(); // if an exception occurs during
// flush user must call
// rollback()
}
notifyLocalSynchsBeforeTransactionCompletion();
if (callback) {
jdbcContextbeforeTransactionCompletion(this);
}
try {
commitAndResetAutoCommit();//重点代码它的作用是提交事务并把connection的autocommit属性恢复为true
logdebug(committed JDBC Connection);
committed = true;
if (callback) {
jdbcContextafterTransactionCompletion(true this);
}
notifyLocalSynchsAfterTransactionCompletion(StatusSTATUS_COMMITTED);
} catch (SQLException e) {
logerror(JDBC commit failed e);
commitFailed = true;
if (callback) {
jdbcContextafterTransactionCompletion(false this);
}
notifyLocalSynchsAfterTransactionCompletion(StatusSTATUS_UNKNOWN);
throw new TransactionException(JDBC commit failed e);
} finally {
closeIfRequired();
}
}
上面代码中commitAndResetAutoCommit()方法的源码如下
private void commitAndResetAutoCommit() throws SQLException {
try {
nnection(mit();//这段不用说也能理解了
} finally {
toggleAutoCommit();//这段的作用是恢复connection的autocommit属性为true
}
}
上述代码的toggleAutoCommit()源代码如下
private void toggleAutoCommit() {
try {
if (toggleAutoCommit) {
logdebug(reenabling autocommit);
nnection()setAutoCommit(true);//这行代码的意义很明白了吧
}
} catch (Exception sqle) {
logerror(Could not toggle autocommit sqle);
}
}
因此如果你是直接使用hibernate并手动管理它的session并手动开启事务关闭事务的话完全可以保证你的事务(好像完全是废话)
但是如果你用的是HibernateTemplate如同源代码一样则不要指望spring在关闭session的时候为你提交事务(罪魁祸首就是在代码中调用了sessionflush())因为在使用代码时spring中得到session的方式如下Session session = (entityInterceptor != null ? sessionFactoryopenSession(entityInterceptor) sessionFactoryopenSession())简单地说它就是得到了一个session而没有对connection的autocommit()作任何操作spring管理范围内的session所持有的connection是autocommit=true的spring借助这个属性在它关闭session时提交数据库事务因此如果你在源代码中加上一句话
public static void main(String ss[]) {
CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException SQLException {
(nnection()getAutoCommit());//打印一下事务提交方式
// 保存stu
Student stu = new Student();
stusetName(aaaa);// 在数据库中name字段不允许为null
sessionsave(stu);
sessionflush();
Student stu = new Student();
sessionsave(stu);// 没有设置name字段预期会报出例外
sessionflush();
return null;
}
});
}
运行后它打出的结果是true也就是说虽然保存stu时会报出例外但如果commit属性为true则每一个到达数据库的sql语句会立即被提交换句话说在调用完sessionsave(stu)后调用sessionflush()会发送sql语句到数据库再根据commit属性为true则保存stu的操作已经被持久到数据库了尽管后面的一条insert语句出了问题
因此如果你想在HibernateCallback中使用session的事务需要如下写
public static void main(String ss[]) {
CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException SQLException {
nnection()setAutoCommit(false);
//保存stu
Student stu=new Student();
stusetName(aaaa);//在数据库中name字段不允许为null
sessionsave(stu);
sessionflush();
Student stu = new Student();
sessionsave(stu);//没有设置name字段预期会报出例外
sessionflush();
nnection(mit();
//至于session的关闭就不用我们操心了
return null;
}
});
}
运行上述代码没问题了至此可能有些读者早就对代码不满意了为什么每次save()以后要调用flush()?这是有原因的下面我们来看看把sessionflush()去掉后会出什么问题改掉后的代码如下
public static void main(String ss[]) {
CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException SQLException {
nnection()setAutoCommit(false);
// 保存stu
Student stu = new Student();
stusetName(aaaa);// 在数据库中name字段不允许为null
sessionsave(stu);
// sessionflush();
Student stu = new Student();
sessionsave(stu);// 没有设置name字段预期会报出例外
// sessionflush();
nnection(mit();
return null;
}
});
}
运行上述代码后台报数据库的not null错误这个是合理的打开数据库没有发现新增记录这个也是合理的你可能会说由于事务失败数据库当然不可能会有任何新增记录好吧我们再把代码改一下去除not null的错误以确保它能正常运行代码如下
public static void main(String ss[]) {
CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException SQLException {
nnection()setAutoCommit(false);
// 保存stu
Student stu = new Student();
stusetName(aaaa);// 在数据库中name字段不允许为null
sessionsave(stu);
// sessionflush();
Student stu = new Student();
stusetName(asdfasdf);//好了这个字段设过值不会再报not null错误了
sessionsave(stu);
// sessionflush();
nnection(mit();
return null;
}
});
}
至此再运行上述代码出现了一个奇怪的问题虽然控制台把insert语句打出来了但是数据库没有出现任何新的记录
究其原因有二
一 nnection()commit()确实导致数据库事务提交了但是此刻session并没有向数据库发送任何语句
二 在spring后继的flushIfNecessary()和closeSessionOrRegisterDeferredClose()方法中第一个方法向数据库发送sql语句第二个方法关闭session同时关闭connection然后问题在于connection已经在程序中被手动设置为auttocommit=false了因此在关闭数据库时也不会提交事务
解决这个问题很容易在程序中手动调用sessionflush()就可以了如下代码
public static void main(String ss[]) {
CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException SQLException {
nnection()setAutoCommit(false);
//保存stu
Student stu=new Student();
stusetName(aaaa);//在数据库中name字段不允许为null
sessionsave(stu);
Student stu = new Student();
sessionsave(stu);//没有设置name字段预期会报出例外
sessionflush();//向数据库发送sql
nnection(mit();
return null;
}
});
}
运行上述代码打开数据库查看没有新增任何记录在代码中新加一行stusetName(aaa)再次运行代码发现数据库表中多了两条记录事务操作成功
至此虽然操作成功但事情还没有结束这是因为spring在调用doInHibernate()的后继的步骤中还要进行flushIfNecessary()操作这个操作其实最后调用的还是sessionflush()因为在程序中已经手动调用过sessionflush()所以由spring调用的sessionflush()并不会对数据库发送sql(因为髒数据比对的原因)虽然不会对结果有什么影响但是多调了一次flush()还是会对性能多少有些影响能不能控制让spring不调用sessionflush()呢?可以的只要加上一句代码如下所示
public static void main(String ss[]) {
CtxUtilgetBaseManager()getHibernateTemplate()setFlushMode();//也就是FLUSH_NEVER
CtxUtilgetBaseManager()getHibernateTemplate()execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException SQLException {
nnection()setAutoCommit(false);
//保存stu
Student stu=new Student();
stusetName(aaaa);//在数据库中name字段不允许为null
sessionsave(stu);
Student stu = new Student();
stusetName(sdf);
sessionsave(stu);//没有设置name字段预期会报出例外
sessionflush();
nnection(mit();
return null;
}
});
}
通过设置HibernateTemplate的flushMode=FLUSH_NEVER来通知spring不进行sessionflush()的调用则spring的flushIfNecessary()将不进行任何操作它的flushIfNecessary()源代码如下
protected void flushIfNecessary(Session session boolean existingTransaction) throws HibernateException {
if (getFlushMode() == FLUSH_EAGER || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) {
loggerdebug(Eagerly flushing Hibernate session);
sessionflush();
}
}
至此代码中的main()终于修改完毕但事实上这样的操作无疑是比较麻烦的因此如果在spring中想利用session进行事务操作时最好还是用TransactionTemplate(编程式事务)或是声明式事务比较方便一些
本例通过这么一个虽然简单但又绕来绕去的例子主要是说明hibernate事务的一些内在特性以及HibernateTemplate中如何处理session和事务的开关让读者对HibernateTemplate的源代码处理细节有一些了解希望能给读者有抛砖引玉的作用