五(一)、spring|五(一)、spring 声明式事务注解配置

一、事务概述:

  • 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用;比如 用户购买图书;购买动作之前需要确认 ①图书的数量是否足够;②用户账号余额是否足够;如果①满足条件 那么 库存减-1 ;如果②满足条件则账户余额- 书价 ;如果 ① 和②只要有一个不满足条件 则 图书库存回滚到之前的状态(此次操作之前的数量)用户余额回滚到原来的状态(此次操作之前的余额);① 和②都满足条件 则 事务动作完成,事务就被提交.;
  • 事务的四个关键属性(ACID)
    • 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
    • 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
    • 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
    • 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.
  • spring的事务管理
    • 支持编程式事务管理:将事务管理代码 写入代码中;存在代码冗余;
    • 支持声明式事务管理:将事务管理代码 从代码中分离,通过声明的方式来实现事务管理;应用更广泛更方便;
二、声明式事务注解配置 事务的配置以实例:
用户购买图书;购买动作之前需要确认 ①图书的数量是否足够;②用户账号余额是否足够;如果①满足条件 那么 库存减-1 ;如果②满足条件则账户余额- 书价 ;如果 ① 和②只要有一个不满足条件 则 图书库存回滚到之前的状态(此次操作之前的数量)用户余额回滚到原来的状态(此次操作之前的余额);① 和②都满足条件 则 事务动作完成,事务就被提交;
用户账户表表SQL:
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 CREATE TABLE `account` ( 2`id` int(10) NOT NULL AUTO_INCREMENT, 3`userName` varchar(20) NOT NULL, 4`balance` varchar(20) NOT NULL, 5PRIMARY KEY (`id`) 6 )

View Code 五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片


图书库存表bookstockSQL:
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 CREATE TABLE `bookstock` ( 2`id` int(10) NOT NULL AUTO_INCREMENT, 3`isbn` int(20) NOT NULL, 4`stock` varchar(20) NOT NULL, 5PRIMARY KEY (`id`) 6 )

View Code 五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片


图书表bookSQL:
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 CREATE TABLE `book` ( 2`id` int(10) NOT NULL AUTO_INCREMENT, 3`Isbn` int(20) NOT NULL, 4`price` int(10) NOT NULL, 5`bookName` varchar(20) CHARACTER SET utf8 NOT NULL, 6PRIMARY KEY (`id`) 7 ) ;

View Code 五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片


1.配置事务管理器
1 3 4


2.启用事务注解
1

附上xml 文件 引入了context tx bean的命名空间:
1 2 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 38 39 40 41 42 43

db.properties:

五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 jdbc.user=root 2 jdbc.password= 3 jdbc.driverClass=com.mysql.jdbc.Driver 4 jdbc.jdbcUrl=jdbc:mysql:///test 5 6 7 jdbc.initPoolSize =5 8 jdbc.maxPoolSize = 10 9 jdbc.maxStatements=0

View Code
3.添加事务注解@Transactional DAO层:
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 package lixiuming.spring.tx; 2 3 public interface BookShopDao { 4 5/** 6* 根据书号查找书的价格 7* 8* @param Isbn 9* @return 10*/ 11public int findBookPriceByIsbn(int Isbn); 12 13/** 14* 使书号对应的库存减一 15*/ 16public void updateBookSock(int Isbn); 17 18/** 19* 更新用户的账户余额:blance -price 20*/ 21public void updatUserAccount(String userName, int price); 22 23 }

View Code 五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 package lixiuming.spring.tx; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.jdbc.core.JdbcTemplate; 5 import org.springframework.stereotype.Repository; 6 7 @Repository("bookShopImpl") 8 public class BookShopImpl implements BookShopDao { 9 10@Autowired 11private JdbcTemplate jdbcTemplate; 12 13@Override 14public int findBookPriceByIsbn(int Isbn) { 15String sql = "select price from book where isbn = ? "; 16return jdbcTemplate.queryForObject(sql, Integer.class, Isbn); 17} 18 19@Override 20public void updateBookSock(int Isbn) { 21// 检查书的库存是否足够,若不够则抛出异常 22String sql2 = "select stock from bookstock where isbn =?"; 23int stock = jdbcTemplate.queryForObject(sql2, Integer.class, Isbn); 24if (stock == 0) { 25throw new BookStockException("库存不足"); 26} 27 28String sql = "update bookstock set stock = stock-1 where Isbn = ?"; 29jdbcTemplate.update(sql, Isbn); 30 31} 32 33@Override 34public void updatUserAccount(String userName, int price) { 35String sql2 = "select balance from account where userName =?"; 36int account = jdbcTemplate.queryForObject(sql2, Integer.class, userName); 37if (account < price) { 38throw new UserAccountException("余额不足"); 39} 40String sql = "update account set balance = balance-? where userName =?"; 41jdbcTemplate.update(sql, price, userName); 42} 43 44 }

View Code Service层:
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 package lixiuming.spring.tx; 2 3 public interface BookShopService { 4 5public void purchase(String userName,int isbn); 6 7 }

View Code
1 package lixiuming.spring.tx; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Service; 5 import org.springframework.transaction.annotation.Propagation; 6 import org.springframework.transaction.annotation.Transactional; 7 8 @Service("bookShopServiceImpl") 9 public class BookShopServiceImpl implements BookShopService { 10 11@Autowired 12private BookShopDao dao; 13 14 @Transactional15@Override 16public void purchase(String userName, int isbn) { 17// 书的单价 18int price = dao.findBookPriceByIsbn(isbn); 19// 更新库存 20dao.updateBookSock(isbn); 21// 更新余额 22dao.updatUserAccount(userName, price); 23 24} 25 26 }

其他(自定义异常):
  • 库存异常
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 package lixiuming.spring.tx; 2 3 public class BookStockException extends RuntimeException { 4 5/** 6* 7*/ 8private static final long serialVersionUID = 4237643951857538899L; 9 10/** 11* 12*/ 13 14public BookStockException() { 15super(); 16// TODO Auto-generated constructor stub 17} 18 19public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 20super(message, cause, enableSuppression, writableStackTrace); 21// TODO Auto-generated constructor stub 22} 23 24public BookStockException(String message, Throwable cause) { 25super(message, cause); 26// TODO Auto-generated constructor stub 27} 28 29public BookStockException(String message) { 30super(message); 31// TODO Auto-generated constructor stub 32} 33 34public BookStockException(Throwable cause) { 35super(cause); 36// TODO Auto-generated constructor stub 37} 38 39 }

View Code
  • 用户账户异常
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 package lixiuming.spring.tx; 2 3 public class UserAccountException extends RuntimeException { 4 5/** 6* 7*/ 8private static final long serialVersionUID = -3973495734669194251L; 9 10/** 11* 12*/ 13 14public UserAccountException() { 15super(); 16// TODO Auto-generated constructor stub 17} 18 19public UserAccountException(String message, Throwable cause, boolean enableSuppression, 20boolean writableStackTrace) { 21super(message, cause, enableSuppression, writableStackTrace); 22// TODO Auto-generated constructor stub 23} 24 25public UserAccountException(String message, Throwable cause) { 26super(message, cause); 27// TODO Auto-generated constructor stub 28} 29 30public UserAccountException(String message) { 31super(message); 32// TODO Auto-generated constructor stub 33} 34 35public UserAccountException(Throwable cause) { 36super(cause); 37// TODO Auto-generated constructor stub 38} 39 40 }

View Code
测试方法:
1 package lixiuming.spring.tx; 2 3 import org.junit.Test; 4 import org.springframework.context.ApplicationContext; 5 import org.springframework.context.support.ClassPathXmlApplicationContext; 6 7 public class SpringTransactionTest { 8 9private ApplicationContext cxt = null; 10private BookShopService parchase = null; 11 12{ 13cxt = new ClassPathXmlApplicationContext("application_transaction.xml"); 14parchase = cxt.getBean(BookShopService.class); 15} 16 17@Test 18public void testpurchase() { 19parchase.purchase("aa", 1001); 20} 21 22 }

4.测试 测试前提:用户账户表 账户金额为120 ; 书号1001的图书库存为 10 ;
当第一次运行testpurchase 时,没有异常 ; 书号为1001的库存为 9 ,账户金额为20;当第二次执行testpurchase时,抛出异常;异常内容为 余额不足且 书号为1001的库存为 9 ,账户金额为20;

三、声明式事务的事务传播行为
  • 当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.
  • 使用propagation指定事务的传播行为,
  • 事务的传播行为可以由传播属性指定. Spring 定义了 7种类传播行为.默认为REQUIRED,常用为REQUIRED和REQUIRED_NEW;
  • 五(一)、spring|五(一)、spring 声明式事务注解配置
    文章图片
1.REQUIRED 上一节 用户购买图书的实例。添加另外一个方法事务方法checkout,checkout 调用purchase 方法来测试 事务的传播行为;
添加 Cashier接口及其实现类:
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片
1 package lixiuming.spring.tx; 2 3 import java.util.List; 4 5 public interface Cashier { 6 7public void checkout(String username, List isbns); 8 9 }

View Code
1 package lixiuming.spring.tx; 2 3 import java.util.List; 4 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.stereotype.Service; 7 import org.springframework.transaction.annotation.Transactional; 8 9 @Service("cashierImpl") 10 public class CashierImpl implements Cashier { 11 12@Autowired 13private BookShopService bookShopService; 14 15 @Transactional 16@Override 17public void checkout(String username, List isbns) { 18for (Integer isbn : isbns) { 19bookShopService.purchase(username, isbn); 20} 21} 22 23 }

测试方法:
1 package lixiuming.spring.tx; 2 3 import java.util.Arrays; 4 5 import org.junit.Test; 6 import org.springframework.context.ApplicationContext; 7 import org.springframework.context.support.ClassPathXmlApplicationContext; 8 9 public class SpringTransactionTest { 10 11private ApplicationContext cxt = null; 12private BookShopService parchase = null; 13private Cashier c = null; 14 15{ 16cxt = new ClassPathXmlApplicationContext("application_transaction.xml"); 17parchase = cxt.getBean(BookShopService.class); 18c = cxt.getBean(Cashier.class); 19} 20 21@Test 22public void testCheckout() { 23c.checkout("aa", Arrays.asList(1001, 1002)); 24 25} 26 27@Test 28public void testpurchase() { 29parchase.purchase("aa", 1001); 30} 31 32 }


测试:
测试前提:用户账户表 账户金额为120 ; 书号1001和1002的图书库存为 10 ;购买第一本书时,账户余额是够的,但是第二本书钱不够;
当第一次运行testCheckout时,报错为余额不足; 书号1001和1002的图书库存为 还是为10;用户账户表 账户金额为120 ;

REQUIRED_NEW: 更改purchase方法:设置事务的传播行为为REQUIRES_NEW(即:@Transactional(propagation = Propagation.REQUIRES_NEW)
REQUIRES_NEW使用自己的事务,调用事务被挂起
1@Transactional(propagation = Propagation.REQUIRES_NEW) 2@Override 3public void purchase(String userName, int isbn) { 4// 书的单价 5int price = dao.findBookPriceByIsbn(isbn); 6// 更新库存 7dao.updateBookSock(isbn); 8// 更新余额 9dao.updatUserAccount(userName, price); 10 11}

测试:
测试前提:用户账户表 账户金额为120 ; 书号1001和1002的图书库存为 10 ;购买第一本书时,账户余额是够的,但是第二本书钱不够;
当第一次运行testCheckout时,报错为余额不足; 书号1001的图书库存为 还是为9,书号1002的图书库存为 10 ;用户账户表 账户金额为20 ;

四、事务的隔离级别和设置回滚事务属性 1.事务的隔离级别
  • 使用isolation指定事务的隔离级别,最常用的是READ_COMMITTED
  • 默认情况下声明试事务对运行时异常进行回滚,也可以对对应的属性进行设置
五(一)、spring|五(一)、spring 声明式事务注解配置
文章图片



2.回滚事务属性(rollbackFor 、noRollbackFor )
  • rollbackFor:遇到时必须进行回滚
  • noRollbackFor: 一组异常类,遇到时必须不回滚
示例:
@Transactional(propagation=Propagation.REQUIRES_NEW ,isolation=Isolation.READ_COMMITTED,,noRollbackFor = {UserAccountException.class})
通常情况,不对其进行设置;

五、超时和只读属性
  • 超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.
  • 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.
超时: 更改 purchase方法:使用timeout=1,指定强制回滚之前事务可以占用时间,单位:秒,例如线程暂停5秒,则强制退出
1@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED,timeout = 1) 2@Override 3public void purchase(String userName, int isbn) { 4try { 5Thread.sleep(5000); 6} catch (InterruptedException e) { 7} 8 9// 书的单价 10int price = dao.findBookPriceByIsbn(isbn); 11// 更新库存 12dao.updateBookSock(isbn); 13// 更新余额 14dao.updatUserAccount(userName, price); 15 16}

测试:
测试前提:用户账户表 账户金额为120 ; 书号1001和1002的图书库存为 10 ;购买第一本书时,账户余额是够;
运行testpurchase 方法;只买1001书:
报错:org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Sun Nov 07 22:29:18 CST 2021...
用户账户表 账户金额为120 ; 书号1001和1002的图书库存为 10
只读 使用readOnly指定事务是否只读‘readOnly=false’若只读取数据库方法readOnly=true:
更改 purchase方法:
1@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, readOnly = true) 2@Override 3public void purchase(String userName, int isbn) { 4// 书的单价 5int price = dao.findBookPriceByIsbn(isbn); 6// 更新库存 7dao.updateBookSock(isbn); 8// 更新余额 9dao.updatUserAccount(userName, price); 10 11}

测试:
测试前提:用户账户表 账户金额为120 ; 书号1001和1002的图书库存为 10 ;购买第一本书时,账户余额是够;
运行testpurchase 方法;只买1001书:
报错:
org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [update bookstock set stock = stock-1 where Isbn = ?]; Connection is read-only. Queries leading to data modification are not allowed...
【五(一)、spring|五(一)、spring 声明式事务注解配置】用户账户表 账户金额为120 ; 书号1001和1002的图书库存为 10

    推荐阅读