- Spring 事务
- 事务特点
- 局部事物 vs 全局事务
- 编程式 VS 声明式
- 事务传播性
- 事务隔离级别
- 脏读、不可重复读、幻象读概念说明
- Spring 事务常见接口/类介绍
- Spring 事务实现方式
- 编程式事务实现步骤
- Spring 声明式事务管理
- 总结
Spring 事务
事务概念:事务是逻辑上的一组操作,这组操作要么全部成功,要么全部失败。
事务特点
- 原子性(Atomicity):原子性是指事务是一个不可分割的工作单元,事务中的操作要么都成功,要么都失败。
- 一致性(Consistency):一致性指事务前后数据的完整性必须保持一致。
- 隔离性(Isolation):隔离性指多个用户并发访问数据时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。
- 持久性(Durability):持久性是指一个事务一旦被提交,他对数据库中数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响。
局部事物 vs 全局事务
局部事务是特定于一个单一的事务资源,如一个 JDBC 连接,而全局事务可以跨多个事务资源事务,如在一个分布式系统中的事务。
局部事务管理在一个集中的计算环境中是有用的,该计算环境中应用程序组件和资源位于一个单位点,而事务管理只涉及到一个运行在一个单一机器中的本地数据管理器。局部事务更容易实现。
全局事务管理需要在分布式计算环境中,所有的资源都分布在多个系统中。在这种情况下事务管理需要同时在局部和全局范围内进行。分布式或全局事务跨多个系统执行,它的执行需要全局事务管理系统和所有相关系统的局部数据管理人员之间的协调。
编程式 VS 声明式
Spring 支持两种类型的事务管理:
- 编程式事务管理 :这意味着你在编程的帮助下有管理事务。这给了你极大的灵活性,但却很难维护。
- 声明式事务管理 :这意味着你从业务代码中分离事务管理。你仅仅使用注释或 XML 配置来管理事务。
声明式事务管理比编程式事务管理更可取,尽管它不如编程式事务管理灵活,但它允许你通过代码控制事务。但作为一种横切关注点,声明式事务管理可以使用 AOP 方法进行模块化。Spring 支持使用 Spring AOP 框架的声明式事务管理。
事务传播性
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。Spring定义了7中传播行为。传播行为就是解决多个业务方法相互之间调用问题
No | 事务传播性 | 说明 |
---|---|---|
1 | propagation_requierd | 如果当前没有事务,就新建一个事务,如果已存在一个事务中, 加入到这个事务中,这是Spring默认的选择。 |
2 | propagation_supports | 支持当前事务,如果没有当前事务,就以非事务方法执行。 |
3 | propagation_mandatory | 使用当前事务,如果没有当前事务,就抛出异常。 |
4 | propagation_required_new | 新建事务,如果当前存在事务,把当前事务挂起。 |
5 | propagation_not_supported | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
6 | propagation_never | 以非事务方式执行操作,如果当前事务存在则抛出异常。 |
7 | propagation_nested | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务, 则执行与propagation_required类似的操作。 |
注解声明式事务在事务注解(Transactional)中以属性方式声明传播行为
@Transactional(propagation = Propagation.REQUIRED)
事务隔离级别
No | 隔离级别 | 说明 |
---|---|---|
1 | read uncommited | 是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。 |
2 | read commited | 保证一个事物提交后才能被另外一个事务读取。 另外一个事务不能读取该事物未提交的数据。 |
3 | repeatable read | 这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。 它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免 了以下情况产生(不可重复读)。 |
4 | serializable | 是花费最高代价但最可靠的事务隔离级别。 事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读 |
脏读、不可重复读、幻象读概念说明
- 脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效数据。
- 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
- 幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
幻读:一个事务读取了几行记录后,另一个事务插入一些记录幻读就发生了。在后来的查询中,第一个事务就会发现有些原来没有记录。
Spring 事务常见接口/类介绍
PlatformTransactionManager 平台事务管理器
spring 为不同的持久化框架提供了不同PlatformTransactionManager接口实现
事务 | 说明 |
---|
TransactionDefinition 事务定义信息(隔离、传播、超时、只读)
TransactionDefinition接口控制着事务的属性
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为及4中隔离级别
TransactionStatus 事务具体运行状态
TransactionStatus 接口为事务代码提供了一个简单的方法来控制事务的执行和查询事务状态。
Spring 事务实现方式
- 引入所需JAR
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
- 编程式事务管理
在实际应用中很少使用
通过TransactionTemplate 手动管理事务 - 声明式事务管理
开发中推荐使用(代码侵入性最小)
Spring 的声明事务是通过AOP实现的
编程式事务实现步骤
- 配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- 配置事务模板
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
- 在需要使用事务的业务逻辑类中编写代码实现事务
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//业务代码
}
});
范例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
<bean id="accountService" class="com.javaee.spring.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
<property name="transactionTemplate" ref="transactionTemplate" />
</bean>
<bean id="accountDao" class="com.javaee.spring.dao.AccountDao">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
@Override
public void transfer(int in, int out, double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.outMoney(out, money);
int i = 1 / 0;
accountDao.inMoney(in, money);
}
});
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
}
Spring 声明式事务管理
1. 基于TransactionProxyFactoryBean的方式 实现声明事务管理
实现步骤
- 引入Aop相关资源(JAR,命名空间)
- 配置事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- 配置业务代理类
配置目标对象
注入事务管理器
配置事务属性
<!-- 配置业务代理类 -->
<bean id="accountProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目标对象 -->
<property name="target" ref="accountService" />
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 事务属性(隔离级别,传播行为) -->
<property name="transactionAttributes">
<props>
<!--
prop 的格式
PROPAGATION 事务的传播行为
ISOLATION 事务的隔离级别
readOnly 只读(不可进行修改,插入,删除操作)
-Exception 发生哪些异常事务回滚
+Exception 发生哪些异常事务不回滚
-->
<!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
prop格式介绍
属性介绍 | 属性说明 |
---|---|
PROPAGATION | 事务的传播行为 |
ISOLATION | 事务的隔离级别 |
readOnly | 只读(不可进行修改,插入,删除操作) |
-Exception | 发生哪些异常事务回滚 |
+Exception | 发生哪些异常事务不回滚 |
范例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
</bean>
<bean id="accountService" class="com.javaee.spring.demo2.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<bean id="accountDao" class="com.javaee.spring.demo2.dao.AccountDao">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置业务代理类 -->
<bean id="accountProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目标对象 -->
<property name="target" ref="accountService" />
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 事务属性(隔离级别,传播行为) -->
<property name="transactionAttributes">
<props>
<!--
prop 的格式
PROPAGATION 事务的传播行为
ISOLATION 事务的隔离级别
readOnly 只读(不可进行修改,插入,删除操作)
-Exception 发生哪些异常事务回滚
+Exception 发生哪些异常事务不回滚
-->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
<!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
</props>
</property>
</bean>
</beans>
Test 测试类
ApplicationContext context;
AccountService accountService;
@Before
public void init() {
context = new ClassPathXmlApplicationContext("spring-demo.xml");
accountService = (AccountService) context.getBean("accountProxyFactoryBean");
}
@Test
public void testTransfer() {
accountService.transfer(1, 2, 200);
}
Dao层
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDao extends JdbcDaoSupport {
public void inMoney(int in, double money) {
String sql = "update account set money = money + ? where id = ?";
super.getJdbcTemplate().update(sql, money, in);
}
public void outMoney(int out, double money) {
String sql = "update account set money = money - ? where id = ?";
super.getJdbcTemplate().update(sql, money, out);
}
}
业务逻辑层
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
@Override
public void transfer(int in, int out, double money) {
accountDao.outMoney(out, money);
int i = 1 / 0;
accountDao.inMoney(in, money);
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
2. 基于AspectJ的XML方式 实现声明式事务
AspectJ 实现事务管理步骤
- 导入相关JAR包等资源文件
- 配置事务管理器
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- 配置事务的通知(事务增强)
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation 事务传播行为
isolation 数据库隔离级别
read-only 只读
rollback-for 发生安歇异常回滚
no-rollback-for 发生哪些异常不会滚
timeout 过期信息
-->
<tx:method name="transfer" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
- 配置切面
<!-- 配置切面 -->
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* com.javaee.spring.demo3.service.*.*(..))" />
<!-- 配置切面(一个切面) -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
</aop:config>
使用AspectJ XML 管理事务方式非常简单,只需要在配置文件配置好即可,业务代码等无需要做任何修改;
propagation 属性解释
属性 | 属性说明 |
---|---|
propagation | 事务传播行为 |
isolation | 数据库隔离级别 |
read-only | 只读 |
rollback-for | 发生安歇异常回滚 |
no-rollback-for | 发生哪些异常不会滚 |
timeout | 过期信息 |
范例
Spring核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务的通知(事务增强) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
propagation 事务传播行为
isolation 数据库隔离级别
read-only 只读
rollback-for 发生安歇异常回滚
no-rollback-for 发生哪些异常不会滚
timeout 过期信息
-->
<tx:method name="transfer" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* com.javaee.spring.demo3.service.*.*(..))" />
<!-- 配置切面(一个切面) -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
</aop:config>
<bean id="accountService" class="com.javaee.spring.demo3.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<bean id="accountDao" class="com.javaee.spring.demo3.dao.AccountDao">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
DAO层
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDao extends JdbcDaoSupport {
public void inMoney(int in, double money) {
String sql = "update account set money = money + ? where id = ?";
super.getJdbcTemplate().update(sql, money, in);
}
public void outMoney(int out, double money) {
String sql = "update account set money = money - ? where id = ?";
super.getJdbcTemplate().update(sql, money, out);
}
}
Service层
@Override
public void transfer(int in, int out, double money) {
accountDao.outMoney(out, money);
int i = 1 / 0;
accountDao.inMoney(in, money);
}
3. 基于注解的方式 实现声明事务
注解实现事务管理方式步骤
- 添加相关资源文件
- 配置事务管理器
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- 开启注解事务
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
- 需要注解业务类使用@Transactionnal注解
@Transactional
public void transfer(int in, int out, double money) {
accountDao.outMoney(out, money);
//int i = 1 / 0;
accountDao.inMoney(in, money);
}
注意 @Transactionnal 注解可以作用类,也可以作用在具体方法上
@Transactionnal 注解说明
注解 | 注解说明 | 配置案例 |
---|---|---|
propagation | 事务的传播行为 | propagation=Propagation.REQUIRED |
isolation | 事务的隔离级别 | isolation=Isolation.DEFAULT |
readOnly | 只读 | readOnly=false |
rollbackFor | 发生哪些异常回滚 | |
noRollbackFor | 发生哪些异常不回滚 | |
noRollbackForClassName | 发生哪些异常不回滚(按照异常类填写) |
范例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="accountService" class="com.javaee.spring.demo4.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
</bean>
<bean id="accountDao" class="com.javaee.spring.demo4.dao.AccountDao">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
业务类
@Transactional
public void transfer(int in, int out, double money) {
accountDao.outMoney(out, money);
//int i = 1 / 0;
accountDao.inMoney(in, money);
}
总结
Spring 事务管理有两种
- 编程事务管理
手动编写代码进行事务管理(很少使用) - 声明事务管理
2.1 基于TransactionProxyFactoryBean 的方式(很少使用)
需要为每个进行事务管理的类,配置一个TransactionProxyFactoryBean 进行增强
2.2 基于AspectJ 的XML 方式(经常使用)
2.3 基于注解方式(经常使用)
配置简单,需要修改业务层上的代码(添加@Transactionnal 注解)