Spring5事务管理
事务概念复习
什么是事务?
-
什么是事务?
事务是数据库操作的最基本单元,逻辑上的一组操作,要么都成功,要么都失败
-
典型事务场景:银行转账
比如:从 A 账户向 B 账户中转账 10000,将 A 账户的钱减去 10000,将 B 账户的钱增加 10000
事务四大特性(ACID)
- A:原子性(说明事务是最小的工作单元,不可再分)
- C:一致性(所有事务要求,在同一个事务当中,所有操作必须同时成功,或者同时失败,以保证数据的安全性)
- I:隔离性(A 事务和 B 事务之间具有一定的隔离(比如:教室 A 和教室 B 中间有一道墙,这道墙就是隔离性))
- D:持久性(事务最终结束的一个保障,事务提交,就相当于将没有保存到硬盘上的数据保存到硬盘上)
详情可看:https://blog.csdn.net/Cicci_/article/details/121952760?spm=1001.2014.3001.5501
搭建事务操作环境
- 案例:使用事务实现简单的转账操作
这里会给大家演示没有事务时出现的问题和解决的方案
一、创建数据库表(t_account),添加记录
表结构:
表数据:
二、创建 service,搭建 dao,完成对象创建和注入关系
-
service 注入 dao,在 dao 中注入 JdbcTemplate,在 JdbcTemplate 中注入DataSource
JdbcTemplate 配置:
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone"> <property name="url" value="jdbc:mysql:///spring_test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!-- 开启组件扫描 --> <context:component-scan base-package="com.laoyang.spring" /> <!-- 创建 JdbcTemplate 对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入 dataSource --> <property name="dataSource" ref="dataSource" /> </bean> </beans>
UserDao 接口:
package com.laoyang.spring.dao; public interface UserDao { }
UserDaoImpl 实现类:
package com.laoyang.spring.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class UserDaoImpl implements UserDao { /** * 注入 JdbcTemplate */ @Autowired private JdbcTemplate jdbcTemplate; }
UserService 类:
package com.laoyang.spring.service; import com.laoyang.spring.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { /** * 注入 DAO */ @Autowired private UserDao userDao; }
三、在 dao 和 service 创建对应的方法
-
dao 创建两个方法(多钱和少钱的方法)
package com.laoyang.spring.dao; public interface UserDao { /** * 多钱 */ public void addMoney(); /** * 少钱 */ public void reduceMoney(); }
package com.laoyang.spring.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class UserDaoImpl implements UserDao { /** * 注入 JdbcTemplate */ @Autowired private JdbcTemplate jdbcTemplate; /** * 多钱 * 需求:把 lucy 转的钱增加到 mary 的账户上 */ @Override public void addMoney() { String sql = "update t_account set money = money+? where username = ?"; Object[] objects = {100, "mary"}; int update = jdbcTemplate.update(sql, objects); if (update > 0) { System.out.println("收款成功!"); return; } System.out.println("收款失败!"); } /** * 少钱 * 需求:lucy 转账 100 给 mary */ @Override public void reduceMoney() { String sql = "update t_account set money = money-? where username = ?"; Object[] objects = {100, "lucy"}; int update = jdbcTemplate.update(sql, objects); if (update > 0) { System.out.println("转账成功!"); return; } System.out.println("转账失败!"); } }
-
service 创建一个方法(转账的方法)
package com.laoyang.spring.service; import com.laoyang.spring.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { /** * 注入 DAO */ @Autowired private UserDao userDao; /** * 转账操作 */ public void transferAccounts () { // 先减去 luay 的 100 元 userDao.reduceMoney(); // 在增加 mary 的 100 元 userDao.addMoney(); } }
-
测试效果
package com.laoyang.spring.test; import com.laoyang.spring.service.UserService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class UserTest { @Test public void testTransferAccounts() { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); UserService userService = context.getBean("userService", UserService.class); userService.transferAccounts(); } }
-
数据库效果
小问题
以上代码正常执行是没有任何问题的,但是如果在执行过程中出现了异常,就会出现一些问题,比如:
- lucy 转出 100 元之后出现了异常,导致 mary 没有接收到转过来的 100 元(也就是说你钱没了,但是事没办成)
演示出现问题时的效果
-
先将数据库中的数据手动设置回每个人 1000 元
-
手动在 service 转账方法中制造一个异常
package com.laoyang.spring.service; import com.laoyang.spring.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { /** * 注入 DAO */ @Autowired private UserDao userDao; /** * 转账操作 */ public void transferAccounts () { // 先减去 luay 的 100 元 userDao.reduceMoney(); // 制造异常 int i = 10 / 0; // 在增加 mary 的 100 元 userDao.addMoney(); } }
-
测试效果
package com.laoyang.spring.test; import com.laoyang.spring.service.UserService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class UserTest { @Test public void testTransferAccounts() { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); UserService userService = context.getBean("userService", UserService.class); userService.transferAccounts(); } }
-
数据库效果
我们可以明显的看到,lucy 虽然转出了 100 元,但是这 100 元没有成功转到 mary 的账户上
解决方案
使用事务进行解决,步骤如下:
- 开启事务
- 进行业务操作(使用 try - catch 捕获异常)
- 如果没有出现异常,则提交事务
- 如果出现了异常,则回滚事务
package com.laoyang.spring.service;
import com.laoyang.spring.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
@Service
public class UserService {
/**
* 注入 DAO
*/
@Autowired
private UserDao userDao;
/**
* 转账操作
* 以下是使用普通方式解决,但是比较麻烦,所以只把步骤写了一下,,没有写实现的操作
*/
public void transferAccounts () {
// 1、开启事务
// 2、进行业务操作
try {
// 先减去 luay 的 100 元
userDao.reduceMoney();
// 模拟异常
int i = 10 / 0;
// 在增加 mary 的 100 元
userDao.addMoney();
// 3、如果没有出现异常,则提交事务
} catch (Exception e) {
// 4、如果出现了异常,则回滚事务
}
}
}
虽然现在也可以用代码实现,但是既然是学 Spring,所以还是使用 Spring 的方式实现吧(而且 Spring 的方式更加简单),下面就会为大家一 一介绍 Spring 事务的使用方法
Spring 事务管理
说明
-
事务添加到 JavaEE 三层结构中的 Service 层(业务逻辑层)
-
在 Spring 中进行事务管理操作有两种方式:
编程式事务管理
和声明式事务管理
开发中一般都是使用声明式事务管理,因为比较简便。
上面解决方案中说的代码实现就是编程式事务管理,这种方式比较繁琐。
-
声明式事务管理:
- 基于注解方式实现(推荐)
- 基于 xml 配置文件方式实现
-
Spring 声明式事务管理,底层使用了 AOP 原理
Spring 事务管理提供的 API
- PlatformTransactionManager 接口:代表事务管理器,这个接口针对不同的框架提供了不同的实现类
声明式事务管理(基于注解实现)
-
1、在 Spring 配置文件中配置事务管理器
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone"> <property name="url" value="jdbc:mysql:///spring_test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!-- 开启组件扫描 --> <context:component-scan base-package="com.laoyang.spring" /> <!-- 创建 JdbcTemplate 对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入 dataSource --> <property name="dataSource" ref="dataSource" /> </bean> <!-- ====================下面是新增的代码==================== --> <!-- 创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource" /> </bean> </beans>
-
2、在 Spring 配置文件中引入 tx 名称空间
<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd ">
-
3、开启事务注解
<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd "> <!-- 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone"> <property name="url" value="jdbc:mysql:///spring_test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!-- 开启组件扫描 --> <context:component-scan base-package="com.laoyang.spring" /> <!-- 创建 JdbcTemplate 对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入 dataSource --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- ====================下面是新增的代码==================== --> <!-- 开启事务注解 transaction-manager:通过哪个事务管理器来开启注解 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>
-
4、在 service 类上(或者 service 类里面的方法上)添加事务注解(@Transactional)
@Transactional
:该注解可以加在类 / 方法上(如果添加到类上,则表示这个类中的所有方法都添加事务,如果添加到方法上,则表示为该方法添加事务)
package com.laoyang.spring.service; import com.laoyang.spring.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @Transactional:该注解可以加在类 / 方法上, * 如果添加到类上,则表示这个类中的所有方法都添加事务 * 如果添加到方法上,则表示为该方法添加事务 * @Date 2021/12/21 17:27 */ @Service @Transactional public class UserService { /** * 注入 DAO */ @Autowired private UserDao userDao; /** * 转账操作 */ public void transferAccounts () { // 先减去 luay 的 100 元 userDao.reduceMoney(); // 模拟异常 int i = 10 / 0; // 在增加 mary 的 100 元 userDao.addMoney(); } }
-
测试效果
package com.laoyang.spring.test; import com.laoyang.spring.service.UserService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.transaction.PlatformTransactionManager; public class UserTest { @Test public void testTransferAccounts() { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); UserService userService = context.getBean("userService", UserService.class); userService.transferAccounts(); } }
-
数据库效果
这波我们可以看到,添加事务之后,如果中途出现异常,也不会影响到数据,就比如 lucy 刚开始转了 100 元出去,但是因为中途出现了问题,钱又退回来了
@Transactional 注解参数说明
- 刚才在 service 类上添加了 @Transactional 注解,在这个注解中可以配置事务相关参数
主要使用框起来的这几个参数
propagation:事务传播行为
-
事务传播行为:多事务方法之间进行调用,这个过程中事务进行管理的方式就被称为事务传播行为
-
Spring 框架事务传播行为有 7 种:
-
REQUIRED
:如果有事务在运行,当前的方法就在这个事务内运行,否则就启动一个新的事务,并在自己的事务内运行如果 add 方法本身就有事务,调用 update 方法之后,update 使用当前 add 方法里面的事务
如果 add 方法本身没有事务,调用 update 方法之后,创建新的事务
-
REQUIRED_NEW
:当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起使用 add 方法调用 update 方法,add 不管有没有事务,都会创建新的事务
-
以上两种是比较常用的事务传播行为,其余的大家也可以了解一下,然后根据自己的需求来进行使用
-
// 设置事务传播行为
@Transactional(propagation = Propagation.REQUIRED)
isolation:事务隔离级别
-
事务中的隔离性:多事务操作之间互不影响(如果不考虑隔离性,就会出现很多问题)
-
主要有三个读问题:脏读、不可重复读、幻影读
-
脏读:一个未提交事务读取到另一个未提交事务的数据
-
不可重复读:一个未提交事务读取到另一个已提交事务的数据
-
幻影读:一个未提交事务每次读取的都是开启事务时的数据
-
通过设置事务隔离级别,解决读问题(下图中的无表示可解决,有表示可能出现该问题):
详情可看:https://blog.csdn.net/Cicci_/article/details/121952760?spm=1001.2014.3001.5501
// 设置隔离级别
@Transactional(isolation = Isolation.REPEATABLE_READ)
timeout:超时时间
- 事务需要在一定的时间内进行提交,如果不提交就会进行回滚
- 默认超时时间是 -1,以秒为单位
// 设置超时时间
@Transactional(timeout = 5)
readOnly:是否只读
- 读:查询操作
- 写:增删改操作
- readOnly 默认值为 false,表示可以查询,也可以进行增删改操作
- 注意:当 readOnly 设置为 true 后,就只能执行查询操作!
// 设置只读
@Transactional(readOnly = true)
rollbackFor:回滚
- 设置出现哪些异常进行事务回滚
// 设置出现哪些异常进行事务回滚
@Transactional(rollbackFor = Exception.class)
noRollbackFor:不回滚
- 设置出现哪些异常不进行事务回滚
// 设置出现哪些异常进行事务回滚
@Transactional(noRollbackFor = Exception.class)
声明式事务(基于 xml 文件实现)
-
这里还是使用前面的 service 和 dao,所以这里就不写了
-
1、在 Spring 配置文件中进行配置
第一步:配置事务管理器
<?xml version="1.0" encoding="UTF-8"?> <!-- 引入 aop 和 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" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> <!-- 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone"> <property name="url" value="jdbc:mysql:///spring_test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!-- 开启组件扫描 --> <context:component-scan base-package="com.laoyang.spring" /> <!-- 创建 JdbcTemplate 对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入 dataSource --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource" /> </bean> </beans>
第二步:配置通知
<?xml version="1.0" encoding="UTF-8"?> <!-- 引入 aop 和 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" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> <!-- 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone"> <property name="url" value="jdbc:mysql:///spring_test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!-- 开启组件扫描 --> <context:component-scan base-package="com.laoyang.spring" /> <!-- 创建 JdbcTemplate 对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入 dataSource --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置通知 --> <tx:advice id="txAdvice"> <!-- 配置事务相关参数 --> <tx:attributes> <!-- 指定哪种规则的方法上添加事务 name:对应的方法名 propagation就是注解中的参数,可根据自己的需求来进行对应的事务配置 > 这里只是演示效果,就只写一个 propagation 了 --> <!-- 方式一:直接写对应的方法名 --> <tx:method name="transferAccounts" propagation="REQUIRED"/> <!-- 方式二:写符合规则的方法,这里表示以 transfer 开头的所有方法 --> <!-- <tx:method name="transfer*"/>--> </tx:attributes> </tx:advice> </beans>
第三步:配置切入点和切面
<?xml version="1.0" encoding="UTF-8"?> <!-- 引入 aop 和 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" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> <!-- 数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone"> <property name="url" value="jdbc:mysql:///spring_test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!-- 开启组件扫描 --> <context:component-scan base-package="com.laoyang.spring" /> <!-- 创建 JdbcTemplate 对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入 dataSource --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置通知 --> <tx:advice id="txAdvice"> <!-- 配置事务相关参数 --> <tx:attributes> <!-- 指定哪种规则的方法上添加事务 name:对应的方法名 propagation就是注解中的参数,可根据自己的需求来进行对应的事务配置 > 这里只是演示效果,就只写一个 propagation 了 --> <!-- 方式一:直接写对应的方法名 --> <tx:method name="transferAccounts" propagation="REQUIRED"/> <!-- 方式二:写符合规则的方法,这里表示以 transfer 开头的所有方法 --> <!-- <tx:method name="transfer*"/>--> </tx:attributes> </tx:advice> <!-- 配置切入点和切面 --> <aop:config> <!-- 配置切入点 --> <aop:pointcut id="pt" expression="execution(* com.laoyang.spring.service.UserService.*(..))"/> <!-- 配置切面 advice-ref:通知 pointcut-ref:切入点 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" /> </aop:config> </beans>
-
测试效果
package com.laoyang.spring.test; import com.laoyang.spring.service.UserService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.transaction.PlatformTransactionManager; public class UserTest { @Test public void testTransferAccounts2() { ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml"); UserService userService = context.getBean("userService", UserService.class); userService.transferAccounts(); } }
因为我们之前在 service 中手动制造了一个 bug,所以现在执行肯定的报错的,并且数据库中的数据是不收到影响的
-
数据库效果
声明式事务(完全注解方式)
-
不使用 xml 配置文件,只使用注解进行实现
-
1、创建配置类,使用配置类替代 xml 配置文件
package com.laoyang.spring.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; /** * @ClassName TxConfig * @Description: 配置类 * @Author Laoyang * @Date 2021/12/21 22:27 */ @Configuration @ComponentScan(basePackages = "com.laoyang.spring") @EnableTransactionManagement public class TxConfig { /* @Configuration:将当前类作为配置类 @ComponentScan:组件扫描,相当于<context:component-scan base-package="com.laoyang.spring" /> @EnableTransactionManagement:开启事务,相当于<tx:annotation-driven transaction-manager="transactionManager" /> @Bean:创建对象 */ /** * 配置数据库连接池 * @return */ @Bean public DruidDataSource getDruidDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); druidDataSource.setUrl("jdbc:mysql:///spring_test"); druidDataSource.setUsername("root"); druidDataSource.setPassword("123456"); return druidDataSource; } /** * 创建 JdbcTemplate 对象 * @param dataSource 在 IOC 容器中根据类型找到对应的 dataSource * @return */ @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(); // 注入 dataSource jdbcTemplate.setDataSource(dataSource); /* 如果直接调用方法会不太好,因为 getDruidDataSource() 执行的时候已经创建了 dataSource 对象了 如果我们这里调用该方法注入到 JdbcTemplate 中,那么就相当于又创建了一个 dataSource,所以不太建议这样写 可以根据类型直接从 IOC 容器中找到对应的 dataSource */ // jdbcTemplate.setDataSource(getDruidDataSource()); return jdbcTemplate; } /** * 创建事务管理器 * @param dataSource 在 IOC 容器中根据类型找到对应的 dataSource * @return */ @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
-
测试效果
package com.laoyang.spring.test; import com.laoyang.spring.config.TxConfig; import com.laoyang.spring.service.UserService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.transaction.PlatformTransactionManager; public class UserTest { @Test public void testTransferAccounts3() { ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class); UserService userService = context.getBean("userService", UserService.class); userService.transferAccounts(); } }
-
数据库效果