事务介绍(重要)
事务是指一组数据库操作,要么操作都完成,要么操作都不完成(只要有一个未完成,其他操作即使完成也恢复到未完成的状态)。比如A账户向B账户转账,就包括了2个数据库操作:
- A账户减少一定额度的资金
- B账户增加相同额度的资金
要保证正确转账就必须将转账的2个操作定义到一个事务中,否则就有可能出现A账户转出资金,但是B账户未收到(用户不答应)或 A账户未转资金,而B账户资金增加的情况(银行不答应)。
在实际开发中,会经常涉及事务管理问题,为此 Spring 提供了专门用于事务管理的API。Spring 的事务管理简化了传统事务管理的流程,并且在一定程度上减少了开发者的工作量。Spring 的事务管理分为2种形式:
- 传统的编程式事务管理:通过编写代码实现的事务管理,包括定义事务的开始、正式执行事务提交和异常时的事务回滚(我们能想到 AOP,这就是把事务代码封装到了 “切面”中,也就是第二种声明式事务管理)
- 声明式事务管理:通过 AOP 技术实现的事务管理,其主要思想是将事务管理抽取到“切面”,然后通过 AOP 技术将事务管理的“切面”代码织入到业务目标类中。
声明式事务管理使得开发者在配置文件中进行相关的事务规则声明,无须编程,就可以将事务规则应用到业务逻辑中,减少了工作量,提高了开发效率。所以在实际开发中,通常都选用声明式事务管理。
通 AspectJ 实现 AOP 一样,Spring 的声明式事务管理也可以通过2种方式来实现,分别是基于xml文件和注解的方式。
基于XML方式的声明式事务
通过在配置文件中配置事务规则的相关声明来实现。Spring2.0 以后,提供了 tx 命名空间来配置事务,<tx:advice>
来配置事务的通知/增强处理。使用<aop:advisor>
将 <tx:advice>
配置的事务的通知/增强处理与切入点整合起来,让 Spring 自动生成代理。
我们将通过转账来说明如何使用 XML 方式的声明式事务。
1.准备数据,在mysql中新建测试表 account,
准备 2 行默认数据
2.创建 Maven 项目或模块
创建一个名为 springdemo_03 的 Maven 项目或模块。
3.添加依赖
其中包括 mysql 数据库连接包,spring-jdbc 连接数据库工具、junit4 测试等
代码语言:javascript复制<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
4.编写一个服务类 service.AccountService,在其中定义转账方法 transfer
我们在 AccountService 类中定义:
- 一个 JdbcTemplate 类型的属性 jdbcTemplate 及其
setter
方法,用于给 spring 注入。JdbcTemplate 是 Spring-jdbc 包中的类,可以简化数据库操作,我们这里就调用其update
方法修改账户余额。 - 转账方法
transfer(String outID, String inID, double amt)
第一个参数表示转出资金账户id,第二个参数表示转入资金账户id,第三个参数表示转账金额。
public class AccountService {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
this.jdbcTemplate = jdbcTemplate;
}
public void transfer(String outID, String inID, double amt) {
jdbcTemplate.update("update account set balance = balance - ? where id = ?", amt, outID);
System.out.println("转出资金成功");
jdbcTemplate.update("update account set balance = balance ? where id = ?", amt, inID);
System.out.println("转入资金成功");
System.out.println("转账成功");
}
}
5.编写配置
我们可以在 Spring 配置文件中为 AccountService 对象注入 jdbcTemplate 属性的值,而 jdbcTemplate 对象需要注入 dataSource 属性值才能正确访问数据库,所以 Spring 配置文件如下:
代码语言:javascript复制<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation
="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost/spring_study?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT+8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountService" class="service.AccountService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
6.写测试类
代码语言:javascript复制public class TestTransaction {
@Test
public void testTransfer(){
ApplicationContext ac = new ClassPathXmlApplicationContext("springConfig.xml");
AccountService accountService = ac.getBean("accountService", AccountService.class);
accountService.transfer("007","25",10000);
}
}
打开数据库,刷新 account 表,可以发现转账成功(一减一增)。
我们在他们的中间制造一个异常,即增加一个除数0异常。
代码语言:javascript复制public void transfer(String outID, String inID, double amt) {
jdbcTemplate.update("update account set balance = balance - ? where id = ?", amt, outID);
System.out.println("转出资金成功");
//制造一个异常
int i = 1/0;
jdbcTemplate.update("update account set balance = balance ? where id = ?", amt, inID);
System.out.println("转入资金成功");
System.out.println("转账成功");
}
在执行测试代码,会出错,打开数据库一看,发现 007 的账户扣款了,24的账户还是不变,说明这个程序已经实现严重的数据不完整性了。
这时候就需要我们的事务来处理了,要么两者都成,要么两者都不成。
7.配置为事务
在 Spring 核心配置文件中进行配置,包括:
- 增加 aop.tx 约束
- 配置事务管理器
- 配置事务通知
- 配置 aop,在其中将切入点与事务通知整合
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation
="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost/spring_study?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT+8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountService" class="service.AccountService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer"/>
</tx:attributes>
</tx:advice>
<!-- 配置aop,在其中将切入点与事务通知整合 -->
<aop:config>
<aop:pointcut id="ptTx" expression="execution(* service.AccountService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="ptTx"/>
</aop:config>
</beans>
首先恢复 id 为007和24的账户余额都为30000,然后重新测试。
虽然结果输出了转出资金成功,但查看表数据并没有一个更新一个没更新的情况,证明了事务已经开启,保证了数据完整准确。
基于注解方式的声明式事务
基于 XML 方式的声明式事务还是比较麻烦,而基于注解方式的声明式事务则简单很多,开发者只需要关注两件事。
1.在 Spring 核心配置文件中注册事务注解驱动,其代码如下:
代码语言:javascript复制 <!-- 配置事务注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
全配置文件如下:
代码语言:javascript复制<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation
="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost/spring_study?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT+8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountService" class="service.AccountService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- <!– 配置事务通知 –>-->
<!-- <tx:advice id="txAdvice" transaction-manager="transactionManager">-->
<!-- <tx:attributes>-->
<!-- <tx:method name="transfer"/>-->
<!-- </tx:attributes>-->
<!-- </tx:advice>-->
<!-- <!– 配置aop,在其中将切入点与事务通知整合 –>-->
<!-- <aop:config>-->
<!-- <aop:pointcut id="ptTx" expression="execution(* service.AccountService.*(..))"/>-->
<!-- <aop:advisor advice-ref="txAdvice" pointcut-ref="ptTx"/>-->
<!-- </aop:config>-->
</beans>
2.在需要使用事务的bean类或者bean类的方法上添加注解 @Transactional
如果将注解添加到类上,则表示事务的设置对整个类的所有方法都起作用;如果将注解添加在类的某个方法上,则表示事务的设置只对该方法有效。
代码语言:javascript复制package service;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class AccountService {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
this.jdbcTemplate = jdbcTemplate;
}
public void transfer(String outID, String inID, double amt) {
jdbcTemplate.update("update account set balance = balance - ? where id = ?", amt, outID);
System.out.println("转出资金成功");
//制造一个异常
int i = 1/0;
jdbcTemplate.update("update account set balance = balance ? where id = ?", amt, inID);
System.out.println("转入资金成功");
System.out.println("转账成功");
}
}
版权属于:乐心湖's Blog
本文链接:https://cloud.tencent.com/developer/article/1774948
声明:博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!