一、事务概述:

  • 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用;比如 用户购买图书;购买动作之前需要确认 ①图书的数量是否足够;②用户账号余额是否足够;如果①满足条件 那么 库存减-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 )

图书库存表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 )

图书表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 ) ;

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

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 }

 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 }

  Service层:

1 package lixiuming.spring.tx;
2
3 public interface BookShopService {
4
5 public void purchase(String userName,int isbn);
6
7 }
 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 }
  • 用户账户异常

 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 }

测试方法:

 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 }
 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

五(一)、spring 声明式事务注解配置的更多相关文章

  1. Spring声明式事务的配置~~~

    /*2011年8月28日 10:03:30 by Rush  */ 环境配置 项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可.添加 ...

  2. Spring声明式事务的配置方式

    1.事务的特性   原子性:事务中的操作是不可分割的一部分   一致性:要么同时成功,要么同时失败(事务执行前后数据保持一致)   隔离性:并发互不干扰     持久性:事务一旦被提交,它就是一条持久 ...

  3. spring声明式事务以及配置

    使用spring提供的事务处理机制的好处是程序员可以不用关心事务的切面了,只要配置就好了,可以少写代码. spring声明式事务处理 spring 声明:针对的是程序员,程序员告诉spring容器,哪 ...

  4. 五(二)、spring 声明式事务xml配置

    概述: 接着上一节内容,把注解配置@@Transactional形式改为xml配置形式: 一.配置步骤 1.配置事务管理器 1 <!-- 1配置事务管理器 --> 2 <bean i ...

  5. JavaEE学习之Spring声明式事务

    一.引言 上一篇文章,学习了AOP相关知识,并做了一个简单的Hello world.本文在上篇文章的基础上,进一步学习下Spring的声明式事务. 二.相关概念 1. 事务(Transaction)— ...

  6. spring 声明式事务管理

    简单理解事务: 比如你去ATM机取5000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉5000元钱:然后ATM出5000元钱.这两个步骤必须是要么都执行要么都不执行.如果银行卡扣除了5000块但 ...

  7. XML方式实现Spring声明式事务管理

    1.首先编写一个实体类 public class Dept { private int deptId; private String deptName; public int getDeptId() ...

  8. spring声明式事务管理方式( 基于tx和aop名字空间的xml配置+@Transactional注解)

    1. 声明式事务管理分类 声明式事务管理也有两种常用的方式, 一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解. 显然基于注解的方式更简单易用,更清爽. ...

  9. Spring声明式事务的两种配置方式(注解/xml)

    application配置tx:annotation-driven 配置声明式事务tx:TransactionManager 声明式事务需要数据源所以需要配置DataSource 使用:在类或者方法上 ...

随机推荐

  1. Spring Boot中如何配置线程池拒绝策略,妥善处理好溢出的任务

    通过之前三篇关于Spring Boot异步任务实现的博文,我们分别学会了用@Async创建异步任务.为异步任务配置线程池.使用多个线程池隔离不同的异步任务.今天这篇,我们继续对上面的知识进行完善和优化 ...

  2. 由浅入深了解cookie

    什么是 Cookie "cookie 是存储于访问者的计算机中的变量.每当同一台计算机通过浏览器请求某个页面时,就会发送这个 cookie.你可以使用 JavaScript 来创建和取回 c ...

  3. 测试开发【提测平台】分享11-Python实现邮件发送的两种方法实践

    微信搜索[大奇测试开],关注这个坚持分享测试开发干货的家伙. 按照开发安排,本篇本应该是关于提测页面的搜索和显示实现,怕相似内容疲劳,这期改下内容顺序,将邮件服务的相关的提前,在之前的产品需求和原型中 ...

  4. CF183D-T-shirtx【dp,贪心】

    正题 题目链接:https://www.luogu.com.cn/problem/CF183D 题目大意 \(n\)个人,\(m\)种衣服,给出每个人喜欢某件衣服的概率,你可以选择\(n\)件衣服带过 ...

  5. element-ui上传多个文件时会发送多个请求

    1. element-ui的默认 默认是异步多次请求上传单个文件 如果业务就是单纯的上传文件,那么这个样子是没有问题的 前端代码参考 https://element-plus.gitee.io/#/z ...

  6. 从零开始学算法---二叉平衡树(AVL树)

    先来了解一些基本概念: 1)什么是二叉平衡树? 之前我们了解过二叉查找树,我们说通常来讲, 对于一棵有n个节点的二叉查找树,查询一个节点的时间复杂度为log以2为底的N的对数. 通常来讲是这样的, 但 ...

  7. 教你轻松构建基于 Serverless 架构的小程序

    前言 自 2017 年第一批小程序上线以来,越来越多的移动端应用以小程序的形式呈现.小程序触手可及.用完即走的优点,大大降低了用户的使用负担,也使小程序得到了广泛的传播.在阿里巴巴,小程序也被广泛地应 ...

  8. Django开发个人博客入门学习经验贴

    [写在前面] 入门学习搭建个人博客系统首先还是参考大佬们的经验,记得刚入手Django的时候,一篇博客大佬说过一句话,做技术的不要一开始就扎头于细节中,先把握整体框架,了解这个对象之后再去了解细节,进 ...

  9. Framework - 性能统计

    摘要 近期对接客户时,客户方希望提供 SDK 的性能.内存.隐私支持等一些数据,所以就对 SDK 进行了一些性能测试. 在用表格统计整理这些数据时,突然发现,经常用统计的方式看 SDK 的相关数据,似 ...

  10. PHP伪协议与文件包含漏洞1

    PHP文件包含漏洞花样繁多,需配合代码审计. 看能否使用这类漏洞时,主要看: (1)代码中是否有include(),且参数可控: 如: (2)php.ini设置:确保 allow_url_fopen= ...