Spring事务
1.什么是事务
把一组业务当成一个业务来做;要么都成功,要么都失败,保证业务操作完整性的一种数据库机制
声明式事务
事务的特性ACID
A:原子性,指的是,在一组业务操作下,要么都成功,要么都失败,保证业务组合的完整性;
C:一致性,事务前后的数据要保持一致性
I:隔离性,并发情况下,事务之间要相互隔离
D:持久性,数据一旦保存就是持久的,可反复使用的
在事务控制方面主要分两类
编程式事务
在代码中直接加入事务处理的逻辑,可能需要在代码中显示调用beginTransaction(),commit(),rollback()等事务管理相关的方法
示例:
try{
connetion.autoCommit(false);
...
...
connction.commint();
}catch(Exception e){
connction.rollback();
}
声明式事务
在方法的外部添加注释或者直接在配置文件中定义,将事务管理代码从业务代码中分离出来,以声明的方式来实现事务管理,spring的AOP恰巧可以完成此类功能,事务管理代码的固定模式作为一种横切点关注,通过AOP方法模块化,进而实现声明式事务
spring声明式事务配置
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来.开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的.
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法.无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的.
事务管理器可以以普通的bean的形式声明在Spring IOC容器中
1.在配置文件中增加事务管理器,和数据库相关配置
示例
添加数据库相关配置和jdbcTemplate配置
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!--配置第三方bean-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource" lazy-init="true">
<property name="username" value="${mysql.username}"></property>
<property name="password" value="${mysql.password}"></property>
<property name="url" value="${mysql.url}"></property>
<property name="driverClassName" value="${mysql.driver}"></property>
</bean>
<!--配置JdbcTemplate Bean组件-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource" ></property>
</bean>
添加事务控制配置
<!-- 配置事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 基于注解的事务,需要开启事务注解驱动-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
2.对需要进行事务的类,方法使用@Transactional进行标注
-1. @Transactional标注在类上时,表示当前类的所有方法都运用了事务
-2. @Transactional标注在方法上时,表示只有当前方法使用事务
注意:
可以类和方法上面同时都存在, 如果类和方法都存在@Transactional会以方法的为准,如果方法上面没有@Transactional会以类上面的为准
建议:
@Transactional写在方法上面,控制粒度更细, 建议@Transactional写在业务逻辑层上,因为只有业务逻辑层才会有嵌套调用的情况。
示例
/**
* 转账
*
* @param add_id 转入人id
* @param sub_id 转出人id
* @param changeBalance 转账金额
* @return
*/
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ)//给方法执行添加事务
public Integer transfer(Integer add_id, Integer sub_id, Integer changeBalance) {
Integer result = accountDao.addBalance(add_id, changeBalance);
result += accountDao.subBalance(sub_id, changeBalance);
return result;
}
事务配置的属性
@Transactional的源码为
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
各个属性为:
1.isolation:设置事务的隔离级别
2.propagation:事务的传播行为
3.noRollbackFor:哪些异常事务可以不回滚
4.noRollbackForClassName:发生异常不会回滚的类名,填写的参数是全类名
5.rollbackFor:哪些异常事务需要回滚
6.rollbackForClassName:填写的参数是全类名
7.readOnly:设置事务是否为只读事务
8.timeout:事务超出指定执行时长后自动终止并回滚,单位是秒
1)设置隔离级别:isolation
隔离级别是用来解决并发事务所产生一些问题的一种设定,不同的隔离级别应对不同的并发下产生问题的处理方案;
并发下可能产生的问题
1.脏读:
概念:一个事务,读取了另一个事务中没有提交的数据,会在本事务中产生的数据不
一致的问题
对应隔离级别处理方案:使用隔离级别"读已提交"可以进行处理,使得事务之间只读取其他事务提交后的信息,而不去获取未提交的数据,从而处理脏读问题
@Transactional(isolation = Isolation.READ_COMMITTED)
2.不可从重复读
概念:一个事务中,多次读取相同的数据, 但是读取的结果不一样, 会在本事务中产生数据不一致的问题
对应隔离级别处理方案:使用隔离级别"重复读"可以进行处理,使用行锁的方式,在事务执行期间,禁止其他事务对这个数据进行操作,让事务在一次更新过程中可以读取到相同的值
@Transactional(isolation = Isolation.REPEATABLE_READ)
3.幻读:
概念:一个事务中,多次对数据进行整表数据读取(统计),但是结果不一样, 会在本事务中产生数据不一致的问题。
对应隔离级别处理方案:使用隔离级别"串行化"可以进行处理
@Transactional(isolation = Isolation.SERIALIZABLE)
注意:
串行化事务隔离级别后在事务执行期间,会禁止其他事务对表进行添加,更新,删除等操作,确实避免了任何并发问题,但是效率十分低下
在不设置事务隔离的情况下不同数据库的默认隔离级别是不同的
oracle默认为读已提交,可已通过以下语句查看隔离级别
SELECT s.sid, s.serial#,
CASE BITAND(t.flag, POWER(2, 28))
WHEN 0 THEN 'READ COMMITTED'
ELSE 'SERIALIZABLE'
END AS isolation_level
FROM v$transaction t
JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID');
mysql默认隔离级别为重复读,可以通过以下语句查看
SELECT @@tx_isolation;
2)事务的传播性:propagation
事务的传播特性
指的是当一个事务方法被另一个事务方法调用时,这个事务方法的处理方式
spring的事务传播行为有如下属性选择具体如下:
事务传播行为类型 | 外部不存在事务 | 外部存在事务 | 操作方式 |
---|---|---|---|
REQUIRED(默认) | 开启新的事务 | 融合到外部事务中 | @Transactional(propagation = Propagation.REQUIRED)适用增删改查 |
SUPPORTS | 不开启新的事务 | 融合到外部事务中 | @Transactional(propagation = Propagation.SUPPORTS)适用查询 |
REQUIRES_NEW | 开启新的事务 | 挂起外部事务,创建新的事务 | @Transactional(propagation = Propagation.REQUIRES_NEW)适用内部事务和外部事务不存在业务关联情况,如日志 |
NOT_SUPPORTED | 不开启新的事务 | 挂起外部事务 | @Transactional(propagation =Propagation.NOT_SUPPORTED)不常用 |
NEVER | 不开启新的事务 | 抛出异常 | @Transactional(propagation = Propagation.NEVER )不常用 |
MANDATORY | 抛出异常 | 融合到外部事务中 | @Transactional(propagation = Propagation.MANDATORY)不常用 |
示例
/**
* 加钱
*
* @param id 人员id
* @param changBalance 增加金额
* @return
*/
@Override
@Transactional(propagation= Propagation.REQUIRED)
public Integer addBalance(Integer id, Integer changBalance) {
return accountDao.addBalance(id, changBalance);
}
3)超时属性:timeout
概念:
指定事务等待的最长时间单位为秒
作用:
当前事务访问数据时,有可能访问的数据被别的数据进行加锁的处理,那么此时事务就必须等待,如果等待时间过长给用户造成的体验感差
示例
/**
* 加钱
*
* @param id 人员id
* @param changBalance 增加金额
* @return
*/
@Override
@Transactional(propagation= Propagation.REQUIRED,timeout = 2)
public Integer addBalance(Integer id, Integer changBalance) {
return accountDao.addBalance(id, changBalance);
}
4)设置事务只读:readOnly
使用场景:
基本只会使用在业务查询之中
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持(如:设置不可重复度、幻影读级别)
示例
@Override
@Transactional(readOnly = true)
public Account selectById(Integer id) {
return accountDao.selectById(id);
}
5)异常属性noRollbackFor,rollbackFor等
设置当前事务出现的那些异常就进行回滚或者提交
默认对于RuntimeException 及其子类 采用的是回滚的策略
默认对于Exception 及其子类 采用的是提交的策略。
noRollbackFor:设置哪些异常不回滚
rollbackFor:设置哪些异常回滚
示例:
@Override
@Transactional(readOnly = true,rollbackFor = {FileNotFoundException.class})
public Account selectById(Integer id) {
return accountDao.selectById(id);
}
6)在实战中事务的使用方式
如果当前业务方法是一组 增、改、删 可以这样设置事务
@Transactional
如果当前业务方法是一组 查询 可以这样设置事务
@Transactionl(readOnly=true)
如果当前业务方法是单个 查询 可以这样设置事务
@Transactionl(propagation=propagation.SUPPORTS ,readOnly=true)
3基于xml的事务配置
<bean id="transactionManager" class="org.springframework.jdbc.datasou
rce.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!‐‐
基于xml配置的事务:依赖tx名称空间和aop名称空间
1、spring中提供事务管理器(切面),配置这个事务管理器
2、配置出事务方法
3、告诉spring哪些方法是事务方法(事务切面按照我们的切入点表达式去切入事
务方法)
‐‐>
<bean id="bookService" class="cn.tulingxueyuan.service.BookService">
</bean>
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* cn.learn.service.*.*(..))"/>
<!‐‐事务建议:advice‐ref:指向事务管理器的配置‐‐>
<aop:advisor advice‐ref="myAdvice" pointcut‐ref="txPoint"></aop:advisor>
</aop:config>
<tx:advice id="myAdvice" transaction‐manager="transactionManager">
<!‐‐事务属性‐‐>
<tx:attributes>
<!‐‐指明哪些方法是事务方法‐‐>
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED"/>
<tx:method name="get*" read‐only="true"></tx:method>
</tx:attributes>
</tx:advice>
</beans>
总结
1.什么是事务
2.声明式事务
3.spring的事务配置
4.spring基于注释的事务实现
5.spring基于xml配置的事务实现