Spring事务传播实验剖析(一)

摘抄概念如下:

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:

表1事务传播行为类型

事务传播行为类型

说明

PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。spring默认使用此传播类型

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY

使用当前的事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

通过阅读Spring3.X企业开发实战书籍,回顾了一下事务传播的相关知识点,并通过小实验来加深印象。

实验如下:

由controller调用service,且包含不同service之间调用,日志启用DEBUG级别

事务传播方式采用默认:PROPAGATION_REQUIRED

隔离级别采用默认:

1:

@RequestMapping(value="/test")
public void insert() {
	System.out.println("---------spring mvc update---------");
	userService.update(111,"aaaazz");
	System.out.println("---------spring mvc all---------");
}

2:UserServiceImpl的update方法:

@Override
@Transactional
public void update(int id,String username) {
	System.out.println("---------UserServiceImpl updateAge start---------");
	updateAge(id);
	System.out.println("---------UserServiceImpl updateAge  end---------");
	
	System.out.println("---------scoreService updateScore start---------");
	scoreService.updateScore(id);
	System.out.println("---------scoreService updateScore end---------");

    // int i = 1 / 0; //exception测试注释
}

3:ScopeService的updateScore方法:

@Override
@Transactional
public void updateScore(int id) {
	scoreDao.updateScore(id);
// int i = 1 / 0; //exception测试注释
}

实例1:

UserServiceImpl#update和ScoreService#updateScore都开启事务注解,输出日志:

---------spring mvc update---------
[DEBUG] 2018-05-21 15:20:31,931 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:243)
Returning cached instance of singleton bean 'org.springframework.transaction.interceptor.TransactionInterceptor#0'
  [DEBUG] 2018-05-21 15:20:31,938 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:243)
Returning cached instance of singleton bean 'transactionManager'
  [DEBUG] 2018-05-21 15:20:31,947 method:org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:366)

Creating new transaction with name [com.paic.ssm.user.service.impl.UserServiceImpl.update]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
  [DEBUG] 2018-05-21 15:20:31,948 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:204)
Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@8de40e] for JDBC transaction
  [DEBUG] 2018-05-21 15:20:31,956 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:221)
Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@8de40e] to manual commit
  ---------UserServiceImpl updateAge start---------
15:20:31.965 [http-bio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
15:20:31.973 [http-bio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e45c4f]
15:20:32.030 [http-bio-8080-exec-1] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@8de40e] will be managed by Spring
15:20:32.033 [http-bio-8080-exec-1] DEBUG c.p.ssm.user.dao.IUserDao.updateAge - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@8de40e]
15:20:32.039 [http-bio-8080-exec-1] DEBUG c.p.ssm.user.dao.IUserDao.updateAge - ==>  Preparing: update user_t set age = ? 
15:20:32.182 [http-bio-8080-exec-1] DEBUG c.p.ssm.user.dao.IUserDao.updateAge - ==> Parameters: 111(Integer)
[DEBUG] 2018-05-21 15:20:32,186 method:com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:123)
{conn-10010, pstmt-20000} enter cache
  15:20:32.187 [http-bio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e45c4f]
---------UserServiceImpl updateAge  end---------
---------scoreService updateScore start---------
[DEBUG] 2018-05-21 15:20:32,187 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:243)
Returning cached instance of singleton bean 'transactionManager'
  [DEBUG] 2018-05-21 15:20:32,188 method:org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:471)

Participating in existing transaction
  15:20:32.188 [http-bio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e45c4f] from current transaction
15:20:32.189 [http-bio-8080-exec-1] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@8de40e]
15:20:32.189 [http-bio-8080-exec-1] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ==>  Preparing: update score_t set score = ? 
15:20:32.189 [http-bio-8080-exec-1] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ==> Parameters: 111(Integer)
[DEBUG] 2018-05-21 15:20:32,254 method:com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:123)
{conn-10010, pstmt-20001} enter cache
  15:20:32.256 [http-bio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e45c4f]
---------scoreService updateScore end---------
[DEBUG] 2018-05-21 15:20:32,257 method:org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:753)
Initiating transaction commit
  [DEBUG] 2018-05-21 15:20:32,258 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit(DataSourceTransactionManager.java:267)

Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@8de40e]
  15:20:32.321 [http-bio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e45c4f]
15:20:32.321 [http-bio-8080-exec-1] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e45c4f]
[DEBUG] 2018-05-21 15:20:32,322 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager.java:325)
Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@8de40e] after transaction
  [DEBUG] 2018-05-21 15:20:32,324 method:org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:327)
Returning JDBC Connection to DataSource
  ---------spring mvc all---------

可以看到,在userServcie执行update之前,spring创建了一个事务:

Creating new transaction with name。。。

执行完毕之后,再执行scoreService的updateScore之前,并未创建新的事务,而是参与到了已存在的事务中:

Participating in existing transaction。。。

最终提交此事务:

Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@8de40e]

说明,不同service之间调用时,若两者都存在事务,则后者不会自省创建事务,而是参与到之前的事务中

2:UserServiceImpl#update开启事务,ScoreService#updateScore不开启事务,输出日志:


  ---------spring mvc update---------
[DEBUG] 2018-05-21 15:21:32,255 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:243)
Returning cached instance of singleton bean 'org.springframework.transaction.interceptor.TransactionInterceptor#0'
  [DEBUG] 2018-05-21 15:21:32,263 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:243)
Returning cached instance of singleton bean 'transactionManager'
  [DEBUG] 2018-05-21 15:21:32,273 method:org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:366)

  
  Creating new transaction with name [com.paic.ssm.user.service.impl.UserServiceImpl.update]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
  [DEBUG] 2018-05-21 15:21:32,273 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:204)
Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@9d935d] for JDBC transaction
  [DEBUG] 2018-05-21 15:21:32,280 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:221)
Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@9d935d] to manual commit
  ---------UserServiceImpl updateAge start---------
15:21:32.289 [http-bio-8080-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
15:21:32.296 [http-bio-8080-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d92da1]
15:21:32.354 [http-bio-8080-exec-3] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@9d935d] will be managed by Spring
15:21:32.356 [http-bio-8080-exec-3] DEBUG c.p.ssm.user.dao.IUserDao.updateAge - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@9d935d]
15:21:32.361 [http-bio-8080-exec-3] DEBUG c.p.ssm.user.dao.IUserDao.updateAge - ==>  Preparing: update user_t set age = ? 
15:21:32.502 [http-bio-8080-exec-3] DEBUG c.p.ssm.user.dao.IUserDao.updateAge - ==> Parameters: 111(Integer)
[DEBUG] 2018-05-21 15:21:32,506 method:com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:123)
{conn-10010, pstmt-20000} enter cache
  15:21:32.507 [http-bio-8080-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d92da1]
---------UserServiceImpl updateAge  end---------
---------scoreService updateScore start---------
15:21:32.507 [http-bio-8080-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d92da1] from current transaction
15:21:32.507 [http-bio-8080-exec-3] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@9d935d]
15:21:32.507 [http-bio-8080-exec-3] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ==>  Preparing: update score_t set score = ? 
15:21:32.508 [http-bio-8080-exec-3] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ==> Parameters: 111(Integer)
[DEBUG] 2018-05-21 15:21:32,586 method:com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:123)
{conn-10010, pstmt-20001} enter cache
  15:21:32.586 [http-bio-8080-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d92da1]
---------scoreService updateScore end---------
[DEBUG] 2018-05-21 15:21:32,587 method:org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:753)
Initiating transaction commit
  [DEBUG] 2018-05-21 15:21:32,587 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit(DataSourceTransactionManager.java:267)

  
  Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@9d935d]
  15:21:32.680 [http-bio-8080-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d92da1]
15:21:32.680 [http-bio-8080-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@d92da1]
[DEBUG] 2018-05-21 15:21:32,680 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager.java:325)
Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@9d935d] after transaction
  [DEBUG] 2018-05-21 15:21:32,681 method:org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:327)
Returning JDBC Connection to DataSource
  ---------spring mvc all--------- 

可以看到,scoreService并未有事务创建和传播的日志,暂无法断定其是否被事务作用

打开userService#update的异常测试代码: int i = 1 / 0;

执行之后,后台报错,部分日志:


  17:18:57.650 [http-bio-8080-exec-6] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c43375]
---------UserServiceImpl updateAge  end---------
---------scoreService updateScore start---------
17:18:57.651 [http-bio-8080-exec-6] DEBUG org.mybatis.spring.SqlSessionUtils - Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c43375] from current transaction
17:18:57.651 [http-bio-8080-exec-6] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@d6b1dc]
17:18:57.651 [http-bio-8080-exec-6] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ==>  Preparing: update score_t set score = ? 
17:18:57.651 [http-bio-8080-exec-6] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ==> Parameters: 111(Integer)
[DEBUG] 2018-05-21 17:18:57,652 method:com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:123)
{conn-10010, pstmt-20001} enter cache
  17:18:57.652 [http-bio-8080-exec-6] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c43375]


---------scoreService updateScore end---------
[DEBUG] 2018-05-21 17:18:57,653 method:org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:844)


Initiating transaction rollback
  [DEBUG] 2018-05-21 17:18:57,653 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doRollback(DataSourceTransactionManager.java:282)
Rolling back JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@d6b1dc]
  17:18:57.827 [http-bio-8080-exec-6] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization rolling back SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c43375]
17:18:57.827 [http-bio-8080-exec-6] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c43375]
[DEBUG] 2018-05-21 17:18:57,829 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager.java:325)
Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@d6b1dc] after transaction
  [DEBUG] 2018-05-21 17:18:57,830 method:org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:327)
Returning JDBC Connection to DataSource
  [DEBUG] 2018-05-21 17:18:57,846 method:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:132)
Resolving exception from handler [public void com.paic.ssm.login.controller.LoginController.insert()]: java.lang.ArithmeticException: / by zero
  [DEBUG] 2018-05-21 17:18:57,850 method:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:132)
Resolving exception from handler [public void com.paic.ssm.login.controller.LoginController.insert()]: java.lang.ArithmeticException: / by zero
  [DEBUG] 2018-05-21 17:18:57,850 method:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:132)
Resolving exception from handler [public void com.paic.ssm.login.controller.LoginController.insert()]: java.lang.ArithmeticException: / by zero
  [DEBUG] 2018-05-21 17:18:57,852 method:org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
Could not complete request
  java.lang.ArithmeticException: / by zero
	at com.paic.ssm.user.service.impl.UserServiceImpl.update(UserServiceImpl.java:49)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

可以看到,在scoreService执行完毕之后,抛出了异常,日志输出了事务rollback,经查验DB,数据并未修改,说明两个service都被事务作用了,也即事务由userService传播到了scoreService,此时和第一种情况是一样的,无论两者谁抛出异常,只要被初始调用捕获,就会回滚了。

注意:如果scoreService的异常被其本身捕获,那么数据也不会回滚。。。

3:UserServiceImpl#update不开启事务,ScoreService#updateScore开启事务,输出日志:

 ---------spring mvc update---------
---------UserServiceImpl updateAge start---------
15:27:15.292 [http-bio-8080-exec-8] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
15:27:15.298 [http-bio-8080-exec-8] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76823b] was not registered for synchronization because synchronization is not active
[DEBUG] 2018-05-21 15:27:15,356 method:org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:110)


Fetching JDBC Connection from DataSource
  15:27:15.356 [http-bio-8080-exec-8] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@c824d8] will not be managed by Spring
15:27:15.358 [http-bio-8080-exec-8] DEBUG c.p.ssm.user.dao.IUserDao.updateAge - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@c824d8]
15:27:15.361 [http-bio-8080-exec-8] DEBUG c.p.ssm.user.dao.IUserDao.updateAge - ==>  Preparing: update user_t set age = ? 
15:27:15.468 [http-bio-8080-exec-8] DEBUG c.p.ssm.user.dao.IUserDao.updateAge - ==> Parameters: 111(Integer)
[DEBUG] 2018-05-21 15:27:15,505 method:com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:123)
{conn-10010, pstmt-20000} enter cache
  15:27:15.506 [http-bio-8080-exec-8] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76823b]
[DEBUG] 2018-05-21 15:27:15,506 method:org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:327)
Returning JDBC Connection to DataSource
  ---------UserServiceImpl updateAge  end---------
---------scoreService updateScore start---------
[DEBUG] 2018-05-21 15:27:15,508 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:243)
Returning cached instance of singleton bean 'org.springframework.transaction.interceptor.TransactionInterceptor#0'
  [DEBUG] 2018-05-21 15:27:15,521 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:243)
Returning cached instance of singleton bean 'transactionManager'
  [DEBUG] 2018-05-21 15:27:15,526 method:org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:366)

  
  Creating new transaction with name [com.paic.ssm.user.service.impl.ScoreServiceImpl.updateScore]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
  [DEBUG] 2018-05-21 15:27:15,527 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:204)
Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@c824d8] for JDBC transaction
  [DEBUG] 2018-05-21 15:27:15,532 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:221)
Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@c824d8] to manual commit
  15:27:15.534 [http-bio-8080-exec-8] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
15:27:15.534 [http-bio-8080-exec-8] DEBUG org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d52634]
15:27:15.536 [http-bio-8080-exec-8] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@c824d8] will be managed by Spring
15:27:15.536 [http-bio-8080-exec-8] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ooo Using Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@c824d8]
15:27:15.536 [http-bio-8080-exec-8] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ==>  Preparing: update score_t set score = ? 
15:27:15.537 [http-bio-8080-exec-8] DEBUG c.p.s.user.dao.IScoreDao.updateScore - ==> Parameters: 111(Integer)
[DEBUG] 2018-05-21 15:27:15,537 method:com.alibaba.druid.pool.PreparedStatementPool.put(PreparedStatementPool.java:123)
{conn-10010, pstmt-20001} enter cache
  15:27:15.538 [http-bio-8080-exec-8] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d52634]
[DEBUG] 2018-05-21 15:27:15,538 method:org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:753)
Initiating transaction commit
  [DEBUG] 2018-05-21 15:27:15,538 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit(DataSourceTransactionManager.java:267)

  
  Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@c824d8]
  15:27:15.622 [http-bio-8080-exec-8] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d52634]
15:27:15.623 [http-bio-8080-exec-8] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d52634]
[DEBUG] 2018-05-21 15:27:15,624 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager.java:325)
Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@c824d8] after transaction
  [DEBUG] 2018-05-21 15:27:15,625 method:org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:327)
Returning JDBC Connection to DataSource
  ---------scoreService updateScore end---------
---------spring mvc all--------- 

可以看到,userService先执行update,但是并未创建事务,而是直接获取到了connection:

Fetching JDBC Connection from DataSource。。。

而scoreService则开启了事务,并正常commit

有什么影响呢?下面再次打开userService的异常测试代码,执行日志如下:

Initiating transaction commit
  [DEBUG] 2018-05-21 17:32:08,784 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit(DataSourceTransactionManager.java:267)


Committing JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@53ba89]
  17:32:08.853 [http-bio-8080-exec-6] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@45ce57]
17:32:08.853 [http-bio-8080-exec-6] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@45ce57]
[DEBUG] 2018-05-21 17:32:08,854 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager.java:325)
Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@53ba89] after transaction
  [DEBUG] 2018-05-21 17:32:08,861 method:org.springframework.jdbc.datasource.DataSourceUtils.doReleaseConnection(DataSourceUtils.java:327)
Returning JDBC Connection to DataSource


  ---------scoreService updateScore end---------
[DEBUG] 2018-05-21 17:32:08,868 method:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:132)
Resolving exception from handler [public void com.paic.ssm.login.controller.LoginController.insert()]: java.lang.ArithmeticException: / by zero
  [DEBUG] 2018-05-21 17:32:08,869 method:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:132)
Resolving exception from handler [public void com.paic.ssm.login.controller.LoginController.insert()]: java.lang.ArithmeticException: / by zero
  [DEBUG] 2018-05-21 17:32:08,869 method:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.resolveException(AbstractHandlerExceptionResolver.java:132)


Resolving exception from handler [public void com.paic.ssm.login.controller.LoginController.insert()]: java.lang.ArithmeticException: / by zero
  [DEBUG] 2018-05-21 17:32:08,869 method:org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
Could not complete request
  java.lang.ArithmeticException: / by zero
	at com.paic.ssm.user.service.impl.UserServiceImpl.update(UserServiceImpl.java:49)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

可以看到,在userService#update方法里面执行时,直到scoreService执行完毕,事务就紧接着提交,最后时刻,方法再抛出异常,已经为时已晚,查验DB,数据已修改,说明调用方userservice的异常不会影响被调用方scoreservice的事务

可以继续验证,打开scoreService的updateScore方法的异常测试代码,会发现scoreService已正常回滚,而userService无法回滚,很符合常识

---------scoreService updateScore start---------
[DEBUG] 2018-05-21 17:38:15,272 method:org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:243)
Returning cached instance of singleton bean 'transactionManager'
  [DEBUG] 2018-05-21 17:38:15,276 method:org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:366)
Creating new transaction with name [com.paic.ssm.user.service.impl.ScoreServiceImpl.updateScore]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
  [DEBUG] 2018-05-21 17:38:15,278 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:204)
Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@a6168] for JDBC transaction


Initiating transaction rollback
  [DEBUG] 2018-05-21 17:38:15,289 method:org.springframework.jdbc.datasource.DataSourceTransactionManager.doRollback(DataSourceTransactionManager.java:282)
Rolling back JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@a6168]
  17:38:15.319 [http-bio-8080-exec-3] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization rolling back SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@bf617d]

4:UserServiceImpl#update不开启事务,ScoreService#updateScore不开启事务,不输出日志了

此时,两者均不会被事务作用,异常不会回滚,不赘述。

由于事务传播方式较多,但是系统大部分都是使用默认的事务传播方式,故其他的模式就不再测试了,经过测试发现,结果符合spring的传播方式的定义。

总结一下,如果要使用事务,一般是在service方法上进行标注,若初始调用某service,则事务会开启,即便后续调用其他service方法,事务仍会继续传播,所有service共用一个事务,任一方法抛出异常,如果被初始调用的service捕获,则方法回滚。若异常被底层预先捕获,则无法回滚了。

特别注意的是,如果初始调用无事务,则后续的方法调用不会影响它,即事务只会往后传播。。。

虽然spring事务传播的定义已经表述很清楚了,但是通过小实验,又加深了印象。

下期讨论下service里面开启新线程,事务的传播。。。

猜你喜欢

转载自my.oschina.net/u/1474131/blog/1816081