zoukankan      html  css  js  c++  java
  • 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
    • 第三步:根据事务配置,使用事务管理器创建出事务
    • 第四步:继续执行下一个拦截器,最终会执行到代理的原始对象的方法
    • 第五步:一旦执行过程发生异常,使用事务拦截器进行事务的回滚
    • 第六步:如果没有异常,则使用事务拦截器提交事务
  • 相关阅读:
    andriod获得textView的值设置textView的text
    Android 自动生成的R类
    andriod 启动日历
    ggplot2在一幅图上画两条曲线
    R语言中动态安装库
    Python中的动态类
    Python中将dict转换为kwargs
    Apache负载均衡
    Python codecs小Tips
    Matlab求三重积分
  • 原文地址:https://www.cnblogs.com/chihirotan/p/6804025.html
Copyright © 2011-2022 走看看