原文博客:https://blog.csdn.net/jiadajing267/article/details/81056057
事务的特性
事务的特性:acid
- atomicity:原子性,保证事务的一些列动作要么全部完成,要么全部回滚
- consistency:一致性,隔离执行事务时(即在没有其他事务并执行的情况下)保持数据库的一致性
- isolation:隔离性,多个事务之间不应该相互影响
- durability:持久性,事务结果被写到存储器中
Spring 的事务管理
Spring 支持编程式事务和声明式事务管理
- 编程式事务管理:将事务管理代码嵌到业务中来控制事务的提交和回滚,缺点是必须在每个事务操作业务逻辑中包含额外的事务管理代码
- 声明式事务管理:将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理,即将事务管理作为横切关注点,通过 aop 方法模块化;Spring 中通过 Spring AOP 框架支持声明式事务管理
Spring 的事务管理器:Spring的核心事务管理抽象,管理封装了一组独立于技术的方法,无论使用哪种事务管理策略(编程式或声明式),事务管理器都是必须的
org.springframework.transaction.PlatformTransactionManager
<!-- 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>
Spring 事务属性
Spring 事务属性:
- 传播行为
- 隔离规则
- 回滚规则
- 事务超时
- 是否只读?
传播行为
事务的传播行为:事务被另一个事务方法调用时,必须指定事务应该如何传播。例如方法可能继续在现有事务中运行,也可能开启一个新的事务,并在自己的事务运行。spring中的事务传播行为可由传播属性 Propagation 指定
REQUIRED | 持当前事务,如果当前没有事务,就新建一个事务,这是最常见的选择 |
---|---|
REQUIRED_NEW | 当前方法必须启动新事务,并在自己的事务内运行,如果有事务正在运行,则将它挂起 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中 |
NOT_SUPPORTED |
表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用 JTATransactionManager 的话,则需要访问 TransactionManager |
MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就会抛出异常 |
NEVER | 当前方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与 PROPAGATION_REQUIRED 一样 |
事务的隔离级别
事务隔离级别:并发事务会导致发生以下三种类型的问题
脏读 | 发生在一个事务读取了另一个事务改写尚未提交的数据时,改写的数据被回滚了,那么第一个事务获取的数据无效 |
---|---|
不可重复读 | 当同一个事务执行两次及以上相同的查询时,每次都得到不同的数据。一般因为另一并发事务在两次查询期间进行了更新 |
幻读 | 第一个事务读取了一些数据,此时第二个事务在该表中插入了一些新数据,这是第一个事务再读取相同的数据就会多几行 |
不可重读读和幻读的区别:
- 不可重复读侧重点在相同数据被修改
- 幻读是删除或新增
Spring 中事务的隔离级别可以通过隔离属性指定:
DEFAULT | 使用底层数据库的默认隔离级别,大部分数据库,默认隔离级别都是READ_COMMITED |
---|---|
READ_COMMITED | 只允许事务读取已经被其他事务提交的更改,可以避免脏读,但不可重复读和幻读问题仍然可能出现 |
READ_UNCOMMITED | 允许事务读取未被其他事务提交的更改。脏读,不可重复读,幻读都可能会出现 |
REPEATABLE_READ | 确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但是幻读的问题依然存在 |
SERIALIZABLE | 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新,删除。所有的并发问题都能避免,但是性能比较低。 |
注意:事务的隔离级别需要底层数据库引擎的支持,而不是应用程序或框架的支持
- Oracle 支持 2 种事务隔离级别:READ_COMMITED、SERIALIZABLE
- MySQL 支持 4 种事务隔离级别
回滚规则
回滚规则:默认情况下只有未检查异常(RuntimeException 和 Error 类型的异常)会导致事务回滚,事务回滚规则可以属性管理
- rollbackFor:遇到时必须进行回滚
- noRollbackFor:一组异常类,遇到时必须不能回滚
rollbackFor={IOException.class , SQLException.class},
noRollbackFor=ArithmeticException.class
在 service 层中进行事务处理时,如果相关方法对异常进行了捕获或拦截,会导致 spring aop 无法捕获到异常,因此会导致事务无法回滚
- 因此在进行事务处理时,一般不要使用 try … catch … 捕获异常
- 如果进行了异常处理,则可以在 catch 语句中进行手动回滚事务或者继续抛出 runtimeException
```java
// 手动回滚
try {
userDao.save(user);
} carch (Exception e){
// 对当前事务进行回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
// 或者继续抛出 runtimeException 异常,在 Controller 层进行同一异常处理
try {
userDao.save(user);
} carch (Exception e){
throw new RuntimeException(“xxx”);
}
**
<a name="DgCbS"></a>
#### 只读属性
只读属性:如果事务只读数据但不修改可以通过配置只读事务属性,帮助数据库引擎优化事务
- 只读事务属性:表示这个事务只读读取数据,但是不更新数据
- @Transactional(readOnly = true)
<a name="HWGrH"></a>
#### 超时事务属性
超时事务属性:
- 事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响
- 可以配置超时事务属性,事务在强制回滚之前可以保持多久,可以避免长期运行的事务占用资源
<a name="IgC6h"></a>
### Spring事务管理配置
1、使用 spring-context.xml 进行配置:
```xml
<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事务属性 -->
<!--<tx:advice>元素声明事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="*"/>
<!--propagation配置事务传播行为-->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<!--isolation配置事务的隔离级别-->
<tx:method name="update*" isolation="SERIALIZABLE"/>
<!--rollback-for配置事务遇到异常必须回滚,no-rollback-for配置事务遇到异常必须不能回滚-->
<tx:method name="add*" rollback-for="java.io.IOException" no-rollback-for="com.dmsd.spring.tx.BookStockException"/>
<!--read-only配置事务只读属性-->
<tx:method name="find*" read-only="true"/>
<!--timeout配置事务的超时属性-->
<tx:method name="get*" timeout="3"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
2、使用 @Transactional 注解声明式地管理事务,同时使用 spring-context.xml 配置重要属性
- Spring中注解的方式 @Transactional 标注事务方法
- 为了将方法定义为支持事务处理,可以在方法上添加@Transactional注解
- 根据Spring AOP基于代理机制,只能标注公有方法
- 如果在类上标注@Transactional注解,那么这个类中所有公有方法都会被定义为支持事务
在java类的方法上添加: ```java //添加事务注解 //1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时如何使用事务 // REQUIRED:默认设置,即使用调用方法的事务 // REQUIRES_NEW: 事务自己的事务, 调用的事务方法的事务被挂起. //2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED //3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的属性进行设置. 通常情况下去默认值即可. //4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true //5.使用 timeout 指定强制回滚之前事务可以占用的时间. @Transactional(propagation=Propagation.REQUIRES_NEW,<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
@Override public void purchase(String username, String isbn) {}isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
rollbackFor = IOException.class,
readOnly=false,
timeout=3)
3、使用 Java Config 进行配置:
```java
@Configuration
@PropertySource(value = {"classpath:db.properties"})
@ComponentScan({"org.example"})
@Import(DataSourceConfig.class)
@EnableTransactionManagement // 开启事务注解功能,等同于<tx:annotation-driven>
public class DemoConfig {
@Autowired
private DataSource dataSource;
@Bean
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
// 配置事务管理器
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
事务失效
因为spring事务是基于aop的代理机制,当方法中调用 this 本身的方法时候即使在 this 的方法标明事务注解,但是事务注解会失效。如下
@Transactional
@Override
public void purchase(String username, String isbn) {
this.update(username, isbn);
}
@Transactional
public void update(String username, String isbn) {
//1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新数的库存
bookShopDao.updateBookStock(isbn);
//3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}
原因:因为调用 this 本身方法不走代理机制,这个时候可以通过配置解决这个问题
**
解决上述原因造成的事务失效:
1、在配置中添加如下内容
<!--开启aspectj代理,并暴露aop代理到ThreadLocal-->
<aop:aspectj-autoproxy expose-proxy="true"/>
2、在上述调用的地方改成如下调用
@Transactional
@Override
public void purchase(String username, String isbn) {
((BookShopServiceImpl)AopContext.currentProxy()).update(username, isbn);
}