请简单介绍Spring支持的常用数据库事务传播属性和事务隔离级别?
事务的属性:
1.★propagation:用来设置事务的传播行为
事务的传播行为:一个方法运行在了一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务
- Propagation.REQUIRED:默认值,使用原来的事务
- Propagation.REQUIRES_NEW:将原来的事务挂起,开启一个新的事务
2.★isolation:用来设置事务的隔离级别
- Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别
- Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别
知识点详解
-
1.事务的传播行为
- 简介
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。 例如:方法可能继 续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。 事务的传播行为可以由传播属性指定。 Spring 定义了 7 种类传播行为。
事务传播属性可以在@Transactional 注解的 propagation 属性中定义。
- 测试
- 简介
1) . 说明
①REQUIRED 传播行为
当 bookService 的 purchase()方法被另一个事务方法 checkout()调用时,它默认会在 现有的事务内运行。
这个默认的传播行为就是 REQUIRED。
因此在 checkout()方法的开 始和终止边界内只有一个事务。
这个事务只在 checkout()方法结束的时候被提交,结果 用户一本书都买不了。
②. REQUIRES_NEW 传播行为
表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。
-
补充
在 Spring 2.x 事务通知中,可以像下面这样在tx:method元素中设定传播事务属性。
-
2.事务的隔离级别
-
数据库事务并发问题
假设现在有两个事务:Transaction01 和 Transaction02 并发执行。
- 脏读
①Transaction01 将某条记录的 AGE 值从 20 修改为 30。
②Transaction02 读取了 Transaction01 更新后的值:30。
③Transaction01 回滚,AGE 值恢复到了 20。
④Transaction02 读取到的 30 就是一个无效的值。
- 不可重复读
①Transaction01 读取了 AGE 值为 20。
②Transaction02 将 AGE 值修改为 30。
③Transaction01 再次读取 AGE 值为 30,和第一次读取不一致。
- 幻读
①Transaction01 读取了 STUDENT 表中的一部分数据。
②Transaction02 向 STUDENT 表中插入了新的行。
③Transaction01 读取了 STUDENT 表时,多出了一些行。
-
隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。
一个事务与其他事务隔离的程度称为隔离级别。SQL 标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,
隔离级别越高,数据一致性就越好,但并发性越弱。
- 读未提交:READ UNCOMMITTED
允许 Transaction01 读取 Transaction02 未提交的修改。
- 读已提交:READ COMMITTED
要求 Transaction01 只能读取 Transaction02 已提交的修改。
- 可重复读:REPEATABLE READ
确保 Transaction01 可以多次从一个字段中读取到相同的值,即 Transaction01 执行期间禁止其它事务对这个字段进行更新。
- 串行化:SERIALIZABLE
确保 Transaction01 可以多次从一个表中读取到相同的行,在 Transaction01 执行期 间,禁止其它事务对这个表进行添加、更新、删除操作。
可以避免任何并发问题,但性 能十分低下。
- 各个隔离级别解决并发问题的能力见下表
- 各种数据库产品对事务隔离级别的支持程度
-
在 Spring 中指定事务隔离级别
- 注解
用@Transactional 注解声明式地管理事务时可以在@Transactional 的 isolation 属性 中设置隔离级别
- XML
在 Spring 2.x 事务通知中,可以在tx:method元素中指定隔离级别
-
代码show
- 代码结构
-
核心代码
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 国际标准书号 * */
-
-
代码地址
彩蛋
- 1.尚硅谷笔记