Spring 下默认事务机制中@Transactional 无效的原因
Posted 衣舞晨风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 下默认事务机制中@Transactional 无效的原因相关的知识,希望对你有一定的参考价值。
1、Spring中 @Transactional 注解的限制
1、 同一个类中, 一个nan-transactional的方法去调用transactional的方法, 事务会失效
If you use (default) Spring Proxy AOP, then all AOP functionality provided by Spring (like @Transational) will only be taken into account if the call goes through the proxy. – This is normally the case if the annotated method is invoked from another bean.
2、在private方法上标注transactional, 事务无效
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
2、测试代码
TestCase01.java
package com.rockbb.test.api.service;
public interface TestCase01
void init();
void clean();
void txnInLocalPrivate();
void txnInLocalPublic();
void txnInOpenPublic();
void txnInOpenPublicByInvokePrivate();
void txnInOpenPublicByInvokePublic();
TestCase01Impl.java
package com.rockbb.test.impl.service.impl;
import com.rockbb.test.api.dto.AccountDTO;
import com.rockbb.test.api.dto.AccountDetailDTO;
import com.rockbb.test.api.service.TestCase01;
import com.rockbb.test.impl.mapper.AccountDetailMapper;
import com.rockbb.test.impl.mapper.AccountMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
@Repository("testCase01")
public class TestCase01Impl implements TestCase01
@Resource(name="accountMapper")
private AccountMapper accountMapper;
@Resource(name="accountDetailMapper")
private AccountDetailMapper accountDetailMapper;
@Resource(name="testCase01")
private TestCase01 testCase01;
/**
* 无效, 未回退
*
* 结论: 在私有方法上加事务注解无效
*/
@Override
public void txnInLocalPrivate()
localPrivate();
/**
* 无效, 未回退
*
* 结论: 在公有方法上事务注解, 再通过接口方法调用, 无效
*/
@Override
public void txnInLocalPublic()
localPublic();
/**
* 有效, 无论下面调用的是否是私有方法
*
* 结论: 在接口方法上加事务, 无论下面调用的是否是私有方法, 都有效
*/
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void txnInOpenPublic()
localPrivate();
@Override
public void txnInOpenPublicByInvokePrivate()
/**
*
* 结论: 普通接口方法直接调用同类带事务的方法, 无效. 通过接口调用则有效
*/
@Override
public void txnInOpenPublicByInvokePublic()
//txnInOpenPublic(); // 无效
testCase01.txnInOpenPublic(); // 有效
@Override
public void init()
accountMapper.truncate();
for (int i = 0; i < 10; i++)
BigDecimal amount = BigDecimal.valueOf(i * 10);
add(String.valueOf(i), BigDecimal.ZERO);
increase(String.valueOf(i), BigDecimal.valueOf(100 + i));
@Override
public void clean()
accountMapper.truncate();
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
private void localPrivate()
AccountDTO dto = new AccountDTO().initialize();
dto.setId("test");
dto.setAmount(BigDecimal.ZERO);
accountMapper.insert(dto);
throw new RuntimeException("localPrivate");
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void localPublic()
AccountDTO dto = new AccountDTO().initialize();
dto.setId("test");
dto.setAmount(BigDecimal.ZERO);
accountMapper.insert(dto);
throw new RuntimeException("localPublic");
public void openPublic()
AccountDTO dto = new AccountDTO().initialize();
dto.setId("test");
dto.setAmount(BigDecimal.ZERO);
accountMapper.insert(dto);
throw new RuntimeException("openPublic");
private int add(String id, BigDecimal amount)
AccountDTO dto = new AccountDTO().initialize();
dto.setId(id);
dto.setAmount(amount);
return accountMapper.insert(dto);
private int increase(String id, BigDecimal amount)
AccountDTO dto = accountMapper.select(id);
AccountDetailDTO detail = new AccountDetailDTO().initialize();
detail.setAmount(amount);
detail.setPreAmount(dto.getAmount());
dto.setAmount(dto.getAmount().add(amount));
detail.setPostAmount(dto.getAmount());
if (accountMapper.update(dto) != 1)
throw new RuntimeException();
return accountDetailMapper.insert(detail);
TestCase02.java
package com.rockbb.test.api.service;
public interface TestCase02
void txnInOpenPublicByPublic();
void txnInOpenPublicByPrivate();
TestCase02Impl.java
package com.rockbb.test.impl.service.impl;
import com.rockbb.test.api.service.TestCase01;
import com.rockbb.test.api.service.TestCase02;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository("testCase02")
public class TestCase02Impl implements TestCase02
@Resource(name="testCase01")
private TestCase01 testCase01;
/**
* 有效
*
* 结论: 在接口方法上加事务, 再被他类的接口方法调用, 无论此接口方法是否加事务, 都有效
*/
@Override
public void txnInOpenPublicByPublic()
testCase01.txnInOpenPublic();
/**
* 有效
*
* 结论: 在接口方法上加事务, 再被他类的私有方法调用后, 依然有效
*/
@Override
public void txnInOpenPublicByPrivate()
localPrivate();
private void localPrivate()
testCase01.txnInOpenPublic();
测试样例 AccountCheckTest.java
package com.rockbb.test.test;
import com.rockbb.test.api.service.TestCase01;
import com.rockbb.test.api.service.TestCase02;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/spring/spring-commons.xml")
public class AccountCheckTest
private static final Logger logger = LoggerFactory.getLogger(AccountCheckTest.class);
@Resource(name="testCase01")
private TestCase01 testCase01;
@Resource(name="testCase02")
private TestCase02 testCase02;
@Test
public void test01()
testCase01.init();
try
testCase01.txnInLocalPrivate();
catch (Exception e)
logger.error(e.getMessage(), e);
@Test
public void test02()
testCase01.init();
try
testCase01.txnInLocalPublic();
catch (Exception e)
logger.error(e.getMessage(), e);
@Test
public void test03()
testCase01.init();
try
testCase01.txnInOpenPublic();
catch (Exception e)
logger.error(e.getMessage(), e);
@Test
public void test04()
testCase01.init();
try
testCase02.txnInOpenPublicByPublic();
catch (Exception e)
logger.error(e.getMessage(), e);
@Test
public void test05()
testCase01.init();
try
testCase02.txnInOpenPublicByPrivate();
catch (Exception e)
logger.error(e.getMessage(), e);
@Test
public void test06()
testCase01.init();
try
testCase01.txnInOpenPublicByInvokePublic();
catch (Exception e)
logger.error(e.getMessage(), e);
3、测试用例情况总结
- @Transactional 加于private方法, 无效
- @Transactional 加于未加入接口的public方法, 再通过普通接口方法调用, 无效
- @Transactional 加于未加入接口的public方法, 再通过它类调用, 有效
- @Transactional 加于接口方法, 无论下面调用的是private或public方法, 都有效
- @Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效
- @Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效
- @Transactional 加于接口方法后, 被它类的接口方法调用, 有效
- @Transactional 加于接口方法后, 被它类的私有方法调用后, 有效
总结: Transactional是否生效, 仅取决于是否加载于接口方法, 并且是否通过接口方法调用(而不是本类调用)
4、@Transactional(readOnly = true) 的含义
执行单条查询语句时, 数据库默认支持SQL执行期间的读一致性. 而执行多条查询语句(例如统计查询, 报表查询)时, 多条查询SQL必须保证整体的读一致性, 否则在每条查询之间数据可能发生变动, 导致最终的查询结果出现数据不一致的错误,此时应该启用事务支持.
read-only="true"表示该事务为只读事务, 上面的多条查询可以使用只读事务. 由于只读事务不存在对数据的修改, 因此数据库将会为只读事务提供一些优化手段, 例如Oracle对于只读事务不启动回滚段, 不记录回滚log.
在JDBC中指定只读事务的办法为 connection.setReadOnly(true), Hibernate中指定只读事务的办法为 session.setFlushMode(FlushMode.NEVER). 在Spring的Hibernate封装中,指定只读事务的办法为bean配置文件中 prop属性增加“read-Only”. 或者用注解方式@Transactional(readOnly=true)
在将事务设置成只读后, 相当于将数据库设置成只读数据库, 此时若要进行写的操作会报错.
5、个人思考
Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.
Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. By default, CGLIB is used if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes, business classes normally implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface or where you need to pass a proxied object to a method as a concrete type.
摘自:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop
从Spring官方文档中可以看出, Spring会灵活使用JDK dynamic proxies或者CGLIB proxies。
比如(Spring默认情况AOP Proxies,而非强制启用CGLIB):
// 实例A
@Autowired
AccountServiceImpl accountServiceImpl;
// 实例B
@Autowired
AccountService accountService;
其中AccountServiceImpl类实现了AccountService接口
对于实例A注入用的是基于JDK的代理,因为这里注入的是接口;
实例B注入用的就是CGLIB的代理,因为这里注入的实例。
虽然代理方式不同,但只要是被代理方法上的@Transactional标识,都会生效。
- JDK代理的接口
- CGLIB代理的pulic方法
原文地址:https://www.cnblogs.com/milton/p/6046699.html
个人微信公众号:
作者:jiankunking 出处:http://blog.csdn.net/jiankunking
以上是关于Spring 下默认事务机制中@Transactional 无效的原因的主要内容,如果未能解决你的问题,请参考以下文章