当前位置:网站首页>4.3 Annotation-based declarative transactions and XML-based declarative transactions
4.3 Annotation-based declarative transactions and XML-based declarative transactions
2022-08-04 05:30:00 【ape white】
4.3、基于注解的声明式事务
4.3.1、准备工作
①加入依赖
<dependencies>
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Spring 持久化层支持jar包 -->
<!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个
jar包 -->
<!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.1</version>
</dependency>
<!-- Spring 测试相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
</dependencies>
②创建jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
③配置Spring的配置文件
<!--扫描组件-->
<context:component-scan base-package="com.gao.spring"></context:component-scan>
<!--引入外部属性文件,jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置 JdbcTemplate -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 装配数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
④创建表
CREATE TABLE `t_book` (
`book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`book_name` varchar(20) DEFAULT NULL COMMENT '图书名称',
`price` int(11) DEFAULT NULL COMMENT '价格',
`stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)',
PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'斗破苍
穹',80,100),(2,'斗罗大陆',50,100);
CREATE TABLE `t_balance` (
`user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(20) DEFAULT NULL COMMENT '用户名',
`balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert into `t_balance`(`user_id`,`username`,`balance`) values (1,'admin',50);
⑤创建组件
创建BookController:
@Controller
public class BookController {
@Autowired
private BookService bookService;
public void buyBook(Integer userId,Integer bookId){
bookService.buyBook(userId,bookId);
}
}
创建接口BookService:
public interface BookService {
//买书
void buyBook(Integer userId, Integer bookId);
}
创建实现类BookServiceImpl:
@Service
//@Transactional Manage all methods
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
@Transactional
public void buyBook(Integer userId, Integer bookId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId,price);
}
}
创建接口BookDao:
public interface BookDao {
//根据图书的id查询价格
Integer getPriceByBookId(Integer bookId);
//更新库存
void updateStock(Integer bookId);
//更新余额
void updateBalance(Integer userId, Integer price);
}
创建实现类BookDaoImpl:
@Repository
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Integer getPriceByBookId(Integer bookId) {
String sql = "select price from t_book where book_id = ?";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class, bookId);
return integer;
}
@Override
public void updateStock(Integer bookId) {
String sql = "update t_book set stock - 1 where book_id = ?";
jdbcTemplate.update(sql,bookId);
}
@Override
public void updateBalance(Integer userId, Integer price) {
String sql = "update t_balance set balance - ? where user_id = ?";
jdbcTemplate.update(sql,price,userId);
}
}
4.3.2、Test the no-transaction case
①创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TXTest {
@Autowired
private BookController bookController;
@Test
public void testBuyBook(){
bookController.buyBook(1,1);
}
}
②模拟场景
User buys book,Check the price of the book first,Then update the book's inventory and the user's balance
假设用户id为1的用户,购买id为1的图书
用户余额为50,The price of the book is 80
After purchasing the book,The user's balance is -30,The balance field in the database is set to unsigned,因此无法将-30Insert into balance field
此时执行sql语句会抛出SQLException
③观察结果
Because no transaction was added,The inventory of books has been updated,But the user's balance is not updated
Obviously this result is wrong,Buying books is a complete feature,Updating inventory and updating balances either succeed or fail
4.3.3、加入事务
①添加事务配置
在Spring的配置文件中添加配置:
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
开启事务的注解驱动
将使用@transactionalThe method identified by the annotation or all methods in the class is managed using transactions
transaction-managerThe property sets the transaction manager'sid
If the transaction managerid默认为transactionManager,该属性可以不写
-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
完整配置:
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描组件-->
<context:component-scan base-package="com.gao.spring"></context:component-scan>
<!--引入外部属性文件,jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置 JdbcTemplate -->
<bean 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>
<!--
开启事务的注解驱动
将使用@transactionalThe method identified by the annotation or all methods in the class is managed using transactions
transaction-managerThe property sets the transaction manager'sid
If the transaction managerid默认为transactionManager,该属性可以不写
-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
注意:Imported namespace required tx 结尾的那个.
<tx:annotation-driven有多个:选择tx
xmlns:context=“http://www.springframework.org/schema/context” xmlns:tx=“http://www.springframework.org/schema/tx”
②添加事务注解
因为service层表示业务逻辑层,A method represents a completed function,Therefore, the transaction is generally processedservice层处理.
在BookServiceImpl的buybook()添加注解@Transactional
@Service
//@Transactional Manage all methods
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
@Transactional
public void buyBook(Integer userId, Integer bookId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId,price);
}
}
③观察结果
由于使用了Spring的声明式事务,Neither Update Inventory nor Update Balance was performed
4.3.4、@Transactional注解标识的位置
@Transactional
identified in the method上,Only affects that method
@Transactional标识的类上,How will affect all methods in the class
4.3.5、事务属性:只读
①介绍
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作.这样数据库就能够针对查询操作来进行优化.
②使用方式
@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) {
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId, price);
}
③注意
对
增删改操作设置只读会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
4.3.6、事务属性:超时
①介绍
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,Thereby occupying database resources for a long time.而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等).
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行.概括来说就是一句话:超时回滚,释放资源.
②使用方式
@Override
// @Transactional(readOnly = true)
@Transactional(timeout = 3)
public void buyBook(Integer userId, Integer bookId) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId,price);
}
③观察结果
执行过程中抛出异常:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Wed Aug 03 11:00:24 CST 2022
4.3.7、事务属性:回滚策略
①介绍
声明式事务默认只针对运行时异常回滚,编译时异常不回滚.
可以通过@TransactionalSet the rollback strategy in the related properties
- rollbackFor属性:需要设置一个Class类型的对象
- rollbackForClassName属性:需要设置一个字符串类型的全类名
- noRollbackFor属性:需要设置一个Class类型的对象
- rollbackFor属性:需要设置一个字符串类型的全类名
②使用方式
@Override
// @Transactional(readOnly = true)
// @Transactional(timeout = 3)
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer userId, Integer bookId) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//查询图书的价格
Integer price = bookDao.getPriceByBookId(bookId);
//更新图书的库存
bookDao.updateStock(bookId);
//更新用户的余额
bookDao.updateBalance(userId,price);
System.out.println(1/0);
}
③观察结果
Although there is a math operation exception in the purchase book function(ArithmeticException),But the rollback strategy we set is,当出现ArithmeticExceptionNo rollback occurs,Therefore, the operation of purchasing the book is performed normally.
4.3.8、事务属性:事务隔离级别
①介绍
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题.一个事务与其他事务隔离的程度称为隔离级别.SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱.
隔离级别一共有四种:
读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改.读已提交:READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改.可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新.串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作.可以避免任何并发问题,但性能十分低下.
各个隔离级别解决并发问题的能力见下表:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 有 | 有 | 有 |
| READ COMMITTED | 无 | 有 | 有 |
| REPEATABLE READ | 无 | 无 | 有 |
| SERIALIZABLE | 无 | 无 | 无 |
各种数据库产品对事务隔离级别的支持程度:
| 隔离级别 | Oracle | MySQL |
|---|---|---|
| READ UNCOMMITTED | × | √ |
| READ COMMITTED | √(默认) | √ |
| REPEATABLE READ | × | √(默认) |
| SERIALIZABLE | √ | √ |
②使用方式
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
4.3.9、事务属性:事务传播行为
①介绍
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播.例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行.
②测试
创建接口CheckoutService:
public interface CheckoutService {
//结账
void checkOut(Integer userId, Integer[] bookIds);
}
创建实现类CheckoutServiceImpl:
public class CheckoutServiceImpl implements CheckoutService {
@Autowired
private BookService bookService;
@Override
@Transactional
public void checkOut(Integer userId, Integer[] bookIds) {
for (Integer bookId : bookIds) {
bookService.buyBook(userId,bookId);
}
}
}
在BookController中添加方法:
@Autowired
private CheckoutService checkoutService;
public void checkout(Integer[] bookIds, Integer userId){
checkoutService.checkout(bookIds, userId);
}
③观察结果
可以通过@Transactional中的propagationProperty sets transaction propagation behavior
修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性@Transactional(propagation = Propagation.REQUIRED),默认情况,Indicates if an open transaction is available on the current thread,那么就在这个事务中运行.经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()There are transaction notes on it,So execute in this transaction.The price of the two books purchased is 80和50,And the user's balance is 100,Therefore, insufficient balance fails when purchasing the second book,导致整个checkout()回滚,That is, as long as one book cannot be bought,Can't even buy it
@Transactional(propagation = Propagation.REQUIRES_NEW),Indicates whether there is an open transaction on the current thread,都要开启新事务.同样的场景,Every time you buy a book, you are therebuyBook()的事务中执行,So the first book purchase was successful,事务结束,The second book purchase failed,Only the second timebuyBook()中回滚,Purchases of the first book are not affected,Buy as many copies as you can
4.4、基于XML的声明式事务
4.3.1、场景模拟
See annotation-based declarative transactions
4.3.2、修改Spring配置文件
将Spring配置文件中去掉tx:annotation-driven 标签,并添加配置:
<!--基于xml的事务配置-->
<aop:config>
<!-- Configure transaction advice and pointcut expressions -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(*
com.gao.spring.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>
注意:基于xmlImplemented declarative transactions,必须引入aspectJ的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
边栏推荐
- 附加:对于“与数据表对应的实体类“,【面对MongoDB时,使用的@Id等注解】和【以前面对MySQL时,使用的@Id等注解】,是不同的;
- 腾讯136道高级岗面试题:多线程+算法+Redis+JVM
- 深度学习21天——准备(环境配置)
- [Cocos] cc.sys.browserType可能的属性
- 商城系统APP如何开发 都有哪些步骤
- Redis common interview questions
- SLSA 框架与软件供应链安全防护
- How to dynamically add script dependent scripts
- npm init [email protected] 构建项目报错SyntaxError: Unexpected token ‘.‘解决办法
- idea设置识别.sql文件类型以及其他文件类型
猜你喜欢

Can‘t connect to MySQL server on ‘localhost3306‘ (10061) 简洁明了的解决方法

DP4398:国产兼容替代CS4398立体声24位/192kHz音频解码芯片

SLSA 框架与软件供应链安全防护

The idea setting recognizes the .sql file type and other file types

3面头条,花7天整理了面试题和学习笔记,已正式入职半个月

idea设置识别.sql文件类型以及其他文件类型

npm安装依赖报错npm ERR! code ENOTFOUNDnpm ERR! syscall getaddrinfonpm ERR! errno ENOTFOUND

高性能高可靠性高扩展性分布式防火墙架构

TSF微服务治理实战系列(一)——治理蓝图

Performance testing with Loadrunner
随机推荐
[Evaluation model] Topsis method (pros and cons distance method)
深度学习环境配置
About yolo7 and gpu
Get the selected content of the radio box
5个开源组件管理小技巧
谷粒商城-基础篇(项目简介&项目搭建)
Delphi-C端有趣的菜单操作界面设计
QT 如何识别文件的编码格式
【一步到位】Jenkins的安装、部署、启动(完整教程)
The 2022 PMP exam has been delayed, should we be happy or worried?
[One step in place] Jenkins installation, deployment, startup (complete tutorial)
Large chain best freight d audit with what software?What are the functions?
3000 words, is take you understand machine learning!
C专家编程 第4章 令人震惊的事实:数组和指针并不相同 4.2 我的代码为什么无法运行
Landing, the IFC, GFC, FFC concept, layout rules, forming method, use is analysed
TSF微服务治理实战系列(一)——治理蓝图
力扣:96.不同的二叉搜索树
Shocked, 99.9% of the students didn't really understand the immutability of strings
MySQL日志篇,MySQL日志之binlog日志,binlog日志详解
static在不同位置定义变量居然还有不同的含义?
