spring---transaction(5)---事务的体系
1.写在前面
事务的模型为3中:
本地事务模式。
编程事务模式。
声明事务模式。
例子1:本地事务模式
Connection conn=jdbcDao.getConnection(); PreparedStatement ps=conn.prepareStatement("insert into user(name,age) value(?,?)"); ps.setString(1,user.getName()); ps.setInt(2,user.getAge()); ps.execute();
案例2:编程事务模式
Connection conn=jdbcDao.getConnection(); conn.setAutoCommit(false); try { PreparedStatement ps=conn.prepareStatement("insert into user(name,age) value(?,?)"); ps.setString(1,user.getName()); ps.setInt(2,user.getAge()); ps.execute(); conn.commit(); } catch (Exception e) { e.printStackTrace(); conn.rollback(); }finally{ conn.close(); }
InitialContext ctx = new InitialContext(); UserTransaction txn = (UserTransaction)ctx.lookup("UserTransaction"); try { txn.begin(); //业务代码 txn.commit(); } catch (Exception up) { txn.rollback(); throw up; }
案例3:声明事务模式
@Transactional public void save(User user){ jdbcTemplate.update("insert into user(name,age) value(?,?)",user.getName(),user.getAge()); }
我认为他们各自的特点在于:谁在管理着事务的提交和回滚等操作?
这里有三个角色:数据库、开发人员、spring(等第三方)
- 对于案例1:开发人员不用知道事务的存在,事务全部交给数据库来管理,数据库自己决定什么时候提交或回滚,所以数据库是事务的管理者
- 对于案例2、3:事务的提交和回滚操作完全交给开发人员,开发人员来决定事务什么时候提交或回滚,所以开发人员是事务的管理者
- 对于案例4:开发人员完全不用关心事务,事务的提交和回滚操作全部交给Spring来管理,所以Spring是事务的管理者
2.编程式事务
编程式事务:即通过手动编程方式来实现事务操作,大部分情况,都是类似于上述案例2情况,开发人员来管理事务的提交和回滚,但也可能是Spring自己来管理事务,如Spring的TransactionTemplate。
Spring的TransactionTemplate 封装了对于数据库的操作(使用jdbc操作事务,编程非常麻烦,老是需要写一套模板式的try catch代码)
TransactionTemplate template=new TransactionTemplate(); template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT); template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); template.setTransactionManager(transactionManager); template.execute(new TransactionCallback<User>() { @Override public User doInTransaction(TransactionStatus status) { //可以使用DataSourceUtils获取Connection来执行sql //jdbcTemplate.update(sql2); //可以使用SessionFactory的getCurrentSession获取Session来执行 //hibernateTemplate.save(user1)
//可以使用myBatis的sqlSessionTemplate
//simpleTempalte.insert(Statement.getStatement(TempOrderMapper.class, MapperMethod.INSERT), table); return null; } });
如果使用的是DataSourceTransactionManager,你就可以使用jdbc对应的JdbcTemplate或者myBatis对应的simpleTempalte来执行业务逻辑;或者直接使用Connection,但是必须使用DataSourceUtils来获取Connection
如果使用的是HibernateTransactionManager,就可以使用HibernateTemplate来执行业务逻辑,或者则可以使用SessionFactory的getCurrentSession方法来获取当前线程绑定的Session
- TransactionTemplate继承了DefaultTransactionDefinition,有了默认的事务定义,也可以自定义设置隔离级别、传播属性等
- TransactionTemplate需要一个PlatformTransactionManager事务管理器,来执行事务的操作
- TransactionTemplate在TransactionCallback中执行业务代码,try catch的事务模板代码,则被封装起来,包裹在业务代码的周围,详细见TransactionTemplate的execute方法,如下:
public <T> T execute(TransactionCallback<T> action) throws TransactionException { if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); } else {
//由于TransactionTemplate继承了DefaultTransactionDefinition,所以使用PlatformTransactionManager事务管理器来根据TransactionTemplate来获取事务 TransactionStatus status = this.transactionManager.getTransaction(this); T result; try {
//在TransactionCallback中的doInTransaction中执行相应的业务代码。回调 result = action.doInTransaction(status); } catch (RuntimeException ex) { // Transactional code threw application exception -> rollback
//如果业务代码出现异常,则回滚事务,没有异常则提交事务,回滚与提交都是通过PlatformTransactionManager事务管理器来进行的 rollbackOnException(status, ex); throw ex; } catch (Error err) { // Transactional code threw error -> rollback rollbackOnException(status, err); throw err; } catch (Exception ex) { // Transactional code threw unexpected exception -> rollback rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); }
//由transactionManager关于事务的提交 this.transactionManager.commit(status); return result; } }
事务代码和业务代码可以实现分离的原理
我们可以看到,使用TransactionTemplate,其实就做到了事务代码和业务代码的分离,分离之后的代价就是,必须保证他们使用的是同一类型事务。之后的声明式事务实现分离也是同样的原理,这里就提前说明一下。
1 如果使用DataSourceTransactionManager
- 1.1 事务代码是通过和当前线程绑定的ConnectionHolder中的Connection的commit和rollback来执行相应的事务,所以我们必须要保证业务代码也是使用相同的Connection,这样才能正常回滚与提交。
- 1.2 业务代码使用jdbcTemplate.update(sql)来执行业务,这个方法是使用的Connection从哪来的?是和上面的事务Connection是同一个吗?源码如下(jdbcTemplate在执行sql时,会使用DataSourceUtils从dataSource中获取一个Connection):
JdbcTemplate.java(jdbcTemplate在执行sql时,会使用DataSourceUtils从dataSource中获取一个Connection)
@Override public <T> T execute(StatementCallback<T> action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); //使用DataSourceUtils从dataSource中获取一个Connection Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } stmt = conToUse.createStatement(); applyStatementSettings(stmt); Statement stmtToUse = stmt; if (this.nativeJdbcExtractor != null) { stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); } T result = action.doInStatement(stmtToUse); handleWarnings(stmt); return result; } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex); } finally { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } }
public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); //从当前线程中(TransactionSynchronizationManager管理器)中获取connection ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(dataSource.getConnection()); } return conHolder.getConnection(); } }
也是先获取和当前线程绑定的ConnectionHolder(由于事务在执行业务逻辑前已经开启,已经有了和当前线程绑定的ConnectionHolder),所以会获取到和事务中使用的ConnectionHolder,这样就保证了他们使用的是同一个Connection了,自然就可以正常提交和回滚了。
如果想使用Connection,则需要使用DataSourceUtils从dataSorce中获取Connection,不能直接从dataSource中获取Connection。
- 1.3 业务代码使用myBatis管理的simpleTempalte.insert(Statement.getStatement(TempOrderMapper.class, MapperMethod.INSERT), table);来执行业务,所以我们必须要保证业务代码也是使用相同的sqlSession?源码如下:(详细见myBaits源代码系列文章)
由于myBatis的实际执行tempalte是simpleTempalte的代理对象,可以看到在SqlSessionInterceptor的invoke方法中是从SqlSessionUtils中获取sqlSession和
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取sqlSession SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } }
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { //也是从当前线程中(TransactionSynchronizationManager管理器)中获取SqlSessionHolder SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } //如果没有获取到则, 创建已经绑定到TransactionSynchronizationManager session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
2 如果使用HibernateTransactionManager
- 2.1 事务代码是通过和当前线程绑定的SessionHolder中的Session中的Transaction的commit和rollback来执行相应的事务,详见上一篇文章说明事务管理器的事务分析,所以我们必须要保证业务代码也是使用相同的session
- 2.2业务代码就不能使用jdbcTemplate来执行相应的业务逻辑了,需要使用Session来执行相应的操作,换成对应的HibernateTemplate来执行。
HibernateTemplate在执行save(user)的过程中,会获取一个Session,方式如下:
session = getSessionFactory().getCurrentSession();
Hibernate定义了这样的一个接口:CurrentSessionContext,内容如下:
public interface CurrentSessionContext extends Serializable { public Session currentSession() throws HibernateException; }
上述SessionFactory获取当前Session就是依靠CurrentSessionContext的实现
在spring环境下,默认采用的是SpringSessionContext,它获取当前Session的方式如下:
也是先获取和当前线程绑定的SessionHolder(由于事务在执行业务逻辑前已经开启,已经有了和当前线程绑定的SessionHolder),所以会获取到和事务中使用的SessionHolder,这样就保证了他们使用的是同一个Session了,自然就可以正常提交和回滚了。
如果不想通过使用HibernateTemplate,想直接通过Session来操作,同理则需要使用SessionFactory的getCurrentSession方法来获取Session,而不能使用SessionFactory的openSession方法。
3.Spring的声明式事务
Spring可以有三种形式来配置事务拦截,不同配置形式仅仅是外在形式不同,里面的拦截原理都是一样的,所以先通过一个小例子了解利用AOP实现事务拦截的原理
利用AOP实现声明式事务的原理(简单的AOP事务例子)
@Repository public class AopUserDao implements InitializingBean{ @Autowired private UserDao userDao; private UserDao proxyUserDao; @Resource(name="transactionManager") private PlatformTransactionManager transactionManager; @Override public void afterPropertiesSet() throws Exception {
//使用代理工厂 ProxyFactory proxyFactory = new ProxyFactory();
//设置代理的目标对象 proxyFactory.setTarget(userDao);
//引入spring的事务拦截器(详细见spring事务拦截器) TransactionInterceptor transactionInterceptor=new TransactionInterceptor();
//设置事务管理器(详细见spring事务拦截器) transactionInterceptor.setTransactionManager(transactionManager); Properties properties=new Properties(); properties.setProperty("*","PROPAGATION_REQUIRED");
//设置事务的属性(详细见TransactionDefinition ) transactionInterceptor.setTransactionAttributes(properties);
//对代理对象加入拦截器 proxyFactory.addAdvice(transactionInterceptor); proxyUserDao=(UserDao) proxyFactory.getProxy(); } public void save(User user){ proxyUserDao.save(user); } }
代码分析如下:
- 首先需要一个原始的UserDao,我们需要对它进行AOP代理,产生代理对象proxyUserDao,之后保存的功能就是使用proxyUserDao来执行
- 对UserDao具体的代理过程如下:
- 使用代理工厂,设置要代理的对象 proxyFactory.setTarget(userDao);
- 对代理对象加入拦截器
- 分成2种情况,一种默认拦截原UserDao的所有方法,一种是指定Pointcut,即拦截原UserDao的某些方法。
- 这里使用proxyFactory.addAdvice(transactionInterceptor);就表示默认拦截原UserDao的所有方法。
- 如果使用proxyFactory.addAdvisor(advisor),这里的Advisor可以简单看成是Pointcut和Advice的组合,Pointcut则是用于指定是否拦截某些方法。
设置好代理工厂要代理的对象和拦截器后,便可以创建代理对象。详细见spring的事务拦截器(TransactionInterceptor)
- proxyUserDao=(UserDao) proxyFactory.getProxy()
-
- 之后,我们在使用创建出的proxyUserDao时,就会首先进入拦截器,执行相关拦截器代码,因此我们可以在这里实现事务的处理
事务拦截器的原理分析
事务拦截器需要2个参数:事务配置的提供者、事务管理器PlatformTransactionManager
事务配置的提供者
用于指定哪些方法具有什么样的事务配置
可以通过属性配置方式,或者通过其他一些配置方式,如下三种方式都是为了获取事务配置提供者:
-
- 方式1:
<property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property
-
- 方式2:
<tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> </tx:attributes>
-
- 方式3:
@Transactional(propagation=Propagation.REQUIRED)
事务管理器PlatformTransactionManager
有了事务的配置,我们就可以通过事务管理器来获取事务了。
在执行代理proxyUserDao的save(user)方法时,会先进入事务拦截器中,具体的拦截代码如下:(很早之前有过分析这段代码,spring事务拦截器)
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // 第一步:首先获取所执行方法的对应的事务配置 final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); //第二步:然后获取指定的事务管理器PlatformTransactionManager final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // 第三步:根据事务配置,使用事务管理器创建出事务 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. // 第四步:继续执行下一个拦截器,最终会执行到代理的原始对象的方法 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // 第五步:一旦执行过程发生异常,使用事务拦截器进行事务的回滚 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } //第六步:如果没有异常,则使用事务拦截器提交事务 commitTransactionAfterReturning(txInfo); return retVal; } }
总结:
- 第一步:首先获取所执行方法的对应的事务配置
- 第二步:然后获取指定的事务管理器PlatformTransactionManager
- 第三步:根据事务配置,使用事务管理器创建出事务
- 第四步:继续执行下一个拦截器,最终会执行到代理的原始对象的方法
- 第五步:一旦执行过程发生异常,使用事务拦截器进行事务的回滚
- 第六步:如果没有异常,则使用事务拦截器提交事务