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


    一、事务概述:

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

    二、声明式事务注解配置

    事务的配置以实例:

    用户购买图书;购买动作之前需要确认 ①图书的数量是否足够;②用户账号余额是否足够;如果①满足条件 那么 库存减-1 ;如果②满足条件  则账户余额- 书价 ;如果 ① 和②只要有一个不满足条件 则 图书库存回滚到之前的状态(此次操作之前的数量)且 用户余额回滚到原来的状态(此次操作之前的余额);① 和②都满足条件 则 事务动作完成,事务就被提交;

    用户账户表表SQL:

    1 CREATE TABLE `account` (
    2   `id` int(10) NOT NULL AUTO_INCREMENT,
    3   `userName` varchar(20) NOT NULL,
    4   `balance` varchar(20) NOT NULL,
    5   PRIMARY KEY (`id`)
    6 ) 
    View Code

     

    图书库存表bookstockSQL:

    1 CREATE TABLE `bookstock` (
    2   `id` int(10) NOT NULL AUTO_INCREMENT,
    3   `isbn` int(20) NOT NULL,
    4   `stock` varchar(20) NOT NULL,
    5   PRIMARY KEY (`id`)
    6 ) 
    View Code

     

    图书表bookSQL:

    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,
    6   PRIMARY KEY (`id`)
    7 ) ;
    View Code

     

    1.配置事务管理器

    1     <bean id="transactionManager" 
    2         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    3         <property name="dataSource" ref="datasource"></property>
    4     </bean>

    2.启用事务注解

    1 <tx:annotation-driven transaction-manager="transactionManager"/>

     附上xml 文件 引入了context tx bean的命名空间:

     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:context="http://www.springframework.org/schema/context"
     5     xmlns:tx="http://www.springframework.org/schema/tx"
     6     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     7         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
     8         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
     9 
    10 <context:component-scan base-package="lixiuming.spring.tx"></context:component-scan>
    11 <!-- 导入资源文件 -->
    12 <context:property-placeholder location="classpath:db.properties"/>
    13 <!-- 配置c3p0数据源 -->
    14 <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
    15     <property name="user" value="${jdbc.user}"></property>
    16     <property name="password" value="${jdbc.password}"></property>
    17     <property name="driverClass" value="${jdbc.driverClass}"></property>
    18     <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
    19     
    20     <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
    21     <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
    22         <property name="maxStatements" value="${jdbc.maxStatements}"></property>
    23 </bean>
    24 
    25 <!-- 配置 NamedParameterJdbcTemplate 该对象可以使用具名参数  他没有无参数的构造器,必须指定构造器参数-->
    26 <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
    27     <constructor-arg ref="datasource"></constructor-arg>
    28 </bean>
    29 
    30 <!--配置spring的 jdbcTemplate -->
    31 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    32     <property name="dataSource" ref="datasource"></property>
    33 </bean>
    34 
    35 <!-- 1.配置事务管理器 -->
    36     <bean id="transactionManager" 
    37         class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    38         <property name="dataSource" ref="datasource"></property>
    39     </bean>
    40     
    41     <!-- 2.启用事务注解 -->
    42     <tx:annotation-driven transaction-manager="transactionManager"/>
    43 </beans>
    db.properties:
    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层:

     1 package lixiuming.spring.tx;
     2 
     3 public interface BookShopDao {
     4 
     5     /**
     6      * 根据书号查找书的价格
     7      * 
     8      * @param Isbn
     9      * @return
    10      */
    11     public int findBookPriceByIsbn(int Isbn);
    12 
    13     /**
    14      * 使书号对应的库存减一
    15      */
    16     public void updateBookSock(int Isbn);
    17 
    18     /**
    19      * 更新用户的账户余额:blance -price
    20      */
    21     public void updatUserAccount(String userName, int price);
    22 
    23 }
    View Code
     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
    11     private JdbcTemplate jdbcTemplate;
    12 
    13     @Override
    14     public int findBookPriceByIsbn(int Isbn) {
    15         String sql = "select price from book where isbn = ? ";
    16         return jdbcTemplate.queryForObject(sql, Integer.class, Isbn);
    17     }
    18 
    19     @Override
    20     public void updateBookSock(int Isbn) {
    21         // 检查书的库存是否足够,若不够则抛出异常
    22         String sql2 = "select stock from bookstock where isbn =?";
    23         int stock = jdbcTemplate.queryForObject(sql2, Integer.class, Isbn);
    24         if (stock == 0) {
    25             throw new BookStockException("库存不足");
    26         }
    27 
    28         String sql = "update bookstock set stock = stock-1 where Isbn = ?";
    29         jdbcTemplate.update(sql, Isbn);
    30 
    31     }
    32 
    33     @Override
    34     public void updatUserAccount(String userName, int price) {
    35         String sql2 = "select balance from account where userName =?";
    36         int account = jdbcTemplate.queryForObject(sql2, Integer.class, userName);
    37         if (account < price) {
    38             throw new UserAccountException("余额不足");
    39         }
    40         String sql = "update account set balance = balance-? where userName =?";
    41         jdbcTemplate.update(sql, price, userName);
    42     }
    43 
    44 }
    View Code

      Service层:

    1 package lixiuming.spring.tx;
    2 
    3 public interface BookShopService {
    4     
    5     public 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
    12     private BookShopDao dao;
    13 
    14     @Transactional15     @Override
    16     public void purchase(String userName, int isbn) {
    17         // 书的单价
    18         int price = dao.findBookPriceByIsbn(isbn);
    19         // 更新库存
    20         dao.updateBookSock(isbn);
    21         // 更新余额
    22         dao.updatUserAccount(userName, price);
    23 
    24     }
    25 
    26 }

    其他(自定义异常):

    • 库存异常
     1 package lixiuming.spring.tx;
     2 
     3 public class BookStockException extends RuntimeException {
     4 
     5     /**
     6      * 
     7      */
     8     private static final long serialVersionUID = 4237643951857538899L;
     9 
    10     /**
    11      * 
    12      */
    13 
    14     public BookStockException() {
    15         super();
    16         // TODO Auto-generated constructor stub
    17     }
    18 
    19     public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    20         super(message, cause, enableSuppression, writableStackTrace);
    21         // TODO Auto-generated constructor stub
    22     }
    23 
    24     public BookStockException(String message, Throwable cause) {
    25         super(message, cause);
    26         // TODO Auto-generated constructor stub
    27     }
    28 
    29     public BookStockException(String message) {
    30         super(message);
    31         // TODO Auto-generated constructor stub
    32     }
    33 
    34     public BookStockException(Throwable cause) {
    35         super(cause);
    36         // TODO Auto-generated constructor stub
    37     }
    38 
    39 }
    View Code
    • 用户账户异常
     1 package lixiuming.spring.tx;
     2 
     3 public class UserAccountException extends RuntimeException {
     4 
     5     /**
     6      * 
     7      */
     8     private static final long serialVersionUID = -3973495734669194251L;
     9 
    10     /**
    11      * 
    12      */
    13 
    14     public UserAccountException() {
    15         super();
    16         // TODO Auto-generated constructor stub
    17     }
    18 
    19     public UserAccountException(String message, Throwable cause, boolean enableSuppression,
    20             boolean writableStackTrace) {
    21         super(message, cause, enableSuppression, writableStackTrace);
    22         // TODO Auto-generated constructor stub
    23     }
    24 
    25     public UserAccountException(String message, Throwable cause) {
    26         super(message, cause);
    27         // TODO Auto-generated constructor stub
    28     }
    29 
    30     public UserAccountException(String message) {
    31         super(message);
    32         // TODO Auto-generated constructor stub
    33     }
    34 
    35     public UserAccountException(Throwable cause) {
    36         super(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 
     9     private ApplicationContext cxt = null;
    10     private BookShopService parchase = null;
    11 
    12     {
    13         cxt = new ClassPathXmlApplicationContext("application_transaction.xml");
    14         parchase = cxt.getBean(BookShopService.class);
    15     }
    16 
    17     @Test
    18     public void testpurchase() {
    19         parchase.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;
    •  

    1.REQUIRED

    上一节 用户购买图书的实例。添加另外一个方法事务方法checkout,checkout 调用purchase 方法来测试 事务的传播行为;

    添加 Cashier接口及其实现类:

    1 package lixiuming.spring.tx;
    2 
    3 import java.util.List;
    4 
    5 public interface Cashier {
    6 
    7     public void checkout(String username, List<Integer> 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
    13     private BookShopService bookShopService;
    14 
    15     @Transactional
    16     @Override
    17     public void checkout(String username, List<Integer> isbns) {
    18         for (Integer isbn : isbns) {
    19             bookShopService.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 
    11     private ApplicationContext cxt = null;
    12     private BookShopService parchase = null;
    13     private Cashier c = null;
    14 
    15     {
    16         cxt = new ClassPathXmlApplicationContext("application_transaction.xml");
    17         parchase = cxt.getBean(BookShopService.class);
    18         c = cxt.getBean(Cashier.class);
    19     }
    20 
    21     @Test
    22     public void testCheckout() {
    23         c.checkout("aa", Arrays.asList(1001, 1002));
    24 
    25     }
    26 
    27     @Test
    28     public void testpurchase() {
    29         parchase.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
     3     public void purchase(String userName, int isbn) {
     4         // 书的单价
     5         int price = dao.findBookPriceByIsbn(isbn);
     6         // 更新库存
     7         dao.updateBookSock(isbn);
     8         // 更新余额
     9         dao.updatUserAccount(userName, price);
    10 
    11     }

    测试:

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

    当第一次运行testCheckout时,报错为余额不足; 书号1001的图书库存为 还是为9,书号1002的图书库存为 10 ;用户账户表 账户金额为20 ;

    四、事务的隔离级别和设置回滚事务属性

    1.事务的隔离级别

    • 使用isolation指定事务的隔离级别,最常用的是READ_COMMITTED
    • 默认情况下声明试事务对运行时异常进行回滚,也可以对对应的属性进行设置

    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
     3     public void purchase(String userName, int isbn) {
     4         try {
     5             Thread.sleep(5000);
     6         } catch (InterruptedException e) {
     7         }
     8 
     9         // 书的单价
    10         int price = dao.findBookPriceByIsbn(isbn);
    11         // 更新库存
    12         dao.updateBookSock(isbn);
    13         // 更新余额
    14         dao.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
     3     public void purchase(String userName, int isbn) {
     4         // 书的单价
     5         int price = dao.findBookPriceByIsbn(isbn);
     6         // 更新库存
     7         dao.updateBookSock(isbn);
     8         // 更新余额
     9         dao.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...

    用户账户表 账户金额为120 ; 书号1001和1002的图书库存为 10 

    我从来不相信什么懒洋洋的自由。我向往的自由是通过勤奋和努力实现的更广阔的人生。 我要做一个自由又自律的人,靠势必实现的决心认真地活着。
  • 相关阅读:
    在QT中应用中文
    DDA画直线
    裁剪算法
    VC6与office2007冲突的解决方法
    肾形图案
    OPENGL函数说明
    基数排序
    Qt信号和槽机制
    分形曼德尔波集合图形
    分形朱利亚集合图形1
  • 原文地址:https://www.cnblogs.com/lixiuming521125/p/15521878.html
Copyright © 2020-2023  润新知