Spring源码解析——@Transactional注解的声明式事物介绍

打印 上一主题 下一主题

主题 931|帖子 931|积分 2793

正文
面的几个章节已经分析了spring基于@AspectJ的源码,那么接下来我们分析一下Aop的另一个重要功能,事物管理。最全面的Java面试网站
事务的介绍

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兼容的事务属性的接口
  1. public interface TransactionDefinition {
  2.     // 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中。
  3.     int PROPAGATION_REQUIRED = 0;
  4.     // 支持当前事物,如果当前没有事物,则以非事物方式执行。
  5.     int PROPAGATION_SUPPORTS = 1;
  6.     // 使用当前事物,如果当前没有事物,则抛出异常。
  7.     int PROPAGATION_MANDATORY = 2;
  8.     // 新建事物,如果当前已经存在事物,则挂起当前事物。
  9.     int PROPAGATION_REQUIRES_NEW = 3;
  10.     // 以非事物方式执行,如果当前存在事物,则挂起当前事物。
  11.     int PROPAGATION_NOT_SUPPORTED = 4;
  12.     // 以非事物方式执行,如果当前存在事物,则抛出异常。
  13.     int PROPAGATION_NEVER = 5;
  14.     // 如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与PROPAGATION_REQUIRED传播特性相同
  15.     int PROPAGATION_NESTED = 6;
  16.     // 使用后端数据库默认的隔离级别。
  17.     int ISOLATION_DEFAULT = -1;
  18.     // READ_UNCOMMITTED 隔离级别
  19.     int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
  20.     // READ_COMMITTED 隔离级别
  21.     int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
  22.     // REPEATABLE_READ 隔离级别
  23.     int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
  24.     // SERIALIZABLE 隔离级别
  25.     int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
  26.     // 默认超时时间
  27.     int TIMEOUT_DEFAULT = -1;
  28.     // 获取事物传播特性
  29.     int getPropagationBehavior();
  30.     // 获取事物隔离级别
  31.     int getIsolationLevel();
  32.     // 获取事物超时时间
  33.     int getTimeout();
  34.     // 判断事物是否可读
  35.     boolean isReadOnly();
  36.     // 获取事物名称
  37.     @Nullable
  38.     String getName();
  39. }
复制代码

  • Spring事物传播特性表:
传播特性名称说明PROPAGATION_REQUIRED如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中PROPAGATION_SUPPORTS支持当前事物,如果当前没有事物,则以非事物方式执行PROPAGATION_MANDATORY使用当前事物,如果当前没有事物,则抛出异常PROPAGATION_REQUIRES_NEW新建事物,如果当前已经存在事物,则挂起当前事物PROPAGATION_NOT_SUPPORTED以非事物方式执行,如果当前存在事物,则挂起当前事物PROPAGATION_NEVER以非事物方式执行,如果当前存在事物,则抛出异常PROPAGATION_NESTED如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与PROPAGATION_REQUIRED传播特性相同

  • Spring事物隔离级别表:
事务隔离级别脏读不可重复读幻读读未提交(read-uncommitted)是是是不可重复读(read-committed)否是是可重复读(repeatable-read)否否是串行化(serializable)否否否MySQL默认的事务隔离级别为 可重复读repeatable-read
  3.PlatformTransactionManager-->Spring事务基础结构中的中心接口
分享一份大彬精心整理的大厂面试手册,包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题,非常实用,有小伙伴靠着这份手册拿过字节offer~


需要的小伙伴可以自行下载
http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247485445&idx=1&sn=1c6e224b9bb3da457f5ee03894493dbc&chksm=ce98f543f9ef7c55325e3bf336607a370935a6c78dbb68cf86e59f5d68f4c51d175365a189f8#rd
  1. public interface PlatformTransactionManager {
  2.     // 根据指定的传播行为,返回当前活动的事务或创建新事务。
  3.     TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
  4.     // 就给定事务的状态提交给定事务。
  5.     void commit(TransactionStatus status) throws TransactionException;
  6.     // 执行给定事务的回滚。
  7.     void rollback(TransactionStatus status) throws TransactionException;
  8. }
复制代码
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接口
  1. public interface TransactionStatus extends SavepointManager, Flushable {
  2.     // 返回当前事务是否为新事务(否则将参与到现有事务中,或者可能一开始就不在实际事务中运行)
  3.     boolean isNewTransaction();
  4.     // 返回该事务是否在内部携带保存点,也就是说,已经创建为基于保存点的嵌套事务。
  5.     boolean hasSavepoint();
  6.     // 设置事务仅回滚。
  7.     void setRollbackOnly();
  8.     // 返回事务是否已标记为仅回滚
  9.     boolean isRollbackOnly();
  10.     // 将会话刷新到数据存储区
  11.     @Override
  12.     void flush();
  13.     // 返回事物是否已经完成,无论提交或者回滚。
  14.     boolean isCompleted();
  15. }
复制代码

  • SavepointManager接口
  1. public interface SavepointManager {
  2.     // 创建一个新的保存点。
  3.     Object createSavepoint() throws TransactionException;
  4.     // 回滚到给定的保存点。
  5.     // 注意:调用此方法回滚到给定的保存点之后,不会自动释放保存点,
  6.     // 可以通过调用releaseSavepoint方法释放保存点。
  7.     void rollbackToSavepoint(Object savepoint) throws TransactionException;
  8.     // 显式释放给定的保存点。(大多数事务管理器将在事务完成时自动释放保存点)
  9.     void releaseSavepoint(Object savepoint) throws TransactionException;
  10. }
复制代码
Spring编程式事物



  1. CREATE TABLE `account` (
  2.   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  3.   `balance` int(11) DEFAULT NULL COMMENT '账户余额',
  4.   PRIMARY KEY (`id`)
  5. ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='--账户表'
复制代码

  • 实现
  1. import org.apache.commons.dbcp.BasicDataSource;
  2. import org.springframework.dao.DataAccessException;
  3. import org.springframework.jdbc.core.JdbcTemplate;
  4. import org.springframework.jdbc.datasource.DataSourceTransactionManager;
  5. import org.springframework.transaction.TransactionDefinition;
  6. import org.springframework.transaction.TransactionStatus;
  7. import org.springframework.transaction.support.DefaultTransactionDefinition;
  8. import javax.sql.DataSource;
  9. public class MyTransaction {
  10.      private JdbcTemplate jdbcTemplate;
  11.      private DataSourceTransactionManager txManager;
  12.      private DefaultTransactionDefinition txDefinition;
  13.      private String insert_sql = "insert into account (balance) values ('100')";
  14.      public void save() {
  15.          // 1、初始化jdbcTemplate
  16.          DataSource dataSource = getDataSource();
  17.          jdbcTemplate = new JdbcTemplate(dataSource);
  18.          // 2、创建物管理器
  19.          txManager = new DataSourceTransactionManager();
  20.          txManager.setDataSource(dataSource);
  21.          // 3、定义事物属性
  22.          txDefinition = new DefaultTransactionDefinition();
  23.          txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
  24.          // 3、开启事物
  25.          TransactionStatus txStatus = txManager.getTransaction(txDefinition);
  26.          // 4、执行业务逻辑
  27.          try {
  28.              jdbcTemplate.execute(insert_sql);
  29.              //int i = 1/0;
  30.              jdbcTemplate.execute(insert_sql);
  31.              txManager.commit(txStatus);
  32.          } catch (DataAccessException e) {
  33.              txManager.rollback(txStatus);
  34.              e.printStackTrace();
  35.          }
  36.      }
  37.      public DataSource getDataSource() {
  38.          BasicDataSource dataSource = new BasicDataSource();
  39.          dataSource.setDriverClassName("com.mysql.jdbc.Driver");
  40.          dataSource.setUrl("jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
  41.          dataSource.setUsername("root");
  42.          dataSource.setPassword("dabin1991@");
  43.          return dataSource;
  44.      }
  45. }
复制代码

  • 测试类及结果
  1. public class MyTest {
  2.     @Test
  3.     public void test1() {
  4.         MyTransaction myTransaction = new MyTransaction();
  5.         myTransaction.save();
  6.     }
  7. }
复制代码
运行测试类,在抛出异常之后手动回滚事物,所以数据库表中不会增加记录。
基于@Transactional注解的声明式事物

其底层建立在 AOP 的基础之上,对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。通过声明式事物,无需在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。

  • 接口
  1. import org.springframework.transaction.annotation.Propagation;
  2. import org.springframework.transaction.annotation.Transactional;
  3. @Transactional(propagation = Propagation.REQUIRED)
  4. public interface AccountServiceImp {
  5.     void save() throws RuntimeException;
  6. }
复制代码

  • 实现
  1. import org.springframework.jdbc.core.JdbcTemplate;
  2. public class AccountServiceImpl implements AccountServiceImp {
  3.     private JdbcTemplate jdbcTemplate;
  4.     private static String insert_sql = "insert into account(balance) values (100)";
  5.     @Override
  6.     public void save() throws RuntimeException {
  7.         System.out.println("==开始执行sql");
  8.         jdbcTemplate.update(insert_sql);
  9.         System.out.println("==结束执行sql");
  10.         System.out.println("==准备抛出异常");
  11.         throw new RuntimeException("==手动抛出一个异常");
  12.     }
  13.     public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
  14.         this.jdbcTemplate = jdbcTemplate;
  15.     }
  16. }
复制代码

  • 配置文件
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xmlns:tx="http://www.springframework.org/schema/tx"
  5.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  6.         http://www.springframework.org/schema/beans/spring-beans.xsd
  7.         http://www.springframework.org/schema/tx
  8.         http://www.springframework.org/schema/tx/spring-tx.xsd">
  9.    
  10.     <tx:annotation-driven transaction-manager="transactionManager"/>
  11.    
  12.     <bean id="transactionManager" >
  13.         <property name="dataSource" ref="dataSource"/>
  14.     </bean>
  15.    
  16.     <bean id="dataSource" >
  17.         <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  18.         <property name="url" value="jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
  19.         <property name="username" value="root"/>
  20.         <property name="password" value="dabin1991@"/>
  21.     </bean>
  22.    
  23.     <bean id="jdbcTemplate" >
  24.         <property name="dataSource" ref="dataSource"/>
  25.     </bean>
  26.    
  27.     <bean id="accountService" >
  28.         <property name="jdbcTemplate" ref="jdbcTemplate"/>
  29.     </bean>
  30. </beans>
复制代码

  • 测试
  1. import org.junit.Test;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MyTest {
  5.     @Test
  6.     public void test1() {
  7.         // 基于tx标签的声明式事物
  8.         ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
  9.         AccountServiceImp studentService = ctx.getBean("accountService", AccountServiceImp.class);
  10.         studentService.save();
  11.     }
  12. }
复制代码

  • 测试
  1. ==开始执行sql
  2. ==结束执行sql
  3. ==准备抛出异常
  4. java.lang.RuntimeException: ==手动抛出一个异常
  5.     at com.lyc.cn.v2.day09.AccountServiceImpl.save(AccountServiceImpl.java:24)
  6.     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  7.     at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  8.     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  9.     at java.lang.reflect.Method.invoke(Method.java:498)
复制代码
测试方法中手动抛出了一个异常,Spring会自动回滚事物,查看数据库可以看到并没有新增记录。
注意:默认情况下Spring中的事务处理只对RuntimeException方法进行回滚,所以,如果此处将RuntimeException替换成普通的Exception不会产生回滚效果。
下一篇我们分析基于@Transactional注解的声明式事物的的源码实现。
最后给大家分享一个Github仓库,上面有大彬整理的300多本经典的计算机书籍PDF,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~


Github地址
如果访问不了Github,可以访问码云地址。
码云地址

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我可以不吃啊

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表