【Spring】Spring的事务管理 - 2、声明式事务管理(实现基于XML、Annotation的方式。)
声明式事务管理
简单记录 - 简单记录-Java EE企业级应用开发教程(Spring+Spring MVC+MyBatis) 和 Spring 3.0就这么简单 -Spring的事务管理
声明式事务管理
数据访问的技术很多,如JDBC、JPA、Hibernate、分布式事务等。面对众多的数据访问技术,Spring在不同的事务管理API上定义了一个抽象层PlatformTransaction-Manager,应用程序开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring并不直接管理事务,而是提供了许多内置事务管理器实现,常用的有DataSourceTransactionManager、JdoTransactionManager、JpaTransactionManager以及HibernateTransactionManager等。(Spring作为企业级应用程序框架,在不同的事务管理API上定义了一个抽象层,使应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制。)
Spring事务管理分两种方式,一种是传统的编程式事务管理(也称编码式事务),另一种是声明式事务管理。
编程式事务管理
通过编写代码实现的事务管理,包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚。编程式事务管理是将事务管理代码嵌入业务方法中来控制事务的提交和回滚。在编程式事务中,必须在每个业务操作中包含额外的事务管理代码。
声明式事务管理
声明式事务管理是指将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。在大多数情况下,声明式事务管理比编程式事务管理更好用。Spring是通过AOP框架技术支持实现的事务管理,主要思想是将事务作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”植入到业务目标类中。
Spring的声明式事务管理底层是建立在AOP基础上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完毕之后根据执行情况提交或回滚事务。
Spring事务管理的亮点在于声明式事务管理。Spring允许通过声明方式,在IoC配置中指定事务的边界和事务属性,Spring自动在指定的事务边界上应用事务属性。
声明式事务管理最大的优点在于开发者无需通过编程的方式来管理事务,**只需在配置文件中进行相关的事务规则声明,就可以将事务应用到业务逻辑中**。这使得开发人员可以更加专注于核心业务逻辑代码的编写,在一定程度上减少了工作量,提高了开发效率,所以在实际开发中,通常都推荐使用声明式事务管理。
如何实现Spring的声明式事务管理?
Spring的声明式事务管理可以通过两种方式来实现,一种是基于XML的方式,另一种是基于Annotation的方式。
基于XML方式的声明式事务
基于XML方式的声明式事务是在配置文件中通过<tx:advice>
元素配置事务规则来实现的。当配置了事务的增强处理后,就可以通过编写的AOP配置,让Spring自动对目标生成代理。
配置<tx:advice>
元素时,通常需要指定id和transaction-manager属性,其中id属性是配置文件中的唯一标识,transaction-manager属性用于指定事务管理器。除此之外,还需要配置一个tx:attributes子元素,该子元素可通过配置多个tx:method子元素来配置执行事务的细节
tx:advice元素及其子元素如下图所示:
配置tx:advice元素的重点是配置tx:method子元素,上图中使用灰色标注的几个属性是tx:method元素中的常用属性。其属性描述具体如下:
name 指定对哪些方法起作用
propagation指定事务的传播行为
isolation指定事务的隔离级别
read-only指定事务是否只读
了解了如何在XML文件中配置事务后,接下来通过一个案例来演示如何通过XML方式来实现Spring的声明式事务管理。这里是按照Spring的数据库开发,chapter04的项目代码和数据表为基础(代码在Spring的数据库开发的博客里),编写的一个模拟银行转账的程序,要求在转账时通过Spring对事务进行控制,其具体实现步骤如下。案例代码
(1)在Idea中,在JavaEE-enterprise-application-development-tutorial项目里,创建一个名为chapter05的Maven项目module,导入AOP的依赖
(2)将Spring的数据库开发chapter04项目中的代码和配置文件复制到chapter05项目的java和resources目录下,并在AccountDao接口中,创建一个转账方法transfer(),其代码如下所示。
// 转账
public void transfer(String outUser, String inUser, Double money);
(3)在其实现类AccountDaoImpl中实现transfer()方法,编辑后的代码如下所示。
/**
* 转账
* inUser:收款人
* outUser:汇款人
* money:收款金额
*/
public void transfer(String outUser, String inUser, Double money) {
// 收款时,收款用户的余额=现有余额+所汇金额
this.jdbcTemplate.update("update account set balance = balance +? "
+ "where username = ?",money, inUser);
// 模拟系统运行时的突发性问题
int i = 1/0;
// 汇款时,汇款用户的余额=现有余额-所汇金额
this.jdbcTemplate.update("update account set balance = balance-? "
+ "where username = ?",money, outUser);
}
在上述代码中,使用了两个update()方法对account表中的数据执行收款和汇款的更新操作。在两个操作之间,添加了一行代码“int i = 1/0; ”来模拟系统运行时的突发性问题。如果没有事务控制,那么在转账操作执行后,收款用户的余额会增加,而汇款用户的余额会因为系统出现问题而不变,这显然是有问题的;如果增加了事务控制,那么在转账操作执行后,收款用户的余额和汇款用户的余额在问题出现前后都应该保持不变。
(4)修改配置文件applicationContext.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"
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/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1.配置数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!--连接数据库的url -->
<property name="url" value="jdbc:mysql://localhost/spring?useSSL=false" />
<!--连接数据库的用户名 -->
<property name="username" value="root" />
<!--连接数据库的密码 -->
<property name="password" value="123456" />
</bean>
<!-- 2.配置JDBC模板 -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 默认必须使用数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!--3.定义id为accountDao的Bean -->
<bean id="accountDao" class="com.awen.jdbc.AccountDaoImpl">
<!-- 将jdbcTemplate注入到AccountDao实例中 -->
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!-- 4.事务管理器,依赖于数据源 -->
<bean id="transactionManager" class=
"org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 5.编写通知:对事务进行增强(通知),需要编写对切入点和具体执行事务细节 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- name:*表示任意方法名称 -->
<tx:method name="*" propagation="REQUIRED"
isolation="DEFAULT" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 6.编写aop,让spring自动对目标生成代理,需要使用AspectJ的表达式 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* com.itheima.jdbc.*.*(..))"
id="txPointCut" />
<!-- 切面:将切入点与通知整合 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>
</beans>
在文件applicationContext.xml中,首先启用了Spring配置文件的aop、tx和context 3个命名空间(从配置数据源到声明事务管理器的部分都没有变化),然后定义了id为transactionManager的事务管理器,接下来通过编写的通知来声明事务,最后通过声明AOP的方式让Spring自动生成代理。
(5)在com.awen.jdbc包中,创建测试类TransactionTest,并在类中编写测试方法xmlTest(),如文件所示。文件TransactionTest.java
package com.awen.jdbc;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
//测试类
public class TransactionTest {
@Test
public void xmlTest(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取AccountDao实例
AccountDao accountDao =
(AccountDao)applicationContext.getBean("accountDao");
// 调用实例中的转账方法
accountDao.transfer("柳小子", "柳狗柱", 5000.0);
// 输出提示信息
System.out.println("转账成功!");
}
}
在文件TransactionTest.java中,获取了AccountDao实例后,调用了实例中的转账方法,由柳小子向柳狗柱的账户中转入100元。如果在配置文件中所声明的事务代码能够起作用,那么在整个转账方法执行完毕后,柳小子和柳狗柱的账户余额应该都是原来的数值。在执行转账操作前,先查看account表中的数据,如图所示。
account表从图可以看出,此时柳小子的账户余额是10000,而柳狗柱的账户余额是500。执行完文件TransactionTest.java中的测试方法后,Junit的控制台的显示结果如图所示
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [applicationContext.xml]: BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0': Cannot resolve reference to bean 'txPointCut' while setting bean property 'pointcut'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'txPointCut': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate
java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 60 more
Process finished with exit code -1
应该是没导入AOP的依赖包
导入
<!-- AOP begin -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<!-- AOP end -->
运行结果
java.lang.ArithmeticException: / by zero
Process finished with exit code -1
从所得结果可以看到,java.lang.ArithmeticException: / by zero
Junit控制台中报出了“/by zero”的算术异常信息。此时如果再次查询数据表account,
会发现表中柳小子和柳狗柱的账户余额并没有发生任何变化(与刚开始的中的显示结果一样),这说明Spring中的事务管理配置已经生效。
我这变化了 不是汉字的问题 我实现注解的方法输入了 成功了
钱没有变化
柳狗柱一直增加
柳小子不变的
基于Annotation方式的声明式事务
1、在Spring容器中注册事务注解驱动;
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 5.注册事务管理器驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
2、在需要事务管理的类或方法上使用@Transactional注解。
如果将注解添加在Bean类上,则表示事务的设置对整个Bean类的所有方法都起作用;如果将注解添加在Bean类中的某个方法上,则表示事务的设置只对该方法有效。
使用@Transactional注解时,可以通过参数配置事务详情:
从表可以看出,@Transactional注解与tx:method元素中的事务属性基本是对应的,并且其含义也基本相似。
学习案例
接下来,就对前面模拟银行转账的案例进行改进,来演示基于Annotation方式的声明式事务管理的使用。
案例代码
(1)在在src/main/resources目录下,创建Spring的配置文件applicationContext-annotation.xml,在该文件中声明事务管理器等配置信息,如文件所示。文件 applicationContext-annotation.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1.配置数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!--连接数据库的url -->
<property name="url" value="jdbc:mysql://localhost/spring?useSSL=false" />
<!--连接数据库的用户名 -->
<property name="username" value="root" />
<!--连接数据库的密码 -->
<property name="password" value="123456" />
</bean>
<!-- 2.配置JDBC模板 -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 默认必须使用数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!--3.定义id为accountDao的Bean -->
<bean id="accountDao" class="com.awen.jdbc.AccountDaoImpl">
<!-- 将jdbcTemplate注入到AccountDao实例中 -->
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!-- 4.事务管理器,依赖于数据源 -->
<bean id="transactionManager" class=
"org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 5.注册事务管理器驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
与基于XML方式的配置文件相比,文件applicationContext-annotation.xml通过注册事务管理器驱动,替换了文件applicationContext.xml中的第5步编写通知和第6步编写aop,这样大大减少了配置文件中的代码量。需要注意的是,如果案例中使用了注解式开发,则需要在配置文件中开启注解处理器,指定扫描哪些包下的注解。这里没有开启注解处理器是因为在配置文件中已经配置了AccountDaoImpl类的Bean,而@Transactional注解就配置在该Bean类中,所以可以直接生效。
(2)在AccountDaoImpl类的transfer()方法上添加事务注解,添加后的代码如下所示。
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT, readOnly = false)
public void transfer(String outUser, String inUser, Double money) {
// 收款时,收款用户的余额=现有余额+所汇金额
this.jdbcTemplate.update("update account set balance = balance +? "
+ "where username = ?",money, inUser);
// 模拟系统运行时的突发性问题
int i = 1/0;
// 汇款时,汇款用户的余额=现有余额-所汇金额
this.jdbcTemplate.update("update account set balance = balance-? "
+ "where username = ?",money, outUser);
}
上述方法已经添加了@Transactional注解,并且使用注解的参数配置了事务详情,各个参数之间要用英文逗号“, ”进行分隔。
提示在实际开发中,事务的配置信息通常是在Spring的配置文件中完成的,而在业务层类上只需使用@Transactional注解即可,不需要配置@Transactional注解的属性。
(3)在TransactionTest类中,创建测试方法annotationTest(),编辑后的代码如下所示。
@Test
public void annotationTest(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext-annotation.xml");
// 获取AccountDao实例
AccountDao accountDao =
(AccountDao)applicationContext.getBean("accountDao");
// 调用实例中的转账方法
accountDao.transfer("Jack", "Rose", 100.0);
// 输出提示信息
System.out.println("转账成功!");
}
从上述代码可以看出,与XML方式的测试方法相比,该方法只是对配置文件的名称进行了修改。
java.lang.ArithmeticException: / by zero
注解的是有用的
满足了事务的一致性
【Spring】Spring的事务管理 - 2、声明式事务管理(实现基于XML、Annotation的方式。)的更多相关文章
- 全面分析 Spring 的编程式事务管理及声明式事务管理
开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...
- 全面分析 Spring 的编程式事务管理及声明式事务管理--转
开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...
- Spring编程式事务管理及声明式事务管理
本文将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. Spring 事务属性分析 事务管理 ...
- spring事务配置,声明式事务管理和基于@Transactional注解的使用(转载)
原文地址:http://blog.csdn.net/bao19901210/article/details/41724355 事务管理对于企业应用来说是至关重要的,好使出现异常情况,它也可以保证数据的 ...
- Spring boot 入门五:springboot 开启声明式事务
springboot开启事务很简单,只需要一个注解@Transactional 就可以了.因为在springboot中已经默认对jpa.jdbc.mybatis开启了事务.这里以spring整合myb ...
- spring笔记--事务管理之声明式事务
事务简介: 事务管理是企业级应用开发中必不可少的技术,主要用来确保数据的完整性和一致性, 事务:就是一系列动作,它们被当作一个独立的工作单元,这些动作要么全部完成,要么全部不起作用. Spring中使 ...
- Spring事务管理之声明式事务管理-基于注解的方式
© 版权声明:本文为博主原创文章,转载请注明出处 案例 - 利用Spring的声明式事务(TransactionProxyFactoryBean)管理模拟转账过程 数据库准备 -- 创建表 CREAT ...
- Spring事务管理之声明式事务管理-基于AspectJ的XML方式
© 版权声明:本文为博主原创文章,转载请注明出处 案例 - 利用Spring的声明式事务(AspectJ)管理模拟转账过程 数据库准备 -- 创建表 CREATE TABLE `account`( ` ...
- Spring事务管理之声明式事务管理:基于TransactionProxyFactoryBean的方式
© 版权声明:本文为博主原创文章,转载请注明出处 案例 - 利用Spring的声明式事务(TransactionProxyFactoryBean)管理模拟转账过程 数据库准备 -- 创建表 CREAT ...
- Spring框架的事务管理之声明式事务管理的类型
1. 声明式事务管理又分成两种方式 * 基于AspectJ的XML方式(重点掌握)(具体内容见“https://www.cnblogs.com/wyhluckdog/p/10137712.html”) ...
随机推荐
- [JDK8]Map接口与Dictionary抽象类
package java.util; 一.Map接口 接口定义 public interface Map<K,V> Map是存放键值对的数据结构.map中没有重复的key,每个key最多只 ...
- echarts饼图默认状态高亮显示
需求:饼状图默认状态下高亮显示指定内容. 最常见的两种: 一.饼图中间始终默认展示数据总数和统计事项的名字(如下图),这种实现方式比较简单,就不多介绍 二.饼图中间默认展示某一图例的具体数据,鼠标放在 ...
- idea 中使用Mybatis Generator逆向工程生成代码
通过MAVEN完成 Mybatis 逆向工程 1. POM文件中添加插件 在 pom 文件的build 标签中 添加 plugin 插件和 数据库连接 jdbc 的依赖. <build> ...
- Python命令行模块(sys.argv,argparse,click)
Python作为一门脚本语言,经常作为脚本接受命令行传入参数,Python接受命令行参数大概有三种方式.因为在日常工作场景会经常使用到,这里对这几种方式进行总结. 命令行参数模块 这里命令行参数模块平 ...
- ES6中的Promise和Generator详解
目录 简介 Promise 什么是Promise Promise的特点 Promise的优点 Promise的缺点 Promise的用法 Promise的执行顺序 Promise.prototype. ...
- 配置OSPF与BFD联动
组网图形 OSPF与BFD联动简介 双向转发检测BFD(Bidirectional Forwarding Detection)是一种用于检测转发引擎之间通信故障的检测机制.BFD对两个系统间的.同一路 ...
- zabbix学习(一)——LNMP环境搭建及zabbix安装
第一部分:LNMP环境搭建 一.环境说明: OS: centos7.6_x64nginx:nginx-1.16.0php: php-7.1.11mysql:mysql-5.6.44 zabbi ...
- JDK、JRE、JVM,是什么关系?
作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...
- Centos7路由设置
再添加路由时,很多时候都是采用命令行用route添加的.但是在机器重启后.就失效了.这里也是参考了几位博主的经验 作出以下记载 一:路由表常用设置 1.route命令路由表常用设置: //添加到主机的 ...
- HCIP --- BGP综合实验
实验要求: 实验拓扑: 一.配置IP地址 L:代表环回地址(loop back 0) Y:代表业务网段的地址(loop back 1) 二.因为BGP基于IGP之上,给AS 2内配置OSPF 在R2上 ...