spring---transaction(5)---事务的体系

时间:2022-09-19 11:51:53

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的方式如下:

spring---transaction(5)---事务的体系spring---transaction(5)---事务的体系

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