一、事务
1. 事务定义
是一组执行单元,这个执行单元的各个组成部分要么同时执行成功,要么同时执行失败。
2. 事务特性(ACID)
具有4个基本特性:原子性、一致性、隔离性、持久性
-
原子性(Automicity):一个事务是一个不可再分割的工作单位,事务中的全部操作要么都做,要么都不做;
-
一致性(Consistency):事务必须是使得数据库状态从一个一致性状态,转变到另外一个一致性状态。也就是说在事务前和事务后,被操作的目标资源状态一致。比如银行转账案例中,转账前和转账后总账不变;
-
隔离性(Isolation):一个事务的执行不能被其他事务所影响,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,多个并发事务之间不能相互干扰;
-
持久性(Durability):一个事务一旦提交,它对数据库中数据的改变会永久存储起来,其他操作不会对它产生影响。
二、Spring中的事务管理
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
1. 编程式事务管理
将事务管理代码嵌到业务方法中来控制事务的提交和回滚
缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
2. 声明式事务管理
一般情况下比编程式事务好用,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。将事务管理作为横切关注点,通过AOP方法模块化,Spring中通过Spring AOP框架支持声明式事务管理。
三、Spring声明式事务管理
1. 前言
- JavaEE 体系进行分层开发,事务处理位于业务层, Spring 提供了分层设计业务层的事务处理解决方案;
- Spring 框架为我们提供了一组事务控制的接口;
- Spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。
2. 事务管理器
Spring的核心事务管理抽象,管理封装了一组独立于技术的方法,无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
org.springframework.transaction.PlatformTransactionManager
,此接口是 Spring 的事务管理器,它提供了常用的操作事务的方法
PlaformTransactionManager
接口,包含三个方法:
-
获取事务状态信息:
TransactionStatus getTransaction(TransactionDefinition definition)
-
提交事务:
void commit(TransactionStatus status)
-
回滚事务:
void rollback(TransactionStatis status)
在开发中通常使用其实现类,如:
- SpringJDBC 或 iBatis 进行持久化数据时使用
org.springframework.jdbc.datasource.DataSourceTransactionManager
- Hibernate 版本进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager
3. 事务
JDBC事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Hibernate事务
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JPA事务
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Java原生API事务
通常用于跨越多个事务管理源(多数据源),则需要使用下面的内容
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
四、事务属性
1. 事务隔离级别
并发事务会导致发生以下三种类型的问题:
问题 | 例如 |
---|---|
脏读 | 一个事务读取了另一个事务改写尚未提交的数据时,改写的数据被回滚了,那么第一个事务获取的数据无效 |
不可重复读 | 当同一个事务执行两次及以上相同的查询时,每次都得到不同的数据。一般因为另一并发事务在两次查询期间进行了更新 |
幻读 | 第一个事务读取一些数据,此时第二个事务在该表中插入了一些新数据,这时第一个事务再读取相同的数据就会多几行 |
不可重复读和幻读的区别:不可重复读侧重点在相同数据被修改,而幻读是删除或新增
Spring中事务的隔离级别可以通过隔离属性指定
从理论上讲,事务应该完全隔离,避免并发事务导致的问题,但是这样可能对性能产生极大影响,因为事务必须按顺序进行了。所以在实际的开发中,为了提升性能,事务会以比较低的隔离级别运行。
2. 事务传播行为
事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如方法可能继续在现有事务中运行,也可能开启一个新的事务,并在自己的事务运行,Spring中的事务传播行为可以由传播属性指定。
-
REQUIRED
:当前方法必须有事务环境,如果当前方法已经有事务环境就加入,如果当前方法没有事务环境就新建一个新的事务;适合增删改操作; -
SUPPORTS
:表示当前方法支持事务,有事务可以运行没有事务也可以;适合查询操作; -
REQUERS_NEW
:当前方法必须有事务环境;不管当前方法有没有事务,都新建一个新的事务。
当前事务挂起:意思是先执行刚创建的事务,刚创建事务执行完后,再执行刚挂起的事务。
3. 事务的超时时间
事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。可以配置超时事务属性,事务在强制回滚之前可以保持多久,这样可以避免长期运行的事务占用资源。
默认值是-1,没有超时限制;如果要设置,以秒为单位。
4. 是否是只读事务
如果事务只读数据但不修改可以通过配置只读事务属性,帮助数据库引擎优化事务。只读事务属性:表示这个事务只读读取数据,但是不更新数据建议查询时设置为只读。
五、XML方式实现事务
1. pom.xml添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--aop支持-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
2. ApplicationContext.xml配置事务
<!--1.开启注解扫描-->
<context:component-scan base-package="com.zz"/>
<!--5.Spring声明式事务配置-->
<!--5.1 配置事务管理器(事务切面)-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--5.2 配置事务通知规则(拦截到方法后如何管理事务)-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--find/select/get开头的方法,有事务可以无事务也可以,只读的事务-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<!--* 表示其他所有的方法就是指增删改操作,必须有事务环境,读写事务-->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
<!--5.3 Aop配置:切入点表达式 + 通知规则-->
<aop:config>
<!--5.3.1 配置切入点表达式-->
<aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.*ServiceImpl.*(..))" />
<!--5.3.2 建立切入点表达式与通知规则的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
六、注解方式实现事务
1. 修改ApplicationContext.xml
2. service层使用注解
/**
* @Transactional 事务注解支持
* 1. 可以修饰在实现类上,表示当前实现类的所有方法都应用事务。
* 2. 也可以修饰在方法上,表示当前方法应用事务。
* 3. 还可以修饰在接口上,表示当前的接口的所有实现类都应用事务。
* 4. 也可以修饰在接口方法上,表示所有实现该方法的地方都自动应用事务。
* 5. 事务属性
* propagation = Propagation.REQUIRED 表示当前运行方法必须有事务环境
* readOnly = false 表示读写事务
* noRollbackFor = ArithmeticException.class 遇到指定的异常不回滚
*/
@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
// 注入dao
@Autowired
private IAccountDao accountDao;
@Override
public void save(Account account) {
accountDao.save(account);
//int i=1/0;//如果没有用事务,数据库会多一条数据。有用事务,数据库不会变化。
String str = null;str.length();
accountDao.save(account);
}
}
七、总结
1. Spring声明式事务原理:Aop
- 1)切面类:
DataSourceTransactionManagerSpring
已经提供,且针对不同的持久层实现技术,提供了不同的事务实现; - 2)AOP 关键
切入点表达式,只要目标对象符合切入点表达式规则,就可以声明代理对象;
自动代理:根据目标对象有实现接口,使用JDK代理;目标对象没有实现接口,使用CGLib代理。
2. Spring声明式事务特点
优点:
-
解耦,事务控制代码与业务代码完全解耦;
-
方便维护,假如项目不想用事务,直接移除配置;如果更换了持久层,事务控制只需要修改事务管理器即可。
缺点:
Spring声明式事务原理是AOP,AOP原理是代理,代理只能对方法进行拦截,所以Spring的声明式事务只能对方法进行事务控制,不能对方法的某几行进行事务控制。
3. 事务粒度
-
粗粒度事务控制:Spring声明式事务控制就是粗粒度事务控制,是方法级别的事务控制
-
细粒度事务控制:一般都是指编程式事务控制,通过代码手动控制事务。(耦合,重复代码多)