面的几个章节已经分析了 spring 基于 @AspectJ 的源码, 那么接下来我们分析一下 Aop 的另一个重要功能, 事物管理.
事务的介绍
1. 数据库事物特性
原子性
多个数据库操作是不可分割的, 只有所有的操作都执行成功, 事物才能被提交; 只要有一个操作执行失败, 那么所有的操作都要回滚, 数据库状态必须回复到操作之前的状态
一致性
事物操作成功后, 数据库的状态和业务规则必须一致. 例如: 从 A 账户转账 100 元到 B 账户, 无论数据库操作成功失败, A 和 B 两个账户的存款总额是不变的.
隔离性
当并发操作时, 不同的数据库事物之间不会相互干扰 (当然这个事物隔离级别也是有关系的)
持久性
事物提交成功之后, 事物中的所有数据都必须持久化到数据库中. 即使事物提交之后数据库立刻崩溃, 也需要保证数据能能够被恢复.
2. 事物隔离级别
当数据库并发操作时, 可能会引起脏读, 不可重复读, 幻读, 第一类丢失更新, 第二类更新丢失等现象.
脏读
事物 A 读取事物 B 尚未提交的更改数据, 并做了修改; 此时如果事物 B 回滚, 那么事物 A 读取到的数据是无效的, 此时就发生了脏读.
不可重复读
一个事务执行相同的查询两次或两次以上, 每次都得到不同的数据. 如: A 事物下查询账户余额, 此时恰巧 B 事物给账户里转账 100 元, A 事物再次查询账户余额, 那么 A 事物的两次查询结果是不一致的.
幻读
A 事物读取 B 事物提交的新增数据, 此时 A 事物将出现幻读现象. 幻读与不可重复读容易混淆, 如何区分呢? 幻读是读取到了其他事物提交的新数据, 不可重复读是读取到了已经提交事物的更改数据 (修改或删除)
对于以上问题, 可以有多个解决方案, 设置数据库事物隔离级别就是其中的一种, 数据库事物隔离级别分为四个等级, 通过一个表格描述其作用.
隔离级别 | 脏读 | 不可重复读 | 幻象读 |
---|---|---|---|
READ UNCOMMITTED | 允许 | 允许 | 允许 |
READ COMMITTED | 脏读 | 允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 |
3.Spring 事物支持核心接口
TransactionDefinition--> 定义与 spring 兼容的事务属性的接口
- public interface TransactionDefinition {
- // 如果当前没有事物, 则新建一个事物; 如果已经存在一个事物, 则加入到这个事物中.
- int PROPAGATION_REQUIRED = 0;
- // 支持当前事物, 如果当前没有事物, 则以非事物方式执行.
- int PROPAGATION_SUPPORTS = 1;
- // 使用当前事物, 如果当前没有事物, 则抛出异常.
- int PROPAGATION_MANDATORY = 2;
- // 新建事物, 如果当前已经存在事物, 则挂起当前事物.
- int PROPAGATION_REQUIRES_NEW = 3;
- // 以非事物方式执行, 如果当前存在事物, 则挂起当前事物.
- int PROPAGATION_NOT_SUPPORTED = 4;
- // 以非事物方式执行, 如果当前存在事物, 则抛出异常.
- int PROPAGATION_NEVER = 5;
- // 如果当前存在事物, 则在嵌套事物内执行; 如果当前没有事物, 则与 PROPAGATION_REQUIRED 传播特性相同
- int PROPAGATION_NESTED = 6;
- // 使用后端数据库默认的隔离级别.
- int ISOLATION_DEFAULT = -1;
- // READ_UNCOMMITTED 隔离级别
- int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
- // READ_COMMITTED 隔离级别
- int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
- // REPEATABLE_READ 隔离级别
- int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
- // SERIALIZABLE 隔离级别
- int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
- // 默认超时时间
- int TIMEOUT_DEFAULT = -1;
- // 获取事物传播特性
- int getPropagationBehavior();
- // 获取事物隔离级别
- int getIsolationLevel();
- // 获取事物超时时间
- int getTimeout();
- // 判断事物是否可读
- boolean isReadOnly();
- // 获取事物名称
- @Nullable
- String getName();
- }
Spring 事物传播特性表:
传播特性名称 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中 |
PROPAGATION_SUPPORTS | 支持当前事物,如果当前没有事物,则以非事物方式执行 |
PROPAGATION_MANDATORY | 使用当前事物,如果当前没有事物,则抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事物,如果当前已经存在事物,则挂起当前事物 |
PROPAGATION_NOT_SUPPORTED | 以非事物方式执行,如果当前存在事物,则挂起当前事物 |
PROPAGATION_NEVER | 以非事物方式执行,如果当前存在事物,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事物,则在嵌套事物内执行; 如果当前没有事物,则与 PROPAGATION_REQUIRED 传播特性相同 |
Spring 事物隔离级别表:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 & nbsp; | 是 | 是 |
可重复读(repeatable-read) | 否 & nbsp; | 否 & nbsp; | 是 |
串行化(serializable) | 否 & nbsp; | 否 & nbsp; | 否 & nbsp; |
MySQL 默认的事务隔离级别为 可重复读 repeatable-read
3.PlatformTransactionManager-->Spring 事务基础结构中的中心接口
- public interface PlatformTransactionManager {
- // 根据指定的传播行为, 返回当前活动的事务或创建新事务.
- TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
- // 就给定事务的状态提交给定事务.
- void commit(TransactionStatus status) throws TransactionException;
- // 执行给定事务的回滚.
- void rollback(TransactionStatus status) throws TransactionException;
- }
Spring 将事物管理委托给底层的持久化框架来完成, 因此, Spring 为不同的持久化框架提供了不同的 PlatformTransactionManager 接口实现. 列举几个 Spring 自带的事物管理器:
事物管理器 | 说明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 提供对单个 javax.sql.DataSource 事务管理,用于 Spring JDBC 抽象框架、iBATIS 或 MyBatis 框架的事务管理 |
org.springframework.orm.jpa.JpaTransactionManager | 提供对单个 javax.persistence.EntityManagerFactory 事务支持,用于集成 JPA 实现框架时的事务管理 |
org.springframework.transaction.jta.JtaTransactionManager | 提供对分布式事务管理的支持,并将事务管理委托给 Java EE 应用服务器事务管理器 |
TransactionStatus--> 事物状态描述
TransactionStatus 接口
- public interface TransactionStatus extends SavepointManager, Flushable {
- // 返回当前事务是否为新事务 (否则将参与到现有事务中, 或者可能一开始就不在实际事务中运行)
- boolean isNewTransaction();
- // 返回该事务是否在内部携带保存点, 也就是说, 已经创建为基于保存点的嵌套事务.
- boolean hasSavepoint();
- // 设置事务仅回滚.
- void setRollbackOnly();
- // 返回事务是否已标记为仅回滚
- boolean isRollbackOnly();
- // 将会话刷新到数据存储区
- @Override
- void flush();
- // 返回事物是否已经完成, 无论提交或者回滚.
- boolean isCompleted();
- }
SavepointManager 接口
- public interface SavepointManager {
- // 创建一个新的保存点.
- Object createSavepoint() throws TransactionException;
- // 回滚到给定的保存点.
- // 注意: 调用此方法回滚到给定的保存点之后, 不会自动释放保存点,
- // 可以通过调用 releaseSavepoint 方法释放保存点.
- void rollbackToSavepoint(Object savepoint) throws TransactionException;
- // 显式释放给定的保存点.(大多数事务管理器将在事务完成时自动释放保存点)
- void releaseSavepoint(Object savepoint) throws TransactionException;
- }
Spring 编程式事物
表
- CREATE TABLE `account` (
- `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
- `balance` int(11) DEFAULT NULL COMMENT '账户余额',
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='-- 账户表'
实现
- import org.apache.commons.dbcp.BasicDataSource;
- import org.springframework.dao.DataAccessException;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.jdbc.datasource.DataSourceTransactionManager;
- import org.springframework.transaction.TransactionDefinition;
- import org.springframework.transaction.TransactionStatus;
- import org.springframework.transaction.support.DefaultTransactionDefinition;
- import javax.sql.DataSource;
- /**
- * Spring 编程式事物
- * @author: Chenhao
- * @create: 2019-10-08 11:41
- */
- public class MyTransaction {
- private JdbcTemplate jdbcTemplate;
- private DataSourceTransactionManager txManager;
- private DefaultTransactionDefinition txDefinition;
- private String insert_sql = "insert into account (balance) values ('100')";
- public void save() {
- // 1, 初始化 jdbcTemplate
- DataSource dataSource = getDataSource();
- jdbcTemplate = new JdbcTemplate(dataSource);
- // 2, 创建物管理器
- txManager = new DataSourceTransactionManager();
- txManager.setDataSource(dataSource);
- // 3, 定义事物属性
- txDefinition = new DefaultTransactionDefinition();
- txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
- // 3, 开启事物
- TransactionStatus txStatus = txManager.getTransaction(txDefinition);
- // 4, 执行业务逻辑
- try {
- jdbcTemplate.execute(insert_sql);
- //int i = 1/0;
- jdbcTemplate.execute(insert_sql);
- txManager.commit(txStatus);
- } catch (DataAccessException e) {
- txManager.rollback(txStatus);
- e.printStackTrace();
- }
- }
- public DataSource getDataSource() {
- BasicDataSource dataSource = new BasicDataSource();
- dataSource.setDriverClassName("com.mysql.jdbc.Driver");
- dataSource.setUrl("jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
- dataSource.setUsername("root");
- dataSource.setPassword("chenhao1991@");
- return dataSource;
- }
- }
测试类及结果
- public class MyTest {
- @Test
- public void test1() {
- MyTransaction myTransaction = new MyTransaction();
- myTransaction.save();
- }
- }
运行测试类, 在抛出异常之后手动回滚事物, 所以数据库表中不会增加记录.
基于 @Transactional 注解的声明式事物
其底层建立在 AOP 的基础之上, 对方法前后进行拦截, 然后在目标方法开始之前创建或者加入一个事务, 在执行完目标方法之后根据执行情况提交或者回滚事务. 通过声明式事物, 无需在业务逻辑代码中掺杂事务管理的代码, 只需在配置文件中做相关的事务规则声明 (或通过等价的基于标注的方式), 便可以将事务规则应用到业务逻辑中.
接口
- import org.springframework.transaction.annotation.Propagation;
- import org.springframework.transaction.annotation.Transactional;
- /**
- * 账户接口
- * @author: ChenHao
- * @create: 2019-10-08 18:38
- */
- @Transactional(propagation = Propagation.REQUIRED)
- public interface AccountServiceImp {
- void save() throws RuntimeException;
- }
实现
- import org.springframework.jdbc.core.JdbcTemplate;
- /**
- * 账户接口实现
- * @author: ChenHao
- * @create: 2019-10-08 18:39
- */
- public class AccountServiceImpl implements AccountServiceImp {
- private JdbcTemplate jdbcTemplate;
- private static String insert_sql = "insert into account(balance) values (100)";
- @Override
- public void save() throws RuntimeException {
- System.out.println("== 开始执行 sql");
- jdbcTemplate.update(insert_sql);
- System.out.println("== 结束执行 sql");
- System.out.println("== 准备抛出异常");
- throw new RuntimeException("== 手动抛出一个异常");
- }
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
- this.jdbcTemplate = jdbcTemplate;
- }
- }
配置文件
- <?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: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/tx
- http://www.springframework.org/schema/tx/spring-tx.xsd">
- <!-- 开启 tx 注解 -->
- <tx:annotation-driven transaction-manager="transactionManager"/>
- <!-- 事物管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"/>
- </bean>
- <!-- 数据源 -->
- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
- <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
- <property name="username" value="root"/>
- <property name="password" value="chenhao1991@"/>
- </bean>
- <!--jdbcTemplate-->
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource"/>
- </bean>
- <!-- 业务 bean-->
- <bean id="accountService" class="com.chenhao.aop.AccountServiceImpl">
- <property name="jdbcTemplate" ref="jdbcTemplate"/>
- </bean>
- </beans>
测试
- import org.junit.Test;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- /**
- * @author: ChenHao
- * @create: 2019-10-08 18:45
- */
- public class MyTest {
- @Test
- public void test1() {
- // 基于 tx 标签的声明式事物
- ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
- AccountServiceImp studentService = ctx.getBean("accountService", AccountServiceImp.class);
- studentService.save();
- }
- }
测试
== 开始执行 sql
== 结束执行 sql
== 准备抛出异常
java.lang.RuntimeException: == 手动抛出一个异常
- at com.lyc.cn.v2.day09.AccountServiceImpl.save(AccountServiceImpl.java:24)
- at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
- at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
- at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
- at java.lang.reflect.Method.invoke(Method.java:498)
测试方法中手动抛出了一个异常, Spring 会自动回滚事物, 查看数据库可以看到并没有新增记录.
注意: 默认情况下 Spring 中的事务处理只对 RuntimeException 方法进行回滚, 所以, 如果此处将 RuntimeException 替换成普通的 Exception 不会产生回滚效果.
接下来我们就分析基于 @Transactional 注解的声明式事物的的源码实现.
来源: https://www.cnblogs.com/java-chen-hao/p/11635525.html