开篇:总体战线拉的有些长,所以没有面面俱到,但有时间的话,后续文章会补上
环境介绍:win7,mysql5.6 ,mysql是新安装的,如果没有mysql的话,可以下载我上篇分享的链接,中间有段插曲,叙述如下,代码集成完毕,但是事物是真的不生效,思来想去,想到mysql的储存引擎是否是innodb,命令如下
show ENGINES; //显示支持的储存引擎,mysam不支持事物。
ALTER TABLE user
ENGINE = InnoDB; 使用mysql innodb储存引擎。
重试单元测试,可以正常事物回滚了。
原理简述:spring事物是基于aop事物增强,aop底层实现俩种方式 一种是jdk,另一种是cglib
我们知道spring事物是基于动态代理实现,动态代理会有拦截器,对目标类进行处理,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器进行提交或者回滚,主要设计几个类如下
TransactionManager
TransactionDefinition,定义了事物隔离级别等
TransactionAttribute继承了TransactionDefinition
TransactionStatus
哪些方法能进行动态代理?不能代理意味着事物不生效
jdk:接口中的方法都是public ,所有只能是 public 或 public final,同时不能使用static
cglib:不能使用 final,static 和 private 修饰符 ,因为这些修饰符不能被子类覆盖,cglib基于扩展子类来实现代理
为什么Transactional 只能应用到 public 方法才有效 ?
源码执行流程,
事物拦截器生产代理 --》invoke–》invokeWithinTransaction–》getTransactionAttribute–》computeTransactionAttribute
正确的设置@Transactional 的 rollbackFor 属性,Transactional默认为运行时异常,如果不指定异常,那么非运行时异常不会回滚 测试方法3
如果传播级别设置不对,也会发生事物不生效。
下面给出测试案例:
package com.example.demo.service.impl;
import com.example.demo.entity.Customer;
import com.example.demo.entity.User;
import com.example.demo.resp.UserRepository;
import com.example.demo.service.CustomerService;
import com.example.demo.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceImplTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Autowired
CustomerService customerService;
/**
* 测试 事物是否正常,数据库表中的数据为空
* 确实测试出问题了,再确定代码无误的情况下,请考虑数据库的存储引擎
*/
@Test
public void testSave() {
User user = new User();
user.setName("hhhh4");
user.setEmail("nihao@163.com4");
userService.save(user);
}
/**
* 事物正常回滚
* save中为运行时异常 int i=1/0
*/
@Test
public void testSave1() {
User user = new User();
user.setName("hhhh4");
user.setEmail("nihao@163.com4");
userService.save(user);
}
/**
* 同一个类中save方法没有事物的方法,save2有事物的方法
* 事物不会发生回滚,事物被忽略
* <p>
* 原因:只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理
*/
@Test
public void testSave5() {
User user = new User();
user.setName("hhhh5");
user.setEmail("nihao@163.com5");
userService.save(user);
}
/**
* 同一个类中俩个方法都有事物
* 会发生回滚
*/
@Test
public void testSave6() {
User user = new User();
user.setName("hhhh6");
user.setEmail("nihao@163.com6");
userService.save(user);
}
/**
* 同一个类中 save 方法有事物,save2 没有事物
* 会发生回滚
*/
@Test
public void testSave7() {
User user = new User();
user.setName("hhhh7");
user.setEmail("nihao@163.com7");
userService.save(user);
}
/**
* 测试方法3
*/
@Test
public void testSave8() throws IllegalAccessException {
User user = new User();
user.setName("hhhh8");
user.setEmail("nihao@163.com8");
userService.save3(user);
}
@Test
public void testSave9() throws IllegalAccessException {
User user = new User();
user.setName("hhhh8");
user.setEmail("nihao@163.com8");
userService.save3(user);
}
/**
* 重点 应该由外部直接调用俩个方法,避免方法2 没有被增强
* 事物2 方法2中有事物注解,但是没有被增强,所以事物并没有被回滚
*/
@Test
public void testSave10() {
customerService.save();
}
}
参考文章:https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
参考书籍:精通企业spring5.x
工程地址如下:https://github.com/ylha/spring-tx.git,看着很简单吧? 自己试试 哈哈。总结的话都是测试中说了。