spring 中 Hibernate 事务和JDBC事务嵌套问题

http://www.iteye.com/topic/11063?page=2

---mixed ORM and JDBC usage is a feature of Spring DAO

这是Rod Johnson在Spring官方上说的....以前我的理解是可以在同一个事务里
混合JDBC和ORM 编程(不考虑JTA).但我发现我错了..

比如有这样一个业务方法

void doBusiness(){
  doAction1(); 使用JdbcTemplete
  doAction2(); 使用JdoDaoSupport或HBDaoSupport
}

但这样缺是行不通的,Spring只是能拿到HB和JDBC的同一个数据源(DataSource),却拿不到Hibernate和Jdbc的同一个底层Connection的连接
导致这2个方法将分别完成,并不能处在同一个事务,而且我运行的后还出现连接未释放的情况.(他们单独拿出来当然是正确的)...

难道rod的话的意思只是可以在不同的方法里,使jdbc和ORM拿到同一个数据源,mixed JDBC and ORM ?????郁闷中...]

 

===============

楼主以及二楼的朋友的论断错误倒也罢了,那种肯定的,结论性总结的态度很容易误导初学者的学习和理解。

提个小小的建议:下结论前要进行充分的考证,我们技术工作者尤其需要严谨的态度。需要用证据来说话。

jdo dao和jdbc dao能否在同一个事务里这我不太清楚。因为我没用过jdo daosupport。
但是jdbc daosupport和hibernate daosupport却能被wrap到同一个事务里。成立需要几点条件:
1、使用同一个datasource
2、事务交由hibernateTransactionManager管理
3、相关dao以及service需要使用runtime exception体系,使用spring提供的exception可以,自己封装设计的runtime exception体系也行。

与此相关的事务代码片断在HibernateTransactionManager类中。最好可以把DatasourceTransactionManager和HibernateTransactionManager对比来看。
在此贴上几个源码片断,多余的我就不解释了。相信大家一看自明。
HibernateTransactionManager#doGetTransaction

Java代码 复制代码  收藏代码
  1. HibernateTransactionObject txObject = new HibernateTransactionObject();;   
  2. txObject.setSavepointAllowed(isNestedTransactionAllowed(););;   
  3.   
  4. if (TransactionSynchronizationManager.hasResource(getSessionFactory(););); {   
  5.     SessionHolder sessionHolder =   
  6.             (SessionHolder); TransactionSynchronizationManager.getResource(getSessionFactory(););;   
  7.     if (logger.isDebugEnabled();); {   
  8.         logger.debug("Found thread-bound session [" + sessionHolder.getSession(); +   
  9.                 "] for Hibernate transaction");;   
  10.     }   
  11.     txObject.setSessionHolder(sessionHolder, false);;   
  12.     if (getDataSource(); != null); {   
  13.         ConnectionHolder conHolder = (ConnectionHolder);   
  14.                 TransactionSynchronizationManager.getResource(getDataSource(););;   
  15.         txObject.setConnectionHolder(conHolder);;   
  16.     }   
  17. }   
  18.   
  19. return txObject;  
		HibernateTransactionObject txObject = new HibernateTransactionObject();;
		txObject.setSavepointAllowed(isNestedTransactionAllowed(););;

		if (TransactionSynchronizationManager.hasResource(getSessionFactory(););); {
			SessionHolder sessionHolder =
					(SessionHolder); TransactionSynchronizationManager.getResource(getSessionFactory(););;
			if (logger.isDebugEnabled();); {
				logger.debug("Found thread-bound session [" + sessionHolder.getSession(); +
						"] for Hibernate transaction");;
			}
			txObject.setSessionHolder(sessionHolder, false);;
			if (getDataSource(); != null); {
				ConnectionHolder conHolder = (ConnectionHolder);
						TransactionSynchronizationManager.getResource(getDataSource(););;
				txObject.setConnectionHolder(conHolder);;
			}
		}

		return txObject;


由此可以看出hibernateTransactionManager可以检测到绑定在当前线程上的connection

HibernateTransactionManager#doBegin

Java代码 复制代码  收藏代码
  1. Connection con = session.connection();;   
  2. Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);;   
  3. txObject.setPreviousIsolationLevel(previousIsolationLevel);;   
  4.   
  5. ...........   
  6.   
  7. if (getDataSource(); != null); {   
  8.     ConnectionHolder conHolder = new ConnectionHolder(con);;   
  9.     if (definition.getTimeout(); != TransactionDefinition.TIMEOUT_DEFAULT); {   
  10.         conHolder.setTimeoutInSeconds(definition.getTimeout(););;   
  11.     }   
  12.     if (logger.isDebugEnabled();); {   
  13.         logger.debug("Exposing Hibernate transaction as JDBC transaction [" +   
  14.                 conHolder.getConnection(); + "]");;   
  15.     }   
  16.     TransactionSynchronizationManager.bindResource(getDataSource();, conHolder);;   
  17.     txObject.setConnectionHolder(conHolder);;   
  18. }   
  19. // bind the session holder to the thread   
  20. if (txObject.isNewSessionHolder();); {   
  21.     TransactionSynchronizationManager.bindResource(getSessionFactory();, txObject.getSessionHolder(););;   
  22. }  
			Connection con = session.connection();;
			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);;
			txObject.setPreviousIsolationLevel(previousIsolationLevel);;

..............

			if (getDataSource(); != null); {
				ConnectionHolder conHolder = new ConnectionHolder(con);;
				if (definition.getTimeout(); != TransactionDefinition.TIMEOUT_DEFAULT); {
					conHolder.setTimeoutInSeconds(definition.getTimeout(););;
				}
				if (logger.isDebugEnabled();); {
					logger.debug("Exposing Hibernate transaction as JDBC transaction [" +
							conHolder.getConnection(); + "]");;
				}
				TransactionSynchronizationManager.bindResource(getDataSource();, conHolder);;
				txObject.setConnectionHolder(conHolder);;
			}
			// bind the session holder to the thread
			if (txObject.isNewSessionHolder();); {
				TransactionSynchronizationManager.bindResource(getSessionFactory();, txObject.getSessionHolder(););;
			}


由此可以看出,在真正启动一个事务时,hbTxnManager会先把connection绑定到当前线程,再绑定session到当前线程,由TransactionSynchronizationManager统一管理。并且上面提到的connection是从session中取得的,也就是说,无论是jdbc dao还是hibernate dao本质上使用的是同一个database connection

因此得出结论:HibernateTransactionManager实际上是可以同时管理由JdbcTemplate或JdbcDaoSupport实现的dao以及HibernateTemplate或HibernateDaoSupport实现的事务的。


Rod Johnson的话:

引用
It is possible--and sometimes useful--to have coordinated transactions for both. Your JDBC transactions will be managed by the HibernateTransactionManager if you work with the same JDBC DataSource in the same transaction. That is, create the SessionFactory using Spring's SessionFactoryBean using the same DataSource that your JdbcTemplates use.

The only issue to watch, of course, is that you may be invalidating your Hibernate cache by JDBC changes. Generally I find it best to use JDBC to update only tables that don't have Hibernate mappings.



Juergen Hoeller的话:

引用
As Rod said, simply keep using HibernateTransactionManager, which auto-detects the DataSource used by Hibernate and seamlessly exposes Hibernate transactions as JDBC transactions for that DataSource. JDBC code that accesses the same DataSource via Spring will automatically participate in such transactions.

Note that you must specify the DataSource for Hibernate via LocalSessionFactoryBean's "dataSource" property to allow HibernateTransactionManager to auto-detect it. Alternatively, you can explicitly pass the DataSource to HibernateTransactionManager's "dataSource" property.

==================

http://forum.springsource.org/archive/index.php/t-24312.html

I am using Springs DataSourceTransactionManager and the TransactionProxyFactoryBean to provide declarative tx support over a number of service classes. Almost all methods proxied utilize the default isolation level. However, there are 3 methods that utilize SERIALIZABLE. In order to ensure that these methods utilize a new transaction their propogation behavior is set to PROPOGATION_NESTED.

Once in a blue moon I receive a "java.sql.SQLException: ORA-01453: SET TRANSACTION must be first statement of transaction" exception on our production systems when one of these serializable methods is invoked.

This exception occurs at:

org.springframework.jdbc.datasource.DataSourceUtil s.prepareConnectionForTransaction(DataSourceUtils. java:160)

when the DataSourceUtils attempts to set the isolation level.

Once this error occurs in production it occurs with great regularity - most likely as a result of pulling the same "crippled" connection out of the pool. We then have to bounce tomcat, we don't have other access to the DataSource, to stabilize the system.

We use DBCP as our connection pool. DBCP is configured to validate all connections when they are drawn from the pool. Neither the defaultAutoCommit or defaultTransactionIsolation parameters are set. So all connection should come out of the connection pool with autocommit set to true and the isolation level set to READ_COMMITTED.

The only way I think this could happen, is if a connection in the pool is set to autocommit false. When Spring requests the connection from the pool, DBCP would validate it thereby starting a JDBC transaction. When Spring then tries to set the isolation level to SERIALIZABLE the driver complains because a statement in a pre-existing JDBC tx has already been executed.

However, the DataSourceTransactionManager and the DataSourceUtils seem very particular about cleaning and resetting the connections before returning them to the pool. So no connection should ever be returned to the pool with its autoCommit set to false.

We never see this behavior in development. However, on those servers, tomcat is rarely up for more than a few hours between builds.

Can anyone think of another cause of this issue? How Spring would return a connection back to the pool without resetting its autoCommit back to true, perhaps when a checked exception is thrown?

thanks in advance,

carlos

Production is using:
Spring v1.2.1
Oracle DB v8.1.7
Oracle JDBC Driver v10.2.0.1.0
Apache DBCP v1.2.1
Tomcat v5.5.9
Sun JVM v1.5.0_05-b05
Windows Server 2003

TxManager and ProxyFactoryBean definitions:
<bean id="TransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTran sactionManager">
<property name="dataSource">
<ref local="EDISDataSource" />
</property>
</bean>

<bean id="TransactionProxyTemplate"
class="org.springframework.transaction.interceptor.Transa ctionProxyFactoryBean"
abstract="true">
<property name="transactionManager">
<ref local="TransactionManager" />
</property>
<property name="transactionAttributes">
<props>
<prop key="authenticate*">PROPAGATION_REQUIRED</prop>
<prop key="add*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="next*">PROPAGATION_NESTED,ISOLATION_SERIALIZABLE</prop>
<prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>
</props>
</property>
</bean>

STACK TRACE

WARN [http-80-Processor22][11 Apr 2006 13:08:22,124] org.springframework.remoting.support.RemoteInvocat ionTraceInterceptor(RemoteInvocationTraceIntercept or.java:78) - Processing of HessianServiceExporter remote call resulted in fatal exception: gov.usitc.eof.ws.EDISWebService.getNextImageID

org.springframework.transaction.CannotCreateTransa ctionException: Could not open JDBC connection for transaction; nested exception is java.sql.SQLException: ORA-01453: SET TRANSACTION must be first statement of transaction


java.sql.SQLException: ORA-01453: SET TRANSACTION must be first statement of transaction


at oracle.jdbc.driver.DatabaseError.throwSqlException (DatabaseError.java:112)

at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoe r.java:331)

at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoe r.java:288)

at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java: 743)

at oracle.jdbc.driver.T4CStatement.doOall8(T4CStateme nt.java:207)

at oracle.jdbc.driver.T4CStatement.executeForRows(T4C Statement.java:946)

at oracle.jdbc.driver.OracleStatement.doExecuteWithTi meout(OracleStatement.java:1168)

at oracle.jdbc.driver.OracleStatement.executeInternal (OracleStatement.java:1687)

at oracle.jdbc.driver.OracleStatement.execute(OracleS tatement.java:1653)

at oracle.jdbc.driver.PhysicalConnection.setTransacti onIsolation(PhysicalConnection.java:1600)

at org.apache.commons.dbcp.DelegatingConnection.setTr ansactionIsolation(DelegatingConnection.java:277)

at org.apache.commons.dbcp.PoolingDataSource$PoolGuar dConnectionWrapper.setTransactionIsolation(Pooling DataSource.java:308)

at org.springframework.jdbc.datasource.DataSourceUtil s.prepareConnectionForTransaction(DataSourceUtils. java:160)

at org.springframework.jdbc.datasource.DataSourceTran sactionManager.doBegin(DataSourceTransactionManage r.java:178)

at org.springframework.transaction.support.AbstractPl atformTransactionManager.getTransaction(AbstractPl atformTransactionManager.java:234)

at org.springframework.transaction.interceptor.Transa ctionAspectSupport.createTransactionIfNecessary(Tr ansactionAspectSupport.java:217)

at org.springframework.transaction.interceptor.Transa ctionInterceptor.invoke(TransactionInterceptor.jav a:50)

at org.springframework.aop.framework.ReflectiveMethod Invocation.proceed(ReflectiveMethodInvocation.java :144)

at org.springframework.aop.framework.JdkDynamicAopPro xy.invoke(JdkDynamicAopProxy.java:174)

at $Proxy1.nextId(Unknown Source)

at gov.usitc.eof.ws.EDISWebServiceImpl.getNextImageID (Unknown Source)

at sun.reflect.GeneratedMethodAccessor264.invoke(Unkn own Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Un known Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at org.springframework.aop.support.AopUtils.invokeJoi npointUsingReflection(AopUtils.java:288)

at org.springframework.aop.framework.ReflectiveMethod Invocation.invokeJoinpoint(ReflectiveMethodInvocat ion.java:155)

at org.springframework.aop.framework.ReflectiveMethod Invocation.proceed(ReflectiveMethodInvocation.java :122)

at org.springframework.remoting.support.RemoteInvocat ionTraceInterceptor.invoke(RemoteInvocationTraceIn terceptor.java:68)

at org.springframework.aop.framework.ReflectiveMethod Invocation.proceed(ReflectiveMethodInvocation.java :144)

at org.springframework.aop.framework.JdkDynamicAopPro xy.invoke(JdkDynamicAopProxy.java:174)

at $Proxy7.getNextImageID(Unknown Source)

at sun.reflect.GeneratedMethodAccessor263.invoke(Unkn own Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Un known Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at com.caucho.hessian.server.HessianSkeleton.invoke(H essianSkeleton.java:188)

at org.springframework.remoting.caucho.HessianService Exporter.handleRequest(HessianServiceExporter.java :87)

at org.springframework.web.servlet.mvc.SimpleControll erHandlerAdapter.handle(SimpleControllerHandlerAda pter.java:44)

at org.springframework.web.servlet.DispatcherServlet. doDispatch(DispatcherServlet.java:684)

at org.springframework.web.servlet.DispatcherServlet. doService(DispatcherServlet.java:625)

at org.springframework.web.servlet.FrameworkServlet.s erviceWrapper(FrameworkServlet.java:386)

at org.springframework.web.servlet.FrameworkServlet.d oPost(FrameworkServlet.java:355)

at javax.servlet.http.HttpServlet.service(HttpServlet .java:709)

at javax.servlet.http.HttpServlet.service(HttpServlet .java:802)

at org.apache.catalina.core.ApplicationFilterChain.in ternalDoFilter(ApplicationFilterChain.java:252)

at org.apache.catalina.core.ApplicationFilterChain.do Filter(ApplicationFilterChain.java:173)

at org.apache.catalina.core.StandardWrapperValve.invo ke(StandardWrapperValve.java:213)

at org.apache.catalina.core.StandardContextValve.invo ke(StandardContextValve.java:178)

at org.apache.catalina.core.StandardHostValve.invoke( StandardHostValve.java:126)

at org.apache.catalina.valves.ErrorReportValve.invoke (ErrorReportValve.java:105)

at org.apache.catalina.core.StandardEngineValve.invok e(StandardEngineValve.java:107)

at org.apache.catalina.connector.CoyoteAdapter.servic e(CoyoteAdapter.java:148)

at org.apache.coyote.http11.Http11Processor.process(H ttp11Processor.java:856)

at org.apache.coyote.http11.Http11Protocol$Http11Conn ectionHandler.processConnection(Http11Protocol.jav a:744)

at org.apache.tomcat.util.net.PoolTcpEndpoint.process Socket(PoolTcpEndpoint.java:527)

at org.apache.tomcat.util.net.LeaderFollowerWorkerThr ead.runIt(LeaderFollowerWorkerThread.java:80)

at org.apache.tomcat.util.threads.ThreadPool$ControlR unnable.run(ThreadPool.java:684)

at java.lang.Thread.run(Unknown Source)

 

==============

http://www.blogjava.net/RongHao/archive/2007/10/09/151411.html

问题背景:我们是一家工作流公司,客户采购我们的产品后,将其嵌入其项目中。我们的工作流采用的是   spring+hibernate的方式,客户项目则是jdbc直接进行数据库操作。
问题:客户在其数据库操作过程中需要调用我们的工作流接口,这样就需要将我们的工作流操作与他们的业  务操作置于同一个事务中。我们的服务采用的都是spring的声明式事务,而客户采用的是对         connection进行事务处理。如何保证事务的一致性?
想到的解决方案一:使用jta事务,用tomcat+jotm提供事务管理器。为什么一开始就想到要使用jta事务??实际上我们和客户都是使用的同一个数据库,为了方便,各自使用了不同的数据库连接方式,使用jta的话确实有bt的意思在里面。但是事实上是我们的第一反应都是jta。最后没有采用该方法的原因也很简单:我没有将jotm配置成功!汗一个。
想到的解决方案二:将客户的这些特定代码用spring管理起来。因为要修改客户部分代码,这个方案遭到了客户的强烈反对。于是放弃。
想到的解决方案三:客户数据库操作与我们的服务使用同一个数据库连接。然后编程处理事务。存在两种方式:一种是把客户的连接传给我们,另一种则是把我们的连接传给客户。第一种方式对我们的影响太大,所以最后决定采用后一种方式:从hibernate session中获取connection然后传递给客户。接下来查看一下HibernateTemplate的execute()方法,思路就很简单了:获取定义的sessionFactory-->创建一个新的session并打开-->将session与当前线程绑定-->给客户代码返回connection-->打开事务-->客户使用我们传递的connection进行数据库操作-->我们不带声明事务的服务操作-->提交事务-->解除绑定。
实际要注意的地方是:1、将session与当前线程绑定使用的TransactionSynchronizationManager.bindResource()方法,这样在HibernateTemplate里才能找到session;
                    2、我们的服务一定要把声明式事务彻底干掉,否则会有commit;
                    3、我们服务调用完毕后一定要flush session,否则客户代码不会感知数据库里的数据变化。
最终解决:使用了spring里常用的模板和回调。代码如下:

public   class  TransactionTemplate {

    
protected   final  Log logger  =  LogFactory.getLog(TransactionTemplate. class );

    
private  FlushMode flushMode  =  FlushMode.ALWAYS;

    
public  Object execute(TransactionCallback callback) {
        
// 首先获取sessionFactory
        SessionFactory sessionFactory  =  (SessionFactory) Framework.getEngine()
                .getContainer().getComponent(
" sessionFactory " );
        
// 创建一个新的session并打开
        logger.debug( " Opening single Hibernate Session in TransactionTemplate " );
        Session session 
=  getSession(sessionFactory);
        
// 将session与当前线程绑定
        TransactionSynchronizationManager.bindResource(sessionFactory,  new  SessionHolder(session));
        
// 获取数据库连接
        Connection conn  =  session.connection();
        Object result 
=   null ;
        Transaction transaction 
=   null ;
        
try  {
            
// 开始处理事务
            transaction  =  session.beginTransaction();
            
try  {
                result 
=  callback.doInTransaction(conn);
            }
            
catch  (RuntimeException ex) {
                doRollback(session, transaction);
                
throw  ex;
            }
            
catch  (Error err) {
                doRollback(session, transaction);
                
throw  err;
            }
            
// 如果数据库操作过程中没有发生异常则提交事务
            transaction.commit();
        } 
catch  (WorkflowException e) {
            logger.error(
" 数据库操作失败,事务回滚也失败! " );
            
throw  e;
        } 
catch  (RuntimeException ex) {
            logger.error(
" 数据库操作失败,事务被回滚! " );
            
throw  ex;
        } 
catch  (Error err) {
            logger.error(
" 数据库操作失败,事务被回滚! " );
            
throw  err;
        } 
finally  {
            
//  将session与当前线程解除绑定
            TransactionSynchronizationManager.unbindResource(sessionFactory);
            doClose(session);
        }
        
return  result;
    }

    
protected  Session getSession(SessionFactory sessionFactory) {
        Session session 
=  SessionFactoryUtils.getSession(sessionFactory,  true );
        FlushMode flushMode 
=  getFlushMode();
        
if  (flushMode  !=   null ) {
            session.setFlushMode(flushMode);
        }
        
return  session;
    }

    
private   void  doRollback(Session session, Transaction transaction) {
        logger.debug(
" 数据库操作异常,开始回滚事务 " );
        
try  {
            transaction.rollback();
            logger.debug(
" 回滚事务成功! " );
        }
        
catch  (Exception e) {
            logger.error(
" 回滚事务失败! " );
            
throw   new  WorkflowException( " 回滚事务失败! " );
        } 
finally  {
            session.clear();
        }
    }

    
private   void  doClose(Session session) {
        logger.debug(
" 开始关闭连接 " );
        
try  {
            session.close();
        }
        
catch  (Exception e) {
            logger.error(
" 关闭连接失败! " );
            
throw   new  WorkflowException( " 关闭连接失败! " );
        }
    }

    
public  FlushMode getFlushMode() {
        
return  flushMode;
    }

    
public   void  setFlushMode(FlushMode flushMode) {
        
this .flushMode  =  flushMode;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值