目录
手动操作
声明式提交
注解的属性
事务隔离级别
事务传播机制
事务可以将一组操作封装成为一个单元,一组操作要么全部成功,要么全部失败
Mysql中操作事务,有三个步骤;
1、start transaction ;开启事务
2、commit;提交事务
3、rollback;回滚事务
Spring操作事务,可以分为手动操作和声明式自动提交
手动操作
SpringBoot内置了两个对象,DataSourceTransactionManager用户获取事务、开启事务、,提交事务、回滚事务。TransactionDefinition是事务的属性
@Controller
@RequestMapping("/user")
public class InsertUserController {
@Autowired
InsertUserService insertUserService;
//用于管理事务
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
private TransactionDefinition transactionDefinition;
@RequestMapping("/insert")
@ResponseBody
public int insert(String name, String password) {
//开启一个事务
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
//插入数据库
int i = insertUserService.insert(name, password);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
//回滚事务
// dataSourceTransactionManager.rollback(transactionStatus);
return i;
}
}
在启用事务提交后,插入数据成功
如果启用事务回滚,可以看到返回的是1,但是数据库查找不到这个数据(因为事务回滚,删除了新增的数据)
声明式提交
使用@Transactional注解声明方法,这个方法就可以自动开启事务,自动提交事务,遇到没有处理的异常,就可以自动回滚事务
@Transactional注解通过AOP实现,也就是基于动态代理实现。在执行业务代码之前,调用代理类的方法,开启事务,如果执行成功,提交事务,否则回滚事务
@Transactional可以用于修饰类或者方法
1、修饰方法时,只能用于public方法,否则不会生效
2、修饰类时,表示对注解类的所有public方法生效
@Controller
@RequestMapping("/user")
public class InsertUserAutoController {
@Autowired
InsertUserService insertUserService;
@RequestMapping("/autoinsert")
@ResponseBody
@Transactional
public int insert(String name, String password) {
int i = insertUserService.insert(name, password);
// System.out.println(1 / 0);
return i;
}
}
在没有引入异常时,事务自动提交
在引入异常之后,并且没有手动处理这个异常,那么会自动回滚事务
调试发现,i=1,但是调试结束之后,发现数据没有插入,也就是发生了事务回滚
如果异常被处理了,这时spring会认为我们会自己处理业务,执行回滚操作,所以不会进行事务回滚
@Controller
@RequestMapping("/user")
public class InsertUserAutoController {
@Autowired
InsertUserService insertUserService;
@RequestMapping("/autoinsert")
@ResponseBody
@Transactional()
public int insert(String name, String password) {
int i = insertUserService.insert(name, password);
try {
System.out.println(1 / 0);
} catch (Exception e) {
System.out.println(e.getMessage());
}
return i;
}
}
插入成功
这时,有两种解决办法,手动抛出异常,手动回滚事务
1、手动抛出异常
@Controller
@RequestMapping("/user")
public class InsertUserAutoController {
@Autowired
InsertUserService insertUserService;
@RequestMapping("/autoinsert")
@ResponseBody
@Transactional()
public int insert(String name, String password) {
int i = insertUserService.insert(name, password);
try {
System.out.println(1 / 0);
} catch (Exception e) {
System.out.println(e.getMessage());
throw e;//手动抛出异常
}
return i;
}
}
2、手动回滚事务
@Controller
@RequestMapping("/user")
public class InsertUserAutoController {
@Autowired
InsertUserService insertUserService;
@RequestMapping("/autoinsert")
@ResponseBody
@Transactional()
public int insert(String name, String password) {
int i = insertUserService.insert(name, password);
try {
System.out.println(1 / 0);
} catch (Exception e) {
System.out.println(e.getMessage());
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return i;
}
}
注解的属性
@Transactional 注解的关键属性大致有九个:
readOnly(事务读写性)
默认情况下是 false(即:不指定只读性),设置为 true 的含义是: 告诉程序该方法下使用的是只读操作,如果进行其他非读操作,则会抛出异常。
如果一次执行多次查询,这时为了保证数据整体的一致性,要用只读事务。
noRollbackFor 和 noRollbackForClassName(遇到时不回滚)
用来指明不回滚的条件是哪些异常类或者异常类名。
rollbackFor 和 rollbackForClassName(遇到时回滚)
用来指明回滚的条件是哪些异常类或者异常类名。
timeout(超时时间)
用于设置事务处理的时间长度,阻止可能出现的长时间的阻塞系统或者占用系统资源,单位为秒,超出设置时间,会抛出异常TransactionTimedOutException
默认值是-1,表示没有设置超时时间
value(指定使用的事务管理器)
value 主要用来指定不同的事务管理器,主要用来满足在同一个系统中,存在不同的事务管理器的场景需要。
事务隔离级别
事务的隔离级别,可以让多个并发运行的事务更加稳定,减少并发问题
在mysql中,有四种隔离级别:读未提交,读已提交,可重复读,可串行化
而在spring中,有五种隔离级别,由isolation属性设置
Isolation.DEFAULT:使用连接数据库中默认的隔离级别【默认】
Isolation.READ_UNCOMMITTED:读取未提交数据
Isolation.READ_COMMITTED:读取已提交数据
Isolation.REPEATABLE_READ:可重复读
Isolation.SERIALIZABLE:串行化
1、当Spring中设置的隔离级别和数据库的隔离级别有区别时,以Spring中设置的隔离级别为准
2、Spring中隔离级别的实现以连接的数据库的隔离级别的支持为基础
事务传播机制
事务传播机制表示了多个包含了事务的方法在相互调用时,事务在这些方法中如何传递
事务隔离级别用于保证多个事务并发执行的稳定性;事务传播机制用于保证一个事务在多个调用方法之间的稳定性
Spring中定义了“七个”表示隔离级别的值,由propagation属性设置
1、Propagation.REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务,加入这个事务。是Spring默认的传播机制
2、Propagation.SUPPORTS:如果当前有事务,就以事务方式执行;如果当前没有事务,就以非事务方式执行
3、Propagation.MANDATORY:如果已经存在一个事务,加入这个事务。如果当前不存在事务,抛出异常
4、Propagation.REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
5、Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起,非事务方式的方法完毕,继续执行挂起的事务
6、Propagation.NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
7、Propagation.NESTED:如果当前存在事务,则创建一个新的事务作为这个事务的嵌套事务来运行;如果当前没有事务,则创建一个新的事务
1、默认级别(Propagation.REQUIRED):如果当前没有事务,就新建一个事务,如果已经存在一个事务,加入这个事务
@Controller
@RequestMapping("/user")
public class InsertUserAutoController {
@Autowired
InsertUserService insertUserService;
@RequestMapping("/autoinsert")
@ResponseBody
public void insert(String name, String password) throws InterruptedException {
//一个增加 user表的事务
insertUserService.insert(name, password);
}
}
@Service
public class InsertUserService {
@Autowired
InsertUserMapper insertUserMapper;
@Autowired
InsertLogService insertLogService;
@Transactional()
public int insert(@Param("user_name") String user_name, @Param("user_password") String user_password) {
int i = insertUserMapper.insert(user_name, user_password);
System.out.println("添加User表的数据的行数" + i);
java.util.Date day = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//一个增加 日志表的事务
insertLogService.insert(user_name, sdf.format(day));
return i;
}
}
@Service
public class InsertLogService {
@Autowired
InsertBlogMapper insertBlogMapper;
@Transactional()
public int insert(String name, String time) {
int i = insertBlogMapper.insert(name, time);
System.out.println("添加日志表的数据的行数" + i);
System.out.println(1 / 0);
return i;
}
}
程序执行的过程:调用InsertUserAutoController类-》调用user表的数据新增方法-》调用log表的数据新增方法
因为采用默认的传播机制,InsertUserService类中创建一个事务之后,调用InsertLogService类的方法,在log表新增数据,此时不会创建新的事务,而是会进入之前InsertUserService类中创建的事务,但是InsertLogService类中方法在新增数据时报错,导致事务回滚,因为是同一个事务,所以两个表的数据都会被回滚
查询表数据,数据被回滚
2、Propagation.SUPPORTS:如果当前有事务,就以事务方式执行;如果当前没有事务,就以非事务方式执行
@Service
public class InsertLogService {
@Autowired
InsertBlogMapper insertBlogMapper;
@Transactional(propagation = Propagation.SUPPORTS)
public int insert(String name, String time) {
int i = insertBlogMapper.insert(name, time);
System.out.println("添加日志表的数据的行数" + i);
System.out.println(1 / 0);
return i;
}
}
@Controller
@RequestMapping("/user")
public class InsertUserAutoController {
@Autowired
InsertLogService insertLogService;
@RequestMapping("/autoinsert")
@ResponseBody
public void insert(String name, String password) throws InterruptedException {
java.util.Date day = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//一个增加 日志表的事务
insertLogService.insert(name, sdf.format(day));
}
}
程序执行的过程:调用InsertUserAutoController类-》调用log表的数据新增方法;
InsertLogService采用的传播机制是SUPPORTS,所以log表新增操作此时是以非事务的方式运行的,不会造成事务回滚
3、Propagation.MANDATORY:如果已经存在一个事务,加入这个事务。如果当前不存在事务,抛出异常
4、Propagation.REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Controller
@RequestMapping("/user")
public class InsertUserAutoController {
@Autowired
InsertUserService insert;
@Autowired
InsertLogService insertLogService;
@RequestMapping("/autoinsert")
@ResponseBody
public void insert(String name, String password) {
//一个增加 user表的事务
insert.insert(name, password);
java.util.Date day = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//一个增加 日志表的事务
insertLogService.insert(name, sdf.format(day));
}
}
@Service
public class InsertLogService {
@Autowired
InsertBlogMapper insertBlogMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insert(String name, String time) {
int i = insertBlogMapper.insert(name, time);
System.out.println("添加日志表的数据的行数" + i);
System.out.println(1/0);
return i;
}
}
@Service
public class InsertUserService {
@Autowired
InsertUserMapper insertUserMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insert(@Param("user_name") String user_name, @Param("user_password") String user_password) {
int i = insertUserMapper.insert(user_name, user_password);
System.out.println("添加User表的数据的行数" + i);
return i;
}
}
程序执行的过程:调用InsertUserAutoController类-》调用user表的数据新增方法,调用log表的数据新增方法;
采用REQUIRES_NEW这个传播机制,user表新增会创建一个事务,在log表新增也会创建一个事务,此时,log表新增报错,事务回滚,但是user表不会事务回滚
5、Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起
当InsertLogService采用NOT_SUPPORTED传播级别,而InsertUserService采用默认级别时
程序执行的过程:调用InsertUserAutoController类-》调用user表的数据新增方法-》调用log表的数据新增方法;
采用NOT_SUPPORTED传播机制,InsertUserService中会创建一个事务,在InsertLogService会挂起存在的事务,InsertLogService的方法会以非事务方式运行,运行结束后恢复InsertUserService的事务
6、Propagation.NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
7、Propagation.NESTED:如果当前存在事务,则创建一个新的事务作为这个事务的嵌套事务来运行;如果当前没有事务,则创建一个新的事务
@Service
public class InsertUserService {
@Autowired
InsertUserMapper insertUserMapper;
@Autowired
InsertLogService insertLogService;
@Transactional(propagation = Propagation.NESTED)
public int insert(@Param("user_name") String user_name, @Param("user_password") String user_password) {
int i = insertUserMapper.insert(user_name, user_password);
System.out.println("添加User表的数据的行数" + i);
java.util.Date day = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//一个增加 日志表的事务
insertLogService.insert(user_name, sdf.format(day));
return i;
}
}
@Service
public class InsertLogService {
@Autowired
InsertBlogMapper insertBlogMapper;
@Transactional(propagation = Propagation.NESTED)
public int insert(String name, String time) {
int i = insertBlogMapper.insert(name, time);
System.out.println("添加日志表的数据的行数" + i);
System.out.println(1/0);
return i;
}
}
@Controller
@RequestMapping("/user")
public class InsertUserAutoController {
@Autowired
InsertUserService insertUserService;
@RequestMapping("/autoinsert")
@ResponseBody
public void insert(String name, String password) {
//一个增加 user表的事务
insertUserService.insert(name, password);
}
}
程序执行的过程:调用InsertUserAutoController类-》调用user表的数据新增方法-》调用log表的数据新增方法;
InsertUserService采用NESTED传播机制,创建了一个事务,在 InsertLogService类中使用 @Transactional()创建了一个事务,这个事务会作为InsertUserService开启的事务的嵌套事务来运行
当InsertLogService事务出现异常,事务回滚,这时,会继续向上查找调用InsertLogService方法的事务,对其进行回滚,所以,两个表都不会插入数据
REQUIRED和NESTED两种传播机制的区别:
NESTED:如果当前存在事务,则创建一个新的事务作为这个事务的嵌套事务来运行;如果当前没有事务,则创建一个新的事务
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务,加入这个事务
两个实际上都可以实现A,B两个事务的关联,只不过NESTED是A包含B,REQUIRED是AB一起
在NESTED中,在一个保存点,如果B事务手动回滚,会保留到B事务之前的状态,也就是不影响A事务。而REQUIRED没有这种机制。所以,NESTED可以实现部分事务回滚
@Service
public class InsertLogService {
@Autowired
InsertBlogMapper insertBlogMapper;
@Transactional(propagation = Propagation.NESTED)
public int insert(String name, String time) {
int i = insertBlogMapper.insert(name, time);
System.out.println("添加日志表的数据的行数" + i);
try {
System.out.println(1 / 0);
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return i;
}
}
insertUserService类和insertLogService类的事务传播级别都是NESTED,而insertLogService中实现了手动回滚,回到了保留点,不会影响insertUserService的事务