目录
阅读这篇文章,解释上一个发的Spring-JUnit测试单元一起看
事务是数据库操作的一个基本单元,它是一系列数据库操作的集合。这些操作被看作是一个整体,具有以下几个特点
简而言之,事务就像是数据库中的一次“原子”操作,要么全部发生,要么全部不发生,不会出现部分操作生效、部分未生效的情况。
事务具备以下四个重要特性,通常以ACID表示
A:原子性(Atomicity)
原子性确保事务中的所有操作要么全部成功完成,要么全部失败回滚。这意味着事务内部的操作是不可分割的,对于外界来说,一个事务看起来就像是一个单一的操作。如果事务在执行过程中遇到任何问题(如系统故障、逻辑错误等),所有已完成的操作都会被撤销,数据库状态回到事务开始前,保证了数据的完整性。
C:一致性(Consistency)
一致性确保事务执行前后,数据库始终处于一致的状态。这意味着事务的执行结果必须使数据库从一个有效的状态转换到另一个有效的状态。无论事务成功提交还是因故回滚,都不会破坏数据库的完整性约束(如主键唯一、外键引用等)和业务规则。
I:隔离性(Isolation)
隔离性保证在并发环境中,多个事务同时执行时,它们之间互不影响。每个事务都好像在独立、隔离的环境中操作数据,无法看到其他事务未提交的中间结果。为实现隔离性,数据库系统通常提供不同的隔离级别(如读未提交、读已提交、可重复读、串行化等),在不同程度上防止脏读、不可重复读、幻读等问题的发生。
D:持久性(Durability)
持久性确保一旦事务成功提交,其所做的更改就会永久保存在数据库中,即使发生系统崩溃、电源故障等情况,这些更改也不会丢失。数据库通过事务日志(Transaction Log)等机制,确保在系统恢复后能够将已提交事务的更改正确地还原到数据库中,保持数据的持久性。
总结:事务是数据库中保证数据完整性和一致性的基础工具,通过原子性、一致性、隔离性和持久性这四个特性,确保了在并发环境下进行复杂数据操作时,数据状态的正确性和可靠性。理解并正确运用事务是进行数据库编程和管理的重要基础。
编程式事务是指在编写程序时,由开发者直接使用编程语言(如Java、Python等)调用数据库提供的API或接口,手动控制事务的开启、提交和回滚等操作。这种方式让开发者对事务管理有完全的控制权,但同时也需要处理事务相关的所有细节。
简单说就是:事务功能的相关操作全部通过自己编写代码来实现
- Connection conn = ...;
-
- try {
-
- // 开启事务:关闭事务的自动提交
- conn.setAutoCommit(false);
-
- // 核心操作
-
- // 提交事务
- conn.commit();
-
- }catch(Exception e){
-
- // 回滚事务
- conn.rollBack();
-
- }finally{
-
- // 释放数据库连接
- conn.close();
-
- }
编程式的实现方式存在缺陷:
声明式事务是相对于编程式事务而言的一种更为简洁、高级的事务管理方式。它将事务控制的逻辑从具体的业务代码中抽离出来,通过配置文件、注解或其他元数据方式,以声明的方式告诉框架(如Spring、Hibernate等)应该如何管理事务。
声明式事务:
@Transactional
)、标签或属性,声明该方法或类需要进行事务管理。@Transactional
的方法被调用时,Spring会在方法开始前开启事务,方法结束后根据方法执行情况(正常返回还是抛出异常)决定提交或回滚事务。整个过程对业务代码透明,开发者只需关注业务逻辑本身。声明式事务的优势:
总之,声明式事务是一种以配置代替手动编码的方式来管理事务的方法,它极大地简化了事务处理工作,提高了代码质量,是现代企业级应用中广泛采用的事务管理方式。对于初学者来说,理解声明式事务的关键在于认识到其通过声明(如注解)而非直接编写事务控制代码来管理事务,从而减轻开发负担,提升开发效率和代码质量。随着对框架的学习和使用,您会逐渐熟悉如何在实际项目中配置和使用声明式事务。
用户买书过程演示事务操作
在beans.xml添加配置
- <context:component-scan base-package="com.sakurapaid.spring6.tx"/>
- 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,'Java核心技术卷',80,100),(2,'算法导论',50,100);
-
- CREATE TABLE `t_user` (
- `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_user`(`user_id`,`username`,`balance`) values (1,'Sakurapaid',50);
创建BookController
- package com.sakurapaid.spring6.tx.controller;
-
- import com.sakurapaid.spring6.tx.service.BookService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
-
- /**
- * 图书控制器类,负责处理图书相关的HTTP请求。
- */
- @Controller
- public class BookController {
-
- /**
- * 自动注入的图书服务接口,用于执行图书相关的业务逻辑。
- */
- @Autowired
- private BookService bookService;
-
- /**
- * 处理购买图书的请求。
- * @param bookId 要购买的图书ID。
- * @param userId 进行购买的用户ID。
- * 该方法没有返回值,购买操作的结果(例如,是否成功,库存是否足够等)会通过其他方式(如异常、日志)呈现。
- */
- public void buyBook(Integer bookId, Integer userId){
- bookService.buyBook(bookId, userId);
- }
- }
创建接口BookService
- /**
- * 图书服务接口,提供购买图书的功能。
- */
- package com.sakurapaid.spring6.tx.service;
-
- public interface BookService {
- /**
- * 购买图书
- *
- * @param bookId 图书ID,指定要购买的图书。
- * @param userId 用户ID,指定进行购买的用户。
- * 该方法没有返回值,购买操作的结果(例如成功或失败)通过其他方式(如异常处理或日志记录)来处理。
- */
- void buyBook(Integer bookId, Integer userId);
- }
创建实现类BookServiceImpl
- package com.sakurapaid.spring6.tx.service;
-
- import com.sakurapaid.spring6.tx.dao.BookDao;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
-
- /**
- * 图书服务实现类,提供购买图书的功能。
- */
- @Service
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
- /**
- * 购买图书。
- * @param bookId 图书ID
- * @param userId 用户ID
- * 该方法会查询图书价格,更新图书库存和用户余额。
- */
- @Override
- public void buyBook(Integer bookId, Integer userId) {
- // 查询图书价格
- Integer price = bookDao.getPriceByBookId(bookId);
- // 更新图书库存
- bookDao.updateStock(bookId);
- // 更新用户余额
- bookDao.updateBalance(userId, price);
- }
- }
创建接口BookDao
- package com.sakurapaid.spring6.tx.dao;
-
- /**
- * 图书数据访问接口
- */
- public interface BookDao {
-
- /**
- * 根据图书ID获取图书价格
- *
- * @param bookId 图书ID
- * @return 图书价格,返回类型为整数
- */
- Integer getPriceByBookId(Integer bookId);
-
- /**
- * 更新图书库存
- *
- * @param bookId 图书ID
- */
- void updateStock(Integer bookId);
-
- /**
- * 更新用户余额
- *
- * @param userId 用户ID
- * @param price 更新的金额
- */
- void updateBalance(Integer userId, Integer price);
- }
创建实现类BookDaoImpl
- package com.sakurapaid.spring6.tx.dao;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.stereotype.Repository;
-
- @Repository // 标识为数据库访问层组件
- public class BookDaoImpl implements BookDao {
-
- @Autowired // 自动注入JdbcTemplate
- private JdbcTemplate jdbcTemplate;
-
- /**
- * 根据书本ID获取价格
- * @param bookId 书本的ID
- * @return 返回对应书本的价格,如果不存在则返回null
- */
- @Override
- public Integer getPriceByBookId(Integer bookId) {
- String sql = "select price from t_book where book_id = ?";
- return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
- }
-
- /**
- * 更新书本库存
- * @param bookId 书本的ID
- */
- @Override
- public void updateStock(Integer bookId) {
- String sql = "update t_book set stock = stock - 1 where book_id = ?";
- jdbcTemplate.update(sql, bookId);
- }
-
- /**
- * 更新用户余额
- * @param userId 用户的ID
- * @param price 扣除的金额
- */
- @Override
- public void updateBalance(Integer userId, Integer price) {
- String sql = "update t_user set balance = balance - ? where user_id = ?";
- jdbcTemplate.update(sql, price, userId);
- }
- }
项目结构
方便测试,用户余额改为1000
创建测试类
- package com.sakurapaid.spring6.tx;
-
- import com.sakurapaid.spring6.tx.controller.BookController;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
-
- @SpringJUnitConfig(locations = "classpath:beans.xml")
- public class TxByAnnotationTest {
- @Autowired
- private BookController bookController;
-
- @Test
- public void testBuyBook(){
- bookController.buyBook(1, 1);
- }
- }
测试之前的表数据
测试之后的表数据
上面的是正常的案例,现在举例一个错误情况来引出事务
假设用户id为1的用户,购买id为1的图书
用户余额为50,而图书价格为80
购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段
再执行刚才的测试语句
报错了,错误信息简单解释就是
错误信息指出,在执行数据库更新操作时,尝试从t_user表的balance字段中减去一个值(在这个案例中是80),但由于balance字段被定义为BIGINT UNSIGNED类型,即一个无符号的大整数,它的值不能为负。因此,当尝试执行这个减法操作时,数据库抛出了DataIntegrityViolationException异常,导致程序异常终止。
因为没有添加事务,图书的库存更新了,但是用户的余额没有更新 显然这样的结果是错误的,购买图书是一个完整的功能,更新库存和更新余额要么都成功要么都失败
在spring配置文件中引入tx命名空间
- <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">
在Spring的配置文件中添加配置
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="druidDataSource"></property>
- </bean>
-
- <!--
- 开启事务的注解驱动
- 通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
- -->
- <!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
- <tx:annotation-driven transaction-manager="transactionManager" />
完整的是这样的
- "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.sakurapaid.spring6.tx"/>
-
-
- <context:property-placeholder location="classpath:jdbc.properties"/>
-
-
- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
-
- <property name="driverClassName" value="${jdbc.driver}"/>
- <property name="url" value="${jdbc.url}"/>
- <property name="username" value="${jdbc.username}"/>
- <property name="password" value="${jdbc.password}"/>
- 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>
因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理
在BookServiceImpl的buybook()添加注解@Transactional
- package com.sakurapaid.spring6.tx.service;
-
- import com.sakurapaid.spring6.tx.dao.BookDao;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
-
- /**
- * 图书服务实现类,提供购买图书的功能。
- */
- @Service
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
- /**
- * 购买图书。
- * @param bookId 图书ID
- * @param userId 用户ID
- * 该方法会查询图书价格,更新图书库存和用户余额。
- */
- @Override
- @Transactional
- public void buyBook(Integer bookId, Integer userId) {
- // 查询图书价格
- Integer price = bookDao.getPriceByBookId(bookId);
- // 更新图书库存
- bookDao.updateStock(bookId);
- // 更新用户余额
- bookDao.updateBalance(userId, price);
- }
- }
重新测试输出
表数据改为初始值
测试类还是报一样的错
但是表数据没有发生变化,因为事务的回滚和初始值是一样的
@Transactional标识在方法上,则只会影响该方法
@Transactional标识的类上,则会影响类中所有的方法
事务注解是可以写属性值的,简单说有一下几种
视频例子可以点这个链接 --> 事务属性
①介绍
事务的"只读"属性是一种特殊的设置,用于告诉Spring框架和数据库,当前事务仅包含查询操作,而不会进行数据的增加、删除或修改。这个设置可以让数据库针对查询进行优化,因为数据库知道不需要对数据的写操作进行保护,从而可能提高查询效率。
②使用方式--readOnly
在Spring框架中,可以通过@Transactional
注解的readOnly
属性来设置一个方法为只读事务。例如,当你调用一个方法并希望它只进行查询操作时,可以这样设置:
- @Transactional(readOnly = true)
- public void someQueryMethod() {
- // 执行查询操作
- }
在你的示例代码中,buyBook
方法被标记为只读,但是它实际上尝试执行更新操作(如bookDao.updateStock
和bookDao.updateBalance
),这是不允许的,因为只读事务不允许进行写操作。
③注意
如果你尝试在只读事务中执行写操作,比如更新或删除数据,数据库会抛出异常,因为这种行为违反了只读事务的规则。异常信息"java.sql.SQLException: Connection is read-only"表明你正在尝试在一个只读的数据库连接上执行写操作,这是不被允许的。
因此,如果你的方法中需要执行写操作,那么不应该将其设置为只读事务。只读属性应该仅用于那些确实不需要修改任何数据的方法。
①介绍
事务超时是一个预防措施,用来确保事务不会无限期地占用数据库资源。如果在指定的时间内事务没有完成,系统会自动中断该事务,并执行回滚操作。这样做可以防止事务长时间运行,可能引起的资源占用和潜在的死锁问题,确保系统的稳定性和响应性。
②使用方式--timeout
在Spring框架中,可以通过@Transactional
注解的timeout
属性来设置事务的超时时间。超时时间是指事务在完成之前可以运行的最长时间。如果超过了这个时间限制,Spring将抛出一个TransactionTimedOutException
异常,并且事务会被回滚。
例如,下面的代码设置了一个3秒的超时时间:
- @Transactional(timeout = 3)
- public void someMethod() {
- // 事务操作
- }
③观察结果
当事务超时发生时,Spring会抛出TransactionTimedOutException
异常,这个异常表明事务因为超过了设定的执行时间而没有完成。这个异常的抛出是框架自动进行的,目的是中断长时间运行的事务,释放数据库资源,防止资源长时间被占用导致的问题。
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022
这条信息告诉我们,事务因为超过了3秒的超时限制而没有完成,系统设定的最后期限是2022年6月4日16点25分39秒。此时,事务会被回滚,所有在事务中已经执行的操作都会被撤销。
①介绍
在Spring的声明式事务管理中,默认情况下,只有运行时异常(RuntimeException)会导致事务回滚。这意味着,如果方法内部抛出了运行时异常,Spring会自动回滚事务,撤销已经进行的所有数据库操作,以保持数据的一致性。
然而,对于非运行时异常(即预检查异常,继承自Exception
类但不是RuntimeException
的子类),默认情况下不会触发事务回滚
为了更精细地控制哪些异常会导致事务回滚,可以通过@Transactional
注解的rollbackFor
和noRollbackFor
属性来设置回滚策略。rollbackFor
属性用于指定当抛出这些异常时应该回滚事务,而noRollbackFor
属性用于指定当抛出这些异常时不应该回滚事务。
②使用方式
可以通过@Transactional中相关属性设置回滚策略
使用了@Transactional(noRollbackFor = ArithmeticException.class)
注解,这意味着当buyBook
方法内部抛出ArithmeticException
异常时,事务不会回滚。这可以通过noRollbackFor
属性来设置:
- @Transactional(noRollbackFor = ArithmeticException.class)
- //@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
- public void buyBook(Integer bookId, Integer userId) {
- //查询图书的价格
- Integer price = bookDao.getPriceByBookId(bookId);
- //更新图书的库存
- bookDao.updateStock(bookId);
- //更新用户的余额
- bookDao.updateBalance(userId, price);
- System.out.println(1/0);
- }
③观察结果
在buyBook
方法中,故意通过System.out.println(1/0);
制造了一个除以零的数学运算异常(ArithmeticException
)。根据设置的回滚策略,即使出现了这个异常,事务也不会回滚。这可能会导致数据不一致的情况,因为方法中的其他操作(如更新图书库存和用户余额)可能已经提交到数据库。
需要注意的是,在实际应用中,通常建议让所有异常都触发事务回滚,以保持数据的完整性和一致性。设置noRollbackFor
属性来防止事务回滚应该非常谨慎,并且只在你确定即使出现异常也应该保持数据变更的情况下使用。
①介绍
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别一共有四种:
各个隔离级别解决并发问题的能力见下表:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
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)//串行化
①介绍
什么是事务的传播行为?
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
一共有七种传播行为:
常用的是:REQUIRED、REQUIRES_NEW
②测试
创建接口CheckoutService
- package com.sakurapaid.spring6.tx.service;
-
- public interface CheckoutService {
- void checkout(Integer[] bookIds, Integer userId);
- }
创建实现类CheckoutServiceImpl
- package com.sakurapaid.spring6.service.impl;
-
- @Service
- public class CheckoutServiceImpl implements CheckoutService {
-
- @Autowired
- private BookService bookService;
-
- @Override
- @Transactional
- //一次购买多本图书
- public void checkout(Integer[] bookIds, Integer userId) {
- for (Integer bookId : bookIds) {
- bookService.buyBook(bookId, userId);
- }
- }
- }
BookServiceImpl 添加事务注解
- package com.sakurapaid.spring6.tx.service;
-
- import com.sakurapaid.spring6.tx.dao.BookDao;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Propagation;
- import org.springframework.transaction.annotation.Transactional;
-
- /**
- * 图书服务实现类,提供购买图书的功能。
- */
- @Service
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public class BookServiceImpl implements BookService {
-
- @Autowired
- private BookDao bookDao;
-
- /**
- * 购买图书。
- * @param bookId 图书ID
- * @param userId 用户ID
- * 该方法会查询图书价格,更新图书库存和用户余额。
- */
- @Override
- public void buyBook(Integer bookId, Integer userId) {
- // 查询图书价格
- Integer price = bookDao.getPriceByBookId(bookId);
- // 更新图书库存
- bookDao.updateStock(bookId);
- // 更新用户余额
- bookDao.updateBalance(userId, price);
- }
- }
在BookController中添加方法:
- @Autowired
- private CheckoutService checkoutService;
-
- public void checkout(Integer[] bookIds, Integer userId){
- checkoutService.checkout(bookIds, userId);
- }
在数据库中将用户的余额修改为100元
③观察结果
可以通过@Transactional中的propagation属性设置事务传播行为
修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性
@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了
@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本。
可以结合视频例子一起看 --> 70.事务-基于注解的声明式事务-全注解配置事务_哔哩哔哩_bilibili
@Configuration // 标识为配置类
@ComponentScan("com.sakurapaid.spring6") // 扫描包
@EnableTransactionManagement // 开启事务管理
- package com.sakurapaid.spring6.tx.config;
-
- import com.alibaba.druid.pool.DruidDataSource;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.jdbc.datasource.DataSourceTransactionManager;
- import org.springframework.transaction.annotation.EnableTransactionManagement;
- import javax.sql.DataSource;
-
- /**
- * 此类作为Spring应用的核心配置类,主要负责以下功能:
- * - 使用{@link ComponentScan}注解进行包扫描,自动发现并加载指定包("com.sakurapaid.spring6")下的Spring组件。
- * - 通过{@link EnableTransactionManagement}注解启用Spring的事务管理支持。
- * - 定义并初始化关键的基础设施bean,包括数据源、JdbcTemplate以及事务管理器。
- */
- @Configuration
- @ComponentScan("com.sakurapaid.spring6")
- @EnableTransactionManagement
- public class SpringConfig {
-
- /**
- * 创建并配置Druid数据源bean。
- * 参数说明:
- * - 无参数输入
- * 返回值说明:
- * - 返回一个已配置好的DruidDataSource实例,供Spring容器管理和后续数据库访问组件使用。
- * 数据源配置详情:
- * - 使用MySQL的JDBC驱动:com.mysql.cj.jdbc.Driver
- * - 连接本地MySQL服务器,端口3306,数据库名:spring6
- * - 设置字符编码为UTF-8,禁用SSL连接
- * - 用户名:root,密码:2076805863
- */
- @Bean
- public DataSource getDataSource(){
- DruidDataSource dataSource = new DruidDataSource();
- dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
- dataSource.setUrl("jdbc:mysql://localhost:3306/spring6?characterEncoding=utf-8&useSSL=false");
- dataSource.setUsername("root");
- dataSource.setPassword("2076805863");
- return dataSource;
- }
-
- /**
- * 创建并配置JdbcTemplate bean。
- * 参数说明:
- * - {@code dataSource}: 已经配置好的数据源,用于提供与数据库交互的能力。
- * 返回值说明:
- * - 返回一个关联了指定数据源的JdbcTemplate实例,该实例封装了对SQL查询和更新的操作,简化了DAO层的实现。
- */
- @Bean(name = "jdbcTemplate")
- public JdbcTemplate getJdbcTemplate(DataSource dataSource){
- JdbcTemplate jdbcTemplate = new JdbcTemplate();
- jdbcTemplate.setDataSource(dataSource);
- return jdbcTemplate;
- }
-
- /**
- * 创建并配置DataSourceTransactionManager bean。
- * 参数说明:
- * - {@code dataSource}: 已经配置好的数据源,用于提供事务管理所需的数据库连接信息。
- * 返回值说明:
- * - 返回一个关联了指定数据源的DataSourceTransactionManager实例,该实例实现了平台无关的事务管理逻辑,基于数据源进行事务控制。
- */
- @Bean
- public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
- DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
- dataSourceTransactionManager.setDataSource(dataSource);
- return dataSourceTransactionManager;
- }
- }
在这之前,记得在 XML 配置文件中注释相关的事务操作什么的,以免起冲突
- package com.sakurapaid.spring6.tx;
-
- import com.sakurapaid.spring6.tx.config.SpringConfig;
- import com.sakurapaid.spring6.tx.controller.BookController;
- import org.junit.jupiter.api.Test;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-
- public class TestAno {
-
- @Test
- public void testTxAllAnnotation(){
- ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
- BookController accountService = applicationContext.getBean("bookController", BookController.class);
- accountService.buyBook(1, 1);
- }
- }
可以结合视频例子一起看 --> 71.事务-基于XML的声明式事务-具体实现_哔哩哔哩_bilibili
参考基于注解的声明式事务
将Spring配置文件中去掉tx:annotation-driven 标签,并添加配置
- <aop:config>
-
- <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))">aop:advisor>
- aop:config>
- <tx:advice id="txAdvice" transaction-manager="transactionManager">
- <tx:attributes>
-
-
- <tx:method name="get*" read-only="true"/>
- <tx:method name="query*" read-only="true"/>
- <tx:method name="find*" read-only="true"/>
-
-
-
-
-
-
-
- <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>
注意:基于xml实现的声明式事务,必须引入aspectJ的依赖
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-aspects</artifactId>
- <version>6.0.2</version>
- </dependency>