Spring 下默认事务机制中@Transactional 无效的原因
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.
测试代码
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); } } }
结论
- @Transactional 加于private方法, 无效
- @Transactional 加于未加入接口的public方法, 再通过普通接口方法调用, 无效
- @Transactional 加于接口方法, 无论下面调用的是private或public方法, 都有效
- @Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效
- @Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效
- @Transactional 加于接口方法后, 被它类的接口方法调用, 有效
- @Transactional 加于接口方法后, 被它类的私有方法调用后, 有效
- 总结: Transactional是否生效, 仅取决于是否加载于接口方法, 并且是否通过接口方法调用(而不是本类调用)
@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)
在将事务设置成只读后, 相当于将数据库设置成只读数据库, 此时若要进行写的操作会报错.
相关文章
- python sys模块
- Step-by-Step 快速上手 AWS IoT OTA 固件升级
- 【Java】Exception in thread "main" java.lang.UnsupportedClassVersionError
- 降低AWS Lambda 冷启动时间的4种方案
- 欢迎参加 2021 年 AWS 存储日
- 新增功能 – Amazon FSx for NetApp ONTAP
- 新增功能 — Amazon EFS 智能分层通过优化不断变化的访问模式优化工作负载成本
- Python Twisted介绍
- 用机器学习解码媒体的社交影响
- 一文看懂 Amazon EKS 中的网络规划
- AppSync调试方法
- Amazon Managed Grafana 现已全面推出,并具有许多新功能
- AWS CloudFormation 的新功能 – 从故障点快速重试堆栈操作
- 使用更具体的 Amazon VPC 路由检查子网到子网的流量
- Amazon Textract 更新:8 个亚马逊云科技区域的价格降幅达 32%,异步任务处理时间缩短近 50%
- Announcing the latest AWS Heroes – August 2021
- 云原生编排数据分析管道初探
- 使用托管节点组结合启动模板简化EKS升级与运维
- 如何将您的自定义容器镜像导入Amazon SageMaker Studio notebooks
- 如何注册成为亚马逊云科技 Marketplace海外区卖家