Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

在我完成一个项目的时候,遇到了一个Spring事务不回滚的问题,通过aspectJ和@Transactional注解都无法完成对于事务的回滚,经过查看博客和文档

  1. 默认回滚RuntimeException
  2. Service内部方法调用
  3. Spring父子容器覆盖

代码已经上传到 https://github.com/morethink/transactional

异常

下面是@Transactional的注释文档,下面有个If no rules are relevant to the exception,it will be treated like {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute} (rolling back on runtime exceptions).

默认会使用RuntimeException,那为什么Spring默认回滚RuntimeException,因为Java把Exception分为两种。

  1. checked Exception:Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
  2. unchecked Exception: RuntimeException和Error都属于未检查异常。

/**
* Describes transaction attributes on a method or class.
*
* <p>This annotation type is generally directly comparable to Spring's
* {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
* class, and in fact {@link AnnotationTransactionAttributeSource} will directly
* convert the data to the latter class, so that Spring's transaction support code
* does not have to know about annotations. If no rules are relevant to the exception,
* it will be treated like
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}
* (rolling back on runtime exceptions).
*
* <p>For specific information about the semantics of this annotation's attributes,
* consult the {@link org.springframework.transaction.TransactionDefinition} and
* {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs.
*
* @author Colin Sampaleanu
* @author Juergen Hoeller
* @author Sam Brannen
* @since 1.2
* @see org.springframework.transaction.interceptor.TransactionAttribute
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute
* @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
*/

因此,如果发生的不是RuntimeException,而你有没有配置rollback for ,那么,异常就不会回滚。

service 内部方法调用

就是一个没有开启事务控制的方法调用一个开启了事务控制方法,不会事务回滚。

AccountService类

@Service
public class AccountService { @Autowired
private AccountDao accountDao; /**
* 完成转钱业务,transfer方法开启事务
*
* @param out
* @param in
* @param money
*/
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED)
public void transfer(String out, String in, double money) {
Account account = new Account();
account.setName(out);
account.setMoney(money);
accountDao.out(account);
int i = 1 / 0;
account.setName(in);
accountDao.in(account);
} /**
* 完成转钱业务,transferProxy方法没有开启事务
*
* @param out
* @param in
* @param money
*/
public void transferProxy(String out, String in, double money) {
System.out.println("调用transfer方法 开始");
transfer(out, in, money);
System.out.println("调用transfer方法 结束");
}
}

AccountAction类

@RestController
public class AccountAction { @Autowired
private AccountService accountService; @RequestMapping("/transfer")
public String transfer(String out, String in, double money) {
accountService.transfer(out, in, money);
return "transfer";
} @RequestMapping("/transferProxy")
public String transferProxy(String out, String in, double money) {
accountService.transferProxy(out, in, money);
return "transfer";
}
}
  1. 通过transferProxy方法调用transfer方法时
    Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
    调用transfer方法 开始
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a0dbf4] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@6386ed [wrapping: com.mysql.jdbc.JDBC4Connection@9f2009]] will not be managed by Spring
    ==> Preparing: update account set money = money - ? where name = ?
    ==> Parameters: 100.0(Double), aaa(String)
    <== Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a0dbf4]

    发现没有开启事务

  2. 直接调用transfer方法时
    Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
    Creating a new SqlSession
    Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@238be2]
    JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@a502e0 [wrapping: com.mysql.jdbc.JDBC4Connection@3dbe42]] will be managed by Spring
    ==> Preparing: update account set money = money - ? where name = ?
    ==> Parameters: 100.0(Double), aaa(String)
    <== Updates: 1
    Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@238be2]
    Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@238be2]
    Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@238be2]

我们都知道Spring事务管理是通过AOP代理实现的,可是那么什么条件会使得AOP代理开启?通过查看

Sprin官方文档,发现只有把整个Service设为事务控制时,才会进行AOP代理。如果我们通过一个没有事务的transferProxy方法去调用有事务的transfer方法,是通过this引用进行调用,没有开启事务,即使发生了RuntimeException也不会回滚。

然后

Spring父子容器覆盖

Spring容器优先加载由ServletContextListener(对应applicationContext.xml)产生的父容器,而SpringMVC(对应mvc_dispatcher_servlet.xml)产生的是子容器。子容器Controller进行扫描装配时装配的@Service注解的实例是没有经过事务加强处理,即没有事务处理能力的Service,而父容器进行初始化的Service是保证事务的增强处理能力的。如果不在子容器中将Service exclude掉,此时得到的将是原样的无事务处理能力的Service,因为在多上下文的情况下,如果同一个bean被定义两次,后面一个优先。

当我们在applicationContext.xml,spring-mvc.xml都配置如下扫描包时,spring-mvc.xml中的service就会覆盖applicationContext.xml中的service。

context:component-scan base-package="net.morethink"/>

注意:当我们使用JUnit测试的时候,不会出现这种情况。

JUnit配置如下

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml", "classpath:dispatcher-servlet.xml"})
@WebAppConfiguration
public class AccountActionTest { protected MockMvc mockMvc;
@Autowired
protected WebApplicationContext wac; @Before() //这个方法在每个方法执行之前都会执行一遍
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc对象
} @Test
public void testTransfer() throws Exception {
String responseString = mockMvc.perform(
get("/transfer") //请求的url,请求的方法是get
.contentType(MediaType.APPLICATION_FORM_URLENCODED) //数据的格式
.param("out", "aaa")
.param("in", "bbb")
.param("money", "100")
).andExpect(status().isOk()) //返回的状态是200
// .andDo(print()) //打印出请求和相应的内容
.andReturn().getResponse().getContentAsString(); //将相应的数据转换为字符串
System.out.println(responseString);
} @Test
public void testTransferProxy() throws Exception {
String responseString = mockMvc.perform(
get("/transferProxy") //请求的url,请求的方法是get
.contentType(MediaType.APPLICATION_FORM_URLENCODED) //数据的格式
.param("out", "aaa")
.param("in", "bbb")
.param("money", "100")
).andExpect(status().isOk()) //返回的状态是200
// .andDo(print()) //打印出请求和相应的内容
.andReturn().getResponse().getContentAsString(); //将相应的数据转换为字符串
System.out.println(responseString);
}
}

可能因为JUnit不会产生父子容器。

还有可能是其它配置文件出错,例如,连接池配置为多例

参考文档

  1. Spring声明式事务为何不回滚
  2. Spring中@Transactional事务回滚(含实例详细讲解,附源码)
  3. 深入研究java.lang.ThreadLocal类
  4. Spring单实例、多线程安全、事务解析

Spring事务不回滚原因分析的更多相关文章

  1. Spring,SpringMvc配置常见的坑,注解的使用注意事项,applicationContext.xml和spring.mvc.xml配置注意事项,spring中的事务失效,事务不回滚原因

    1.Spring中的applicationContext.xml配置错误导致的异常 异常信息: org.apache.ibatis.binding.BindingException: Invalid ...

  2. Spring事务管理回滚问题

    Spring事务管理不能回滚问题 在前段时间学习SpringMVC的练习中,碰到声明式事务管理时,事务不能回滚的情况,通过查看博客和资料,解决了问题. 原因 导致Spring事务管理不能回滚的原因有两 ...

  3. Spring事务管理——回滚(rollback-for)控制

    探讨Spring事务控制中,异常触发事务回滚原理.文章进行了6种情况下的Spring事务是否回滚. 以下代码都是基于Spring与Mybatis整合,使用Spring声明式事务配置事务方法. 1.不捕 ...

  4. Spring事务异常回滚,捕获异常不抛出就不会回滚(转载) 解决了我一年前的问题

    最近遇到了事务不回滚的情况,我还考虑说JPA的事务有bug? 我想多了.......    为了打印清楚日志,很多方法我都加tyr catch,在catch中打印日志.但是这边情况来了,当这个方法异常 ...

  5. Spring事务异常回滚,捕获异常不抛出就不会回滚

    最近遇到了事务不回滚的情况,我还考虑说JPA的事务有bug? 我想多了.......    为了打印清楚日志,很多方法我都加tyr catch,在catch中打印日志.但是这边情况来了,当这个方法异常 ...

  6. 【转】Spring事务异常回滚,捕获异常不抛出就不会回滚

    最近遇到了事务不回滚的情况,我还考虑说JPA的事务有bug? 我想多了.......     为了打印清楚日志,很多方法我都加tyr catch,在catch中打印日志.但是这边情况来了,当这个方法异 ...

  7. Spring事务异常回滚

    最近遇到了事务不回滚的情况,我还考虑说JPA的事务有bug? 我想多了.......    为了打印清楚日志,很多方法我都加tyr catch,在catch中打印日志.但是这边情况来了,当这个方法异常 ...

  8. Spring3声明式事务处理事务无法回滚rollback分析(annotation与xml配置混用)

    新项目试运行,DBA提示生产数据库一个表的事务20分钟都未提交,分析过程如下: 1.查看日志log文件,最近20分钟是否有error日志: 2.发现某表有insert错误日志,初步判断由该表插入异常, ...

  9. SpringMVC+Spring 事务无法回滚的问题

    问题描述: Controller里面执行Service的方法,Service方法抛出异常,但是没有按照事务配置的方式回滚: Service的事务配置没有问题: 出现此问题的原因: 在springmvc ...

随机推荐

  1. Erlang内存吃紧之解决思路

    首先使用erlang:memory()确定是哪个部分内存吃紧,根据输出的内容,比对内存占用大小,有针对性地进行分析.在erlang系统里内存的单位为word,通过erlang:system_info( ...

  2. Python selenium自动化网页抓取器

    (开开心心每一天~ ---虫瘾师) 直接入正题---Python selenium自动控制浏览器对网页的数据进行抓取,其中包含按钮点击.跳转页面.搜索框的输入.页面的价值数据存储.mongodb自动i ...

  3. Java与算法之(4) - 数字全排列

    全排列是指n个数(或其他字符)所有可能的排列顺序,例如1 2 3三个数字的全排列是 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 那么问题来了,任意输入一个大于1的数字n,列 ...

  4. UVA 1030 - Image Is Everything【模拟+思维+迭代更新】

    题目链接:uva 1030 - Image Is Everything 题目大意:有一个最大为n*n*n的立方体的一个不规整立体,由若干个1*1*1的小正方体构成(每一个小正方体被涂成不同的颜色),给 ...

  5. [51nod1709]复杂度分析

    给出一棵n个点的树(以1号点为根),定义dep[i]为点i到根路径上点的个数.众所周知,树上最近公共祖先问题可以用倍增算法解决.现在我们需要算出这个算法精确的复杂度.我们定义计算点i和点j最近公共组先 ...

  6. [bzoj1242] Zju1015 Fishing Net弦图判定

    弦图判定..MCS算法. 先选一个点,然后每次拿 相邻已选点最多 的未选点. 选完之后判断一下是否是完美消除序列. #include<cstdio> #include<iostrea ...

  7. B. Gerald is into Art

    B. Gerald is into Art time limit per test 2 seconds memory limit per test 256 megabytes input standa ...

  8. Elasticsearch介绍,一些概念的笔记

    Elasticsearch,分布式,高性能,高可用,可伸缩的搜索和分析系统 什么是搜索? 如果用数据库做搜索会怎么样? 什么是全文检索和Lucene? 什么是Elasticsearch? Elasti ...

  9. windows 命令直接搜索局域网计算机的ip

    以前都不知道还可以这样.....孤陋寡闻了... cmd 中 输入 net view ,搜索局域网或域中的计算机名. 找到要查询ip地址的计算机名后右键 标记,接着ping 一下,要用 -4 这个参数 ...

  10. 编写自己的JavaScript方法库

    下面列出了我在项目中经常使用到的一些方法,这些方法可以很方便的提高我们的工作效率,代码在GitHub上面,点击目录就可以跳转了,欢迎大家通过fork,改编和优化成自己的JavaScript方法库. 目 ...