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
  • 第三步:根据事务配置,使用事务管理器创建出事务
  • 第四步:继续执行下一个拦截器,最终会执行到代理的原始对象的方法
  • 第五步:一旦执行过程发生异常,使用事务拦截器进行事务的回滚
  • 第六步:如果没有异常,则使用事务拦截器提交事务

 

posted @ 2017-05-03 21:39  qtyy  阅读(3080)  评论(0编辑  收藏  举报