1、基本概念
1.1、事务的基本概念
事务:事务是数据库操作的基本单元,逻辑上一组SQL语句要么都执行成功、要么都执行失败!事务有四大特性:原子性、一致性、隔离性、持久性。
原子性:原子是物质的最小组成单位、原子性是只整个事务中所有的SQL语句要么都执行成功,要么都失败!
一致性:事务执行前后数据库中的状态是一致的;例如:A账户有1000元、B账户有1000元;现在A给B转200块钱,执行前总共2000、执行后还是2000。
隔离性:当进行多个事务时,每个事务之间互不造成影响、相互隔离。
持久性:当事务成功提交后,它对数据库中数据的改变就是永久性的。
1.2、Spring两大事务
编程式事务:所谓的编程式事务就是直接在源代码中进行增加手动提交、回滚的代码,有点面向过程编程的味道。
声明式事务:Spring的两大核心就是IOC与AOP,声明式事务是基于AOP实现的;通过增强通知(Advice)切入到方法中,从而达到控制事务的开启、回滚、提交…
区别:编程式事务需要在代码中嵌入事务管理的部分、声明式事务只需要声明在哪里需要进行事务,剩下的交给AOP!
2、声明式事务配置参数
声明式事务配置参数是比较多的,功能非常强大;提供了:propagation(传播行为)、ioslation(隔离级别)、timeout(超时时间)、read-only(是否可读)、rollbackfor(异常回滚)、norollbackfor(异常不回滚)
2.1、传播行为(Propagation)
传播行为:指多个方法方法之间调用,事务是如何进行管理运作。Spring提供了七种事务的传播行为,最常用的是REQUIRED 和 REQUIRED_NEW。
2.2、隔离级别(Ioslation)
-
脏读:一个事务读取到了另一个事务未成功提交的数据;例如:事务A对数据库中的数据进行了修改但是还未提交、此时事务B进行数据读取;但是事务A因为某些原因回滚了,此时B拿到的数据是一个无效数据,也叫脏数据!
-
不可重复读:同一个事务内,先后两次读取数据返回结果不一致;例如:事务A第一次读取到数据、事务B
修改
数据并且成功提交、事务A第二次读取数据;两次读取数据结果不一致。由于事务没有形成隔离导致 -
幻读:一个事务读取到另一个事务已经提交的添加或者删除的数据;例如:A事务读取数据、B事务
删除(增加)
数据,A事务再次读取数据,发现多出一些新的数据,像出现了幻觉一样。 -
幻读与不可重复度的区别:不可重复度强调的是修改数据;幻读强调的是添加、删除数据。
-
总结:脏读是致命的操作,因为拿到的数据是无效数据;而不可重复度与幻读是一种现象,只是先后读取不一致的问题,但是数据是有效的(其他事物成功提交)!
为了解决这些问题或现象,Spring提供了4种隔离级别;隔离级别越高,性能越差;一般会使用Repeatable read(可重复读级别)。
2.3、其他参数
-
Timeout:设置事物多长时间内进行提交,如果超过设定时间进行回滚。设置值为-1表示永不超时,其他非负整数值单位为秒;例如:timeout = 5表示5秒后如果为成功提交则自动回滚!
-
read-only:设置是否只有读取的权限,一般使用在专门查询的方法上;当值为false表示可以进行CRUD、值为true只能进行读!
-
rollbackfor:设置事务发生哪些异常时进行回滚!
-
norollbackfor:设置事务发生哪些异常时不进行回滚!
3、XML配置声明式事务
首先要进行数据源和Jdbc执行器的配置,然后在声明事务!声明式事务是以AOP的形式横向切入到方法中的,业务部分只需要专注于业务即可。
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置增强类 -->
<tx:advice id="transAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- transfer方法进行事务 -->
<tx:method name="transfer" propagation="REQUIRED" timeout="30" read-only="false" isolation="REPEATABLE_READ"/>
<!-- 所有方法进行事务,传播行为默认REQUIRED,隔离级别默认RR、超时-1、是否只读默认false... -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- aop配置 -->
<aop:config>
<!-- 定义切入点 -->
<aop:pointcut id="pt" expression="execution(* com.service.AccountService.transfer(..))"/>
<!-- 将增强类注入到切入点上 -->
<aop:advisor advice-ref="transAdvice" pointcut-ref="pt"/>
</aop:config>
4、注解方式配置
注解方式配置首先需要在xml中开启注解扫描、注解支持、事务注解的驱动。当然在完全脱离xml时也可以在配置类上以注解的形式进行手动的开启这些。
<!-- 注解扫描与支持 -->
<context:component-scan base-package="com.*"/>
<context:annotation-config/>
<!-- 事务注解驱动支持 -->
<tx:annotation-driven/>
然后在需要事务的Dao层或者Service层进行配置
@Service("accountService")
@Transactional(
propagation = Propagation.REQUIRED,
timeout = 30,
isolation = Isolation.REPEATABLE_READ,
readOnly = false
)
public class AccountService {
@Autowired
private AccountMapper accountMapper;
public void transfer() {
HashMap<Object, Object> map = new HashMap<>();
map.put("money", 100);
map.put("id", "2");
int j = accountMapper.substractMoney(map);
int sum = 10 / 0;
map.put("id", "1");
int i = accountMapper.addMoney(map);
System.out.println("i = " + i + ", j = " + j);
}
}