五spring 声明式事务注解配置
Posted 啄木鸟伍迪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了五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 )
图书库存表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 SprSpring 事务控制 -- 基于注解的声明式事务控制
01:Spring 事务控制 – 编程式事务控制相关对象
02:Spring 事务控制 – 基于XML的声明式事务控制:环境搭建
03:Spring 事务控制 – 基于XML的声明式事务控制:详细配置
04:Spring 事务控制 – 基于注解的声明式事务控制
1. 基于注解的声明式事务控制
在此之前,你应该先把环境搭建好。
02:Spring 事务控制 – 基于XML的声明式事务控制:环境搭建
1.1 修改applicationContext.xml配置文件
一般来说,自己写的类通过注解的方式交给Spring IoC管理。非自定义的类通过配置文件的方式进行配置。
applicationContext.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:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--组件扫描--> <context:component-scan base-package="com.tian"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/> <property name="user" value="root"/> <property name="password" value="317525"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!--配置平台事务管理器: --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--事物的注解驱动--> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
1.2 托管IoC
1.3 注解配置事务
package com.tian.service.impl; import com.tian.dao.AccountDao; import com.tian.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service("accountService") // 该注解里面配置事务的属性信息:隔离级别、传播行为、超时时间、是否只读 // 如果在类上面配置了信息:那么这个事务的属性消息对于下面的方法是通用的 @Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, timeout = 2, readOnly = true) public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; // 方法上配置事务的属性信息会覆盖类上面配的信息 @Transactional(isolation = Isolation.REPEATABLE_READ, timeout = 3, readOnly = false) public void transfer(String outMan, String inMan, double money) { // 这里应该开启事务 accountDao.out(outMan, money); int i = 1 / 0; accountDao.in(inMan, money); // 这里应该提交事务 } }
运行结果:
我们先把accout表的金额都设置为5000
测试:手动制造异常
运行controller方法:
运行结果:
1.4 基于注解的声明式事务控制要点
以上是关于五spring 声明式事务注解配置的主要内容,如果未能解决你的问题,请参考以下文章
spring中声明式事务 配置好后,在java代码中怎么使用
Spring Boot 中使用 @Transactional 注解配置事务管理
Spring Boot 中使用 @Transactional 注解配置事务管理
Spring Boot 中使用 @Transactional 注解配置事务管理