Spring支持的常用数据库事务传播属性和事务隔离级别

请简单介绍Spring支持的常用数据库事务传播属性和事务隔离级别?

事务的属性:
1.★propagation:用来设置事务的传播行为
  事务的传播行为:一个方法运行在了一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务
    - Propagation.REQUIRED:默认值,使用原来的事务
    - Propagation.REQUIRES_NEW:将原来的事务挂起,开启一个新的事务
2.★isolation:用来设置事务的隔离级别
    - Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别
    - Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别

知识点详解

  • 1.事务的传播行为

    • 简介
      当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
      例如:方法可能继 续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。 
      事务的传播行为可以由传播属性指定。
      Spring 定义了 7 种类传播行为。
      

    image-1660395039411

    事务传播属性可以在@Transactional 注解的 propagation 属性中定义。

    • 测试

image-1660395056709

1) . 说明 

①REQUIRED 传播行为 

当 bookService 的 purchase()方法被另一个事务方法 checkout()调用时,它默认会在 现有的事务内运行。

这个默认的传播行为就是 REQUIRED。

因此在 checkout()方法的开 始和终止边界内只有一个事务。

这个事务只在 checkout()方法结束的时候被提交,结果 用户一本书都买不了。 

image-1660395097244

②. REQUIRES_NEW 传播行为 

表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

image-1660395111099

  • 补充

    在 Spring 2.x 事务通知中,可以像下面这样在tx:method元素中设定传播事务属性。

image-1660395126306

  • 2.事务的隔离级别

    • 数据库事务并发问题

      假设现在有两个事务:Transaction01 和 Transaction02 并发执行。

      1. 脏读

      ①Transaction01 将某条记录的 AGE 值从 20 修改为 30。

      ②Transaction02 读取了 Transaction01 更新后的值:30。

      ③Transaction01 回滚,AGE 值恢复到了 20。

      ④Transaction02 读取到的 30 就是一个无效的值。

      1. 不可重复读

      ①Transaction01 读取了 AGE 值为 20。

      ②Transaction02 将 AGE 值修改为 30。

      ③Transaction01 再次读取 AGE 值为 30,和第一次读取不一致。

      1. 幻读

      ①Transaction01 读取了 STUDENT 表中的一部分数据。

      ②Transaction02 向 STUDENT 表中插入了新的行。

      ③Transaction01 读取了 STUDENT 表时,多出了一些行。

    • 隔离级别

      数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。

      一个事务与其他事务隔离的程度称为隔离级别。SQL 标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,

      隔离级别越高,数据一致性就越好,但并发性越弱。

      1. 读未提交:READ UNCOMMITTED

      允许 Transaction01 读取 Transaction02 未提交的修改。

      1. 读已提交:READ COMMITTED

      要求 Transaction01 只能读取 Transaction02 已提交的修改。

      1. 可重复读:REPEATABLE READ

      确保 Transaction01 可以多次从一个字段中读取到相同的值,即 Transaction01 执行期间禁止其它事务对这个字段进行更新。

      1. 串行化:SERIALIZABLE

      确保 Transaction01 可以多次从一个表中读取到相同的行,在 Transaction01 执行期 间,禁止其它事务对这个表进行添加、更新、删除操作。

      可以避免任何并发问题,但性 能十分低下。

      1. 各个隔离级别解决并发问题的能力见下表

      image-1660395146913

      1. 各种数据库产品对事务隔离级别的支持程度

    image-1660395160350

    • 在 Spring 中指定事务隔离级别

      1. 注解

      用@Transactional 注解声明式地管理事务时可以在@Transactional 的 isolation 属性 中设置隔离级别

      1. XML

      在 Spring 2.x 事务通知中,可以在tx:method元素中指定隔离级别

      image-1660395220583

代码show

  • 代码结构

image-1660395238967

  • 核心代码

    spring_transaction.sql

    /*
    SQLyog Ultimate v11.25 (64 bit)
    MySQL - 5.5.28 : Database - spring_transaction
    *********************************************************************
    */
    
    
    /*!40101 SET NAMES utf8 */;
    
    /*!40101 SET SQL_MODE=''*/;
    
    /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
    /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
    /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
    /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
    CREATE DATABASE /*!32312 IF NOT EXISTS*/`spring_transaction` /*!40100 DEFAULT CHARACTER SET gb2312 */;
    
    USE `spring_transaction`;
    
    /*Table structure for table `account` */
    
    DROP TABLE IF EXISTS `account`;
    
    CREATE TABLE `account` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(100) DEFAULT NULL,
      `balance` double(11,2) unsigned DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=gb2312;
    
    /*Data for the table `account` */
    
    insert  into `account`(`id`,`username`,`balance`) values (1,'HanZong',100.00);
    
    /*Table structure for table `book` */
    
    DROP TABLE IF EXISTS `book`;
    
    CREATE TABLE `book` (
      `isbn` varchar(100) NOT NULL,
      `name` varchar(100) DEFAULT NULL,
      `price` double(11,2) DEFAULT NULL,
      PRIMARY KEY (`isbn`)
    ) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
    
    /*Data for the table `book` */
    
    insert  into `book`(`isbn`,`name`,`price`) values ('1001','Spring',60.00),('1002','SpringMVC',50.00);
    
    /*Table structure for table `book_stock` */
    
    DROP TABLE IF EXISTS `book_stock`;
    
    CREATE TABLE `book_stock` (
      `isbn` varchar(100) NOT NULL,
      `stock` int(11) DEFAULT NULL,
      PRIMARY KEY (`isbn`)
    ) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
    
    /*Data for the table `book_stock` */
    
    insert  into `book_stock`(`isbn`,`stock`) values ('1001',100),('1002',100);
    
    /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
    /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
    /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
    /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
    
    
    • 配置

      beans-tx.xml

      <?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: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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
      		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
      
      	<!-- 设置自动扫描的包 -->
      	<context:component-scan base-package="cn.wangzengqiang.interview.atguigu2019.javaee.spring.tx"></context:component-scan>
      
      	<!-- 引入外部的属性文件 -->
      	<context:property-placeholder location="classpath:druid_tx.properties" />
      
      	<!-- 配置数据源 -->
      	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      		<property name="username" value="${jdbc.username}"></property>
      		<property name="password" value="${jdbc.password}"></property>
      		<property name="url" value="${jdbc.url}"></property>
      		<property name="driverClassName" value="${jdbc.driverClassName}"></property>
      
      		<property name="initialSize" value="${jdbc.initialSize}"></property>
      		<property name="minIdle" value="${jdbc.minIdle}"></property>
      		<property name="maxActive" value="${jdbc.maxActive}"></property>
      		<property name="maxWait" value="${jdbc.maxWait}"></property>
      	</bean>
      
      	<!-- 配置JdbcTemplate -->
      	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
      		<!-- 配置数据源属性值 -->
      		<property name="dataSource" ref="dataSource"></property>
      	</bean>
      
      	<!-- 配置事务管理器 -->
      	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      		<!-- 配置数据源属性值 -->
      		<property name="dataSource" ref="dataSource"></property>
      	</bean>
      
      	<!-- 开启事务注解支持 
      		如果事务管理器的id属性值为transactionManager,那么transaction-manager属性可以省略不写
      	-->
      <!-- 	<tx:annotation-driven transaction-manager="transactionManager"/> -->
      	<tx:annotation-driven/>
      </beans>
      
      

      druid_tx.properties

      jdbc.username=root
      jdbc.password=123456
      jdbc.url=jdbc:mysql://localhost:3306/spring_transaction
      jdbc.driverClassName=com.mysql.jdbc.Driver
      jdbc.initialSize=10
      jdbc.minIdle=5
      jdbc.maxActive=20
      jdbc.maxWait=5000
      
    • jar包

      • druid-1.1.10.jar
      • mysql-connector-java-8.0.29.jar
      • spring-jdbc-4.0.0.RELEASE.jar
      • spring-tx-4.0.0.RELEASE.jar
    • 代码

      • dao

        BookShopDao.java

        public interface BookShopDao {
        
            /**
             * 根据书号查询图书的价格
             *
             * @param isbn
             * @return
             */
            double getBookPriceByIsbn(String isbn);
        
            /**
             * 根据书号更新图书的库存,每次只买一本图书
             *
             * @param isbn
             */
            void updateBookStock(String isbn);
        
            /**
             * 根据用户的id和图书的价格更新用户的账户余额
             *
             * @param userId
             * @param bookPrice
             */
            void updateAccountBalance(int userId, double bookPrice);
        }
        

        BookShopDaoImpl.java

        @Repository("bookShopDao")
        public class BookShopDaoImpl implements BookShopDao {
        
            @Autowired
            private JdbcTemplate jdbcTemplate;
        
            @Override
            public double getBookPriceByIsbn(String isbn) {
                // 写sql语句
                String sql = "select price from book where isbn = ?";
                // 调用JdbcTemplate中的queryForObject方法
                Double bookPrice = jdbcTemplate.queryForObject(sql, Double.class, isbn);
                return bookPrice;
            }
        
            @Override
            public void updateBookStock(String isbn) {
                // 写sql语句
                String sql = "update book_stock set stock = stock - 1 where isbn = ?";
                // 调用JdbcTemplate中的update方法
                jdbcTemplate.update(sql, isbn);
            }
        
            @Override
            public void updateAccountBalance(int userId, double bookPrice) {
                // 写sql语句
                String sql = "update account set balance = balance - ? where id = ?";
                // 调用JdbcTemplate中的update方法
                jdbcTemplate.update(sql, bookPrice, userId);
            }
        
        }
        
        
      • service

        BookShopService.java

        public interface BookShopService {
        
        	/**
        	 * 买东西
        	 * 
        	 * @param userId
        	 * @param isbn
        	 */
        	void purchase(int userId, String isbn);
        }
        

        BookShopServiceImpl.java

        /**
         * @Transactional注解 该注解可以添加到类上,也可以添加到方法上
         * 如果添加到类上,那么类中所有的方法都添加上了事务
         * 如果添加到方法上,只有添加了该注解的方法才添加了事务
         */
        //@Transactional
        @Service("bookShopService")
        public class BookShopServiceImpl implements BookShopService {
        
            @Autowired
            private BookShopDao bookShopDao;
        
            //1.请简单介绍Spring支持的常用数据库事务传播属性和事务隔离级别?
        
            /**
             * 事务的属性:
             * 1.★propagation:用来设置事务的传播行为
             * 事务的传播行为:一个方法运行在了一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务
             * -Propagation.REQUIRED:默认值,使用原来的事务
             * -Propagation.REQUIRES_NEW:将原来的事务挂起,开启一个新的事务
             * 2.★isolation:用来设置事务的隔离级别
             * -Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别
             * -Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别
             */
            @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.REPEATABLE_READ)
            @Override
            public void purchase(int userId, String isbn) {
                //1.获取要买的图书的价格
                double bookPrice = bookShopDao.getBookPriceByIsbn(isbn);
        		//System.out.println(bookPrice);
                //2.更新图书的库存
                bookShopDao.updateBookStock(isbn);
                //3.更新用户的余额
                bookShopDao.updateAccountBalance(userId, bookPrice);
        		//double bookPriceByIsbn = bookShopDao.getBookPriceByIsbn(isbn);
        		//System.out.println(bookPriceByIsbn);
            }
        
        }
        

        Cashier.java

        public interface Cashier {
        
        	/**
        	 * 去结账
        	 * 
        	 * @param userId
        	 * @param isbns
        	 */
        	void checkout(int userId, List<String> isbns);
        }
        

        CashierImpl.java

        @Service("cashier")
        public class CashierImpl implements Cashier {
        
            @Autowired
            private BookShopService bookShopService;
        
            @Transactional
            @Override
            public void checkout(int userId, List<String> isbns) {
                for (String isbn : isbns) {
                    //调用BookShopService中买东西的方法
                    bookShopService.purchase(userId, isbn);
                }
            }
        
        }
        
      • 测试

        TxTest.java

        public class TxTest {
        
            //创建IOC容器对象
            ApplicationContext ioc = new ClassPathXmlApplicationContext("beans-tx.xml");
        
            @Test
            public void testBookShopDao() {
                //获取BookDao
                BookShopDao bookShopDao = (BookShopDao) ioc.getBean("bookShopDao");
                double bookPrice = bookShopDao.getBookPriceByIsbn("1001");
                System.out.println(bookPrice);
                //更新图书的库存
                bookShopDao.updateBookStock("1001");
                //更新账户的余额
                bookShopDao.updateAccountBalance(1, bookPrice);
            }
        
            /**
             * 测试事务隔离级别(在第一次读那里加个断点,然后手工修改数据库数据,然后查看第二次查询结果)
             *  Isolation.REPEATABLE_READ(Isolation.DEFAULT)
             *  Isolation.READ_COMMITTED
             *
             */
            @Test
            public void testBookShopService() {
                BookShopService bookShopService = (BookShopService) ioc.getBean("bookShopService");
                bookShopService.purchase(1, "1001");
            }
        
            /**
             * 测试事务传播属性
             *  Propagation.REQUIRED
             *  Propagation.REQUIRES_NEW
             *
             */
            @Test
            public void testCashier() {
                Cashier cashier = (Cashier) ioc.getBean("cashier");
                //创建List
                List<String> isbns = new ArrayList<>();
                isbns.add("1001");
                isbns.add("1002");
                //去结账
                cashier.checkout(1, isbns);
            }
        
        }
        /**
         * 问题记录:
         * 1.java.sql.SQLException: Unknown system variable 'query_cache_size'
         * ...
         * at cn.wangzengqiang.interview.atguigu2019.javaee.spring.tx.dao.impl.BookShopDaoImpl.getBookPriceByIsbn(BookShopDaoImpl.java:20)
         * 	at cn.wangzengqiang.interview.atguigu2019.javaee.spring.test.TxTest.testBookShopDao(TxTest.java:22)
         * mysql驱动:5.1.36
         * mysql版本:select version(); //8.0.29
         * 去:https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.29 下载:
         * https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.29/mysql-connector-java-8.0.29.jar
         * 放到lib目录下,把原来的mysql驱动删除
         *
         * 知识点:
         * 1.isbn 国际标准书号
         *
         */
        

代码地址

代码地址

彩蛋

最近的文章

SpringMVC中如何解决POST请求中文乱码问题GET的又如何处理呢

SpringMVC中如何解决POST请求中文乱码问题GET的又如何处理呢method属性值改为post,中文乱码解决:添加springmvc过滤器:org.springframework.web.filter.CharacterEncodingFiltermethod属性改为get,中文乱码解决:t…

继续阅读
更早的文章

开发笔记分享

Java尚硅谷 IT 精英计划 JavaSE 内部学习笔记.pdf尚硅谷 Java 基础实战之银行项目.pdf尚硅谷 Java 技术之 JDBC.pdf尚硅谷 Java 技术之 JavaWeb.pdf尚硅谷 Java 技术之 Maven.pdf尚硅谷 Java 技术之 MyBatis.pdf尚硅谷 …

继续阅读