spring 事务部分共5篇文章
第一篇:spring中@EnableTransactionManager @Transactional注解实现机制
第二篇:spring boot中事务功能自动配置的加载过程分析
第三篇:spring@Transactional注解属性字段含义
第四篇:spring @Transactional如何测试事务注解生效
第五篇:spring及spring boot中事务相关知识的总结
本篇为第三篇
@Transactional注解常用属性
参数名称 | 功能描述 |
---|---|
readOnly | 该属性用于设置当前事务是否是只读事务,设置为true表示只读,false则表示可读写,默认值为false |
rollbackFor | 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class,Exception.class}) |
rollbackForClassName | 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如 指定单一异常类:@Transactional(rollbackFor="RuntimeException") 指定多个异常类:@Transactional(rollbackFor={"RuntimeException","Exception"}) |
noRollbackFor | |
noRollbackForClassName | |
propagation | 该属性用于设置事务的传播行为 例如@Transactional(propagation=Propagation.NOT_SUPPORTED, readOnly=true) |
isolation | 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
timeout | 该属性用于设置事务的超时秒数,默认值为-1,表示永不超时 |
transactionManager/value | 指定transactionManager,当有多个datasource的时候 |
readonly作用
官方给出的作用是:This is just a hint to the actual transaction subsystem; it does not necessarily cause the write access attempt to fail. A transaction manager that cannot interpret read-only prompts No exception is thrown when a read-only transaction is requested.
翻译过来就是:这实际上是给实际执行事务的子系统的一个提示,并不一定会导致写失败(这里可以认为如果只读事务生效,那么在这个事务为commit前,其他事务write操作是不允许的)。如果事务管理器不能正确理解只读提示,那么也不会抛出异常
参考知乎上的问题:@Transactional(readOnly=true)意义
“由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,例如Oracle对于只读事务,不启动回滚段,不记录回滚log。
(1)在JDBC中,指定只读事务的办法为: connection.setReadOnly(true);
(2)在Hibernate中,指定只读事务的办法为: session.setFlushMode(FlushMode.NEVER);
此时,Hibernate也会为只读事务提供Session方面的一些优化手段
(3)在Spring的Hibernate封装中,指定只读事务的办法为: bean配置文件中,prop属性增加“read-Only”
或者用注解方式@Transactional(readOnly=true)
Spring中设置只读事务是利用上面两种方式(根据实际情况)
在将事务设置成只读后,相当于将数据库设置成只读数据库,此时若要进行写的操作,会出现错误(指的是在这个只读事务内进行写操作,会报错Cannot execute statement in a READ ONLY transaction)。”
mysql支持只读事务
start transaction read only
1. 只读事务内,不能增加、修改、删除内容,否则报Cannot execute statement in a READ ONLY transaction。
2. 只读事务内,只能读取到执行时间点前的内容,期间修改的内容不能读取到(这个是隔离级别决定的,从下面的例子可以看出read uncommitted和read committed都可能读到)。
3. 只读事务作为ORM框架优化执行的一个暗号,比如放弃加锁,或者flush never。
7个传播行为propagation
事务的传播行为是指:如果在开始当前事务之前,一个事务上下文已经存在,此时可以有若干选项来指定一个事务性方法的执行行为。
-
REQUIERD:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
默认就是Propagation.REQUIRED
-
SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
-
MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
-
REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起
-
NOT_SUPPORTED:以非事务方式运行,如果存在当前事务,则把当前事务挂起
-
NEVER:以非事务方式运行,如果存在当前事务,则抛出异常
-
NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则该取值等价于REQUIRED
4个隔离级别isolation
-
DEFAULT:表示使用底层数据库的默认隔离级别。对大部分数据库而言,READ_COMMITED
-
READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别
-
READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
-
REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读
-
SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下不会用到该级别
测试
我自己测试了一下,在mysql下如果只是查询,似乎加不加readonly似乎没有区别,结果都是其执行时数据库查询出的实际结果。
数据库中ACID中的I就代表isolation,其是由锁机制来保证的。
在我们实际写代码的时候isolation怎么会起作用呢?下面就用一个例子来从代码角度说明指定isolation的作用
模拟两个线程并发的情况
线程1:执行一个分页查询的操作,查询最后一页的内容
@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)
public Page<Employee> selectPageEmployee(Employee employee){
Page<Employee> employees = employeeRepository.findAll(getEmployeeWhereClause(employee),
new PageRequest(employee.getPageNum() - 1,
employee.getPageSize(), new Sort(Sort.Direction.DESC, "id")));
System.out.println("employee: "+employees.getContent().size());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Page<Employee> employees2 = employeeRepository.findAll(getEmployeeWhereClause(employee),
new PageRequest(employee.getPageNum() - 1,
employee.getPageSize(), new Sort(Sort.Direction.DESC, "id")));
System.out.println("employee2: "+employees2.getContent().size());
return employees2;
}
线程2:执行新增操作
// @Transactional
public Employee addEmp(Employee employee) {
Employee save = employeeRepository.save(employee);
return save;
}
过程是:先执行线程1,然后在代码里让其sleep 10秒,期间执行线程2,最后看不同隔离级别下线程1的结果
read uncommitted
该隔离级别下存在脏读问题,所以第一次读没有读到,但第二次读读到了,哪怕是线程2后面rollback掉
read committed
该隔离级别下存在不可重复读问题,第一次和第二次读到的内容不同,但是其可以保证其读到的是线程2已经commit的内容
repeatable read
第一次和第二次读到的内容相同,都是线程2 commit之前的内容,即在此隔离级别下,不存在脏读和不可重复读的问题
注意
这里我们使用的spring data jpa,其默认实现是SimpleJpaRepository,其方法已经加了@Transactional注解,所以像线程2的service完全可以不写@Transactional注解