Loading... <div class="tip share">请注意,本文编写于 170 天前,最后修改于 84 天前,其中某些信息可能已经过时。</div> # 事务介绍(重要) > 事务是指一组数据库操作,要么操作都完成,要么操作都不完成(只要有一个未完成,其他操作即使完成也恢复到未完成的状态)。比如A账户向B账户转账,就包括了2个数据库操作: 1. A账户减少一定额度的资金 2. 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 测试等 ```xml <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,第三个参数表示转账金额。 ```java 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 配置文件如下: ```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: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%2B8"/> <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.写测试类 ```java 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异常。 ```java 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 <?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%2B8"/> <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 核心配置文件中注册事务注解驱动,其代码如下: ```xml <!-- 配置事务注解驱动 --> <tx:annotation-driven transaction-manager="transactionManager"/> ``` 全配置文件如下: ```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: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%2B8"/> <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` 如果将注解添加到类上,则表示事务的设置对整个类的所有方法都起作用;如果将注解添加在类的某个方法上,则表示事务的设置只对该方法有效。 ```java 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("转账成功"); } } ``` 关于 `@Transactional` 的事务提交类型,请查看下文 <div class="preview"> <div class="post-inser post box-shadow-wrap-normal"> <a href="https://www.xn2001.com/archives/609.html" target="_blank" class="post_inser_a no-external-link no-underline-link"> <div class="inner-image bg" style="background-image: url(https://imgcdn.xn2001.com/usr/themes/handsome/assets/img/sj/2.jpg);background-size: cover;"></div> <div class="inner-content" > <p class="inser-title">Spring事务提交类型 </p> <div class="inster-summary text-muted"> 关于事务的介绍REQUIRED:使用当前的事务,如果当前没有事务,则自己创建一个事务,子方法是必须运行一个事务中的... </div> </div> </a> <!-- .inner-content #####--> </div> <!-- .post-inser ####--> </div><hr class="content-copyright" style="margin-top:50px" /><blockquote class="content-copyright" style="font-style:normal"><p class="content-copyright">版权属于:乐心湖's Blog</p><p class="content-copyright">本文链接:<a class="content-copyright" href="https://www.xn2001.com/archives/603.html">https://www.xn2001.com/archives/603.html</a></p><p class="content-copyright">声明:博客所有文章除特别声明外,均采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" target="_blank" rel="nofollow noopener noopener" one-link-mark="yes">CC BY-SA 4.0 协议</a> ,转载请注明出处!</p></blockquote> 腾讯云社区邀请各位技术博主加入,福利多多噢! Last modification:January 24th, 2021 at 08:26 pm © 允许规范转载 Support 如果觉得我的文章对你有用,请随意赞赏 ×Close Appreciate the author Sweeping payments Pay by AliPay Pay by WeChat
One comment
又发现一个好站,收藏了~以后会经常光顾的 (。•ˇ‸ˇ•。)