Spring Transaction 使用入门
一、开篇陈述
1.1 写文缘由
最近在系统学习spring框架IoC、AOP、Transaction相关的知识点,准备写三篇随笔记录学习过程中的感悟。这是第一篇,记录spring Transaction的使用及部分原理。spring的学习过程应该是从IoC到AOP再到Transaction,这里写随笔的顺序没有按照学习路线。
1.2 预备技能
学习spring事务使用最好具备基础的Java知识,掌握spring IoC和spring AOP,并对各类数据库事务的概念有所了解。当然,这些都不是必须的,如果你相信你的理解力。笔者在学习spring 事务之前有很多疑问:如为什么数据库在被事务中的操作改变之后,事务还可以进行回滚,数据库可以像没有操作过一样?事务进行过程中,其它读写数据库的操作看到怎样的结果,会不会看到事务中非完整的操作结果?带着这些问题,开始学习spring事务的使用吧。
二、基本概念和主要接口
2.1 基本概念
为什么需要事务:应用中需要保证用户的操作的可靠性和完整性,有些操作必须作为一组原子操作(如转账、下单减库存等)提交到数据库,如果其中的一个操作失败,其它操作也不应该生效,这就是数据库事务的概念(下单过程中减了库存,随后的下单记录添加失败,那么库存就不应该减,所以应该将这个步骤作为一个事务提交到数据库,以保证数据完整性)。
事务的一些属性:事务有一些属性来描述,其中最重要的有事务的隔离级别、事务的传播属性、事务的超时时间和事务的只读属性。
隔离级别:隔离级别是指多个事务同时执行时,各个事务之间的影响相互隔离的程度。主要有如下几个级别:
ISOLATION_DEFAULT(底层数据库默认隔离级别,通常为ISOLATION_READ_COMMITTED,这个级别的事务只能看到其它事务已经提交的修改,没有提交的修改都不能被看到,如减库存操作操作完成,下单还没完成,整个事务没有提交,那么这种隔离级别的其它事务是没法看到减库存成功的操作结果的,只有整个事务提交之后才能看到,这也是我们常用的默认隔离级别)
ISOLATION_READ_UNCOMMITTED(可以读取另一个事务修改但还没有提交的数据,会导致脏读和不可重复读,很少使用;比如减库存操作完成,其它事务就能看到库存被减了,如果这时候库存正好被减为0,其它用户可能就下单失败,但是如果这个事务最后失败了,库存被回滚,又有可能被其它用户购买)
ISOLATION_READ_COMMITTED(只能读取已提交的数据,可以防止脏读,还是存在不可重复读)
ISOLATION_REPEATABLE_READ(可以多次重复执行某个查询,并且每次返回的记录都相同。有新增数据满足查询也会被忽略,防止脏读和不可重复读。当库存减少时,已经在执行的其它事务看不到这个减少吗?这个太奇怪了。暂时还没搞清楚实现原理)
ISOLATION_SERIALIZABLE(事务依次逐个执行)
读一致性:上面的隔离级别中我们关心的都是读数据的返回,因为写肯定是要互斥且顺序执行的,写不存在并行。下面研究一下读数据的一致性。
脏读(一个事务访问并修改了数据,修改还没提交,另一个事务也访问并使用这个数据;库存被减了,但是还没查下单记录,事务未提交,另一个事务读库存,发现库存为0,于是下单失败,这个时候如果事务回滚,其实库存还是有的,读到了脏数据)
不可重复读(一个事务内多次读同一数据,在这之间,另一个事务修改了数据,导致一个事务内两次读到的数据是不一样的;两次读库存,中间库存被修改)
幻读(事务不是独立执行,在第一个事务对表数据进行修改后,第二个事务也修改了表数据,然后第一个事务发现表中的数据跟预想的不一致;如第一个事务减库存失败,第二个事务增加了库存,第一个事务发现莫名其妙的库存变化,要防止幻读只能用串行执行的隔离级别)
传播属性:当事务开始时,一个事务上下文已经存在,此时可以指定一个事务性方法的执行行为。
PROPAGATION_REQUIRED(有则加入,无则新建)
PROPAGATION_REQUIRES_NEW(新建事务,挂起之前的事务)
PROPAGATION_SUPPORTS(有则加入,没有则以非事务方式运行)
PROPAGATION_NOT_SUPPORTED(有则挂起当前事务)
PROPAGATION_NEVER(有则抛异常)
PROPAGATION_MANDATORY(有则加入,没有则抛异常)
PROPAGATION_NESTED(有则以嵌套事务的方式执行,外部事务提交才会触发内部事务提交,外部事务回滚会触发内部事务回滚)
2.2 主要接口
事务最主要的API:
TransactionDefinition(事务规则:设置事务的一些属性,上文提到的,使用DefaultTransactionDefinition默认实现一般可以满足要求,或者可以扩展接口,实现自己的定义)
PlatformTransactionManager(事务管理:spring没有直接管理事务,而是将事务管理的责任委托给JTA或持久化机制的某个特定平台的事务实现。spring的事务管理器充当了特定平台事务的代理,如下图所示)
TransactionStatus(事务状态:代表一个新的或已经存在的事务,控制事务执行和查询事务状态)
三、编程式事务和声明式事务
所谓编程式事务就是在业务代码中显示编写事务逻辑,而声明式事务则是在配置文件中声明事务,基本不在代码中影响bean的工作方式。
3.1 编程式事务
1)基于底层API的编程式事务管理
这种方式直接使用PlatformTransactionManager、TransactionDefinition、TransactionStatus三个核心接口编程实现事务。示例代码如清单1、2所示:
清单1:业务逻辑
@Service("testDao") public class TestDaoImpl implements TestDao { @Resource(name="dataSource") private DataSource dataSource; @Resource(name="txDefinition") private TransactionDefinition txDefinition; @Resource(name="txManager") private PlatformTransactionManager txManager; public void insert(String key, Object value) { TransactionStatus txStatus = txManager.getTransaction(txDefinition); System.out.println("trans status: " + txStatus.isNewTransaction() + txStatus.isRollbackOnly() + txStatus.isCompleted()); try { JdbcTemplate jt = new JdbcTemplate(dataSource); int ret1 = jt.update("insert into kv (k, v)" + " values('" + key + "', '" + value + "')"); System.out.println("insert first time. ret1 = " + ret1); int ret2 = jt.update("insert into kv (k, v)" + " values('" + key + "', '" + value + "')"); System.out.println("insert second time.ret2 = " + ret2); txManager.commit(txStatus); System.out.println("is completed: " + txStatus.isCompleted()); } catch (Exception e) { txManager.rollback(txStatus); System.out.println("is rollback: " + txStatus.isRollbackOnly()); System.out.println("caught exception: --------"); e.printStackTrace(); } } }
清单2:主程序
public class App { private static ApplicationContext context = new ClassPathXmlApplicationContext("spring/spring.xml"); public static void main( String[] args ) { TestDao testDao = (TestDao)context.getBean("testDao"); try { testDao.insert("i am JUNE", "June is excellent!"); } catch (Exception e) { System.out.println("out side caughter -----"); e.printStackTrace(); } System.out.println( "Hello World!" ); } }
清单3:配置文件
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://10.13.49.201:3306/database" />
<property name="username" value="dmp" />
<property name="password" value="test" />
</bean>
<bean id="txDefinition" class="org.springframework.transaction.support.DefaultTransactionDefinition">
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property>
<property name="timeout" value="10000"></property>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
如上清单1所示,示例中配置了三个bean(数据源dataSource、事务定义txDefinition以及事务管理器txManager),编程式事务开始于txManager.getTransaction,终止于txManager.commit(事务过程中没有异常,事务被提交)或txManager.rollback(事务过程中抛出异常,事务被回滚)。示例中用于测试的事务向数据库kv表中插入两条相同的数据,由于kv表中对key字段做了唯一性约束,所以在插入第二条数据的时候会抛出异常,如果没有事务保证,数据库会被插入一条数据,而第二条数据不能插入,示例将两次插入放入事务中,当第二次插入时会抛出异常,事务被回滚,第一条数据也不会真正插入到数据库。编写这类事务需要注意在合适的地方进行事务提交或回滚。
2)基于TransactionTemplate的编程式事务管理
从上面的例子可以看出,那种方式的事务管理存在很多样板代码,如事务开始、捕获异常、事务提交和事务回滚,这些代码严重破坏了业务代码的结构,spring提供一个改进的方式编程实现事务。示例代码如清单4所示:
清单4:基于TransactionTemplate实现的事务逻辑
@Service("testDaoTemplate") public class TestDaoTemplateImpl implements TestDao{ @Resource private DataSource dataSource; @Resource private TransactionTemplate txTemplate; public void insert(final String key, final Object value) { txTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { System.out.println("trans status: " + status.isNewTransaction() + status.isRollbackOnly() + status.isCompleted()); try { JdbcTemplate jt = new JdbcTemplate(dataSource); int ret1 = jt.update("insert into kv (k, v)" + " values('" + key + "', '" + value + "')"); System.out.println("insert first time. ret1 = " + ret1); int ret2 = jt.update("insert into kv (k, v)" + " values('" + key + "', '" + value + "')"); System.out.println("insert second time.ret2 = " + ret2); System.out.println("is completed: " + status.isCompleted()); } catch (Exception e) { status.setRollbackOnly(); System.out.println("is rollback: " + status.isRollbackOnly()); System.out.println("caught exception: --------"); e.printStackTrace(); } return null; } }); } }
清单5:配置文件
<bean id="txTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="txManager"></property> </bean>
这种方式只是将这些模板代码封装到TransactionTemplate中,其业务逻辑写在一个TransactionCallback内部类中,作为txTemplate的参数传递进去。默认规则是执行回调方法的过程中抛出unchecked异常或显示调用setRollbackOnly方法,事务将被回滚,否则(未抛异常或抛出异常被捕获而没有显示调用setRollbackOnly)提交事务。这类方式比底层API的方式稍微简便些,但是仍然破坏了业务代码的结构。
3.2 声明式事务
声明式事务建立在AOP(事务管理本身就是一个典型的横切逻辑)的基础之上,本质是对方法进行拦截,方法开始前加入事务,方法执行完后根据情况提交或回滚事务。声明式事务最大的优点就是不需要在业务逻辑中掺杂事务管理的代码,只在配置文件中做相关的事务规则声明(大型项目中严重建议使用声明式事务)。声明式事务的缺点就是事务的最细粒度只能作用到方法级别。
1) 基于TransactionInterceptor类实现的声明式事务
清单6: 基于TransactionInterceptor的事务业务逻辑
@Service("testDaoInterceptor") public class TestDaoInterceptor implements TestDao{ @Resource private DataSource dataSource; public void insert(String key, Object value) throws Exception { try { JdbcTemplate jt = new JdbcTemplate(dataSource); int ret1 = jt.update("insert into kv (k, v)" + " values('" + key + "', '" + value + "')"); System.out.println("insert first time. ret1 = " + ret1); int ret2 = jt.update("insert into kv (k, v)" + " values('" + key + "', '" + value + "')"); System.out.println("insert second time.ret2 = " + ret2); } catch (Exception e) { System.out.println("caught exception: --------"); e.printStackTrace(); throw e; } } }
清单7:配置文件
<bean id="txInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="txManager"></property> <property name="transactionAttributes"> <props> <prop key="insert">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="testDaoInterceptorBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="testDaoInterceptor"/> <property name="interceptorNames"> <list> <idref bean="txInterceptor"/> </list> </property> </bean>
使用这种方式配置声明式事务,需要先配置一个TransactionInterceptor来定义相关的事务规则。它主要包括了两个属性,一个是transactionManager,指定一个事务管理器,transactionInterceptor将拦截到的事务相关操作委托给它。另一个是Properties类型的transactionAttributes属性,主要用来定义事务规则, 这个属性的具体配置不在这里详述。前面已经说过这种配置事务的方式是基于spring AOP的,从TransactionInterceptor的类继承关系中可以看到,这个类是继承自Advice接口的,熟悉spring AOP的同学应该知道AOP除了需要配置Advice之外,还需要一个ProxyFactoryBean来组装target 和 advice,通过spring工厂获取proxyFactoryBean实例时,其实返回的是proxyFactoryBean实例getObject返回的对象,也就是织入了事务管理逻辑后的目标类的代理类实例。这种方式的事务实现没有对业务代码进行任何操作,所有设置均在配置文件中完成。但是这种方式也存在一个烦人的问题:配置文件太长。需要为每个目标对象配置一个proxyFactoryBean和一个transactionInterceptor,相当于每个业务类需要配置3个bean,随着业务类增多,配置文件会越来越庞大,管理变得复杂。
2) 基于TransactionProxyFactoryBean的声明式事务管理
为了缓解ProxyFactoryBean实现声明式事务配置繁杂的问题,spring提供了TransactionProxyFactoryBean,将ProxyFactoryBean和TransactionInterceptor的配置打包成一个,其它的东西根本没有改变。示例配置如下:
清单8:基于TransactionProxyFactoryBean的配置文件
<bean id="testDaoTxBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="target" ref="testDaoInterceptor"/> <property name="transactionManager" ref="txManager"></property> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>
这种配置被称为spring经典的声明式事务管理,虽然这种方式比起使用ProxyFactoryBean并没有什么大的改进,其实这两种方式对于配置声明式事务已经足够简单。
3) 基于<tx>命名空间的声明式事务管理
前面的两种方式已经很好的使用了AOP来实现事务管理,此外,spring还提供了一种引入<tx>命名空间,结合使用<aop>命名空间,带给开发人员配置声明式事务的全新体验(这个太扯蛋了,没什么意思嘛)。
清单9:基于<tx>命名空间的事务管理配置文件
<tx:advice id="daoAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="insert" propagation="REQUIRED"></tx:method> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut expression="execution(* *.insert(..))" id="daoPointcut"/> <aop:advisor advice-ref="daoAdvice" pointcut-ref="daoPointcut"/> </aop:config>
这种方式有点好处,就是不需要指定具体的被代理的业务类,这样就只需要合理的配置切点的表达式,然后只要满足条件的业务类都将被代理。
4) 基于@Transactional注解的声明式事务管理
Spring中最简便的配置方式当然要属注解方式,声明式事务管理也不例外,spring使用@Transactional注解作用在接口、接口方法、类和类方法上,一般使用@Transactional在业务类public方法上,这种方式简单明了,没有学习成本。
总的来说,这四种声明式事务管理只是使用形式不同,其后台的实现方式是相同的。
四、结篇总结
4.1 遇到问题
使用ProxyFactoryBean和TransactionProxyFactoryBean实现声明式事务管理类时,发现事务不生效。多次试验后发现示例中的业务逻辑代码把异常都捕获并处理,相对于事务来说,业务没有抛出异常,所以事务会被提交而插入了一条数据。使用这种方式处理事务时切记要将异常捕获放开,让事务检测到异常并针对性的处理事务。
4.2 知识总结
1)编程式事务使用的三个接口。
2)两种编程式事务实现和四种声明式事务实现。
Spring Transaction 使用入门的更多相关文章
- Spring Transaction 使用入门 (转)
Spring Transaction 使用入门 一.开篇陈述 1.1 写文缘由 最近在系统学习spring框架IoC.AOP.Transaction相关的知识点,准备写三篇随笔记录学习过程中的感悟.这 ...
- Spring Boot从入门到精通(八)日志管理实现和配置信息分析
Spring Boot对日志的处理,与平时我们处理日志的方式完全一致,它为Java Util Logging.Log4J2和Logback提供了默认配置.对于每种日志都预先配置使用控制台输出和可选的文 ...
- Spring Boot从入门到精通(九)整合Spring Data JPA应用框架
JPA是什么? JPA全称Java Persistence API,是Sun官方提出的Java持久化规范.是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中. ...
- Spring Mvc的入门
SpringMVC也叫Spring Web mvc,属于表现层的框架.Spring MVC是Spring框架的一部分,是在Spring3.0后发布的. Spring Web MVC是什么: Sprin ...
- 疑惑的 java.lang.AbstractMethodError: org.mybatis.spring.transaction.SpringManagedTransaction.getTimeout()L
在MAVEN项目里面,在整合spring和mybatis在执行数据库操作的时候报出了: java.lang.AbstractMethodError: org.mybatis.spring.transa ...
- [Spring框架]Spring AOP基础入门总结二:Spring基于AspectJ的AOP的开发.
前言: 在上一篇中: [Spring框架]Spring AOP基础入门总结一. 中 我们已经知道了一个Spring AOP程序是如何开发的, 在这里呢我们将基于AspectJ来进行AOP 的总结和学习 ...
- [Spring框架]Spring AOP基础入门总结一.
前言:前面已经有两篇文章讲了Spring IOC/DI 以及 使用xml和注解两种方法开发的案例, 下面就来梳理一下Spring的另一核心AOP. 一, 什么是AOP 在软件业,AOP为Aspect ...
- spring Transaction Propagation 事务传播
spring Transaction中有一个很重要的属性:Propagation.主要用来配置当前需要执行的方法,与当前是否有transaction之间的关系. 我晓得有点儿抽象,这也是为什么我想要写 ...
- Spring Transaction属性之Propagation
spring Transaction中有一个很重要的属性:Propagation.主要用来配置当前需要执行的方法,与当前是否有transaction之间的关系. 我晓得有点儿抽象,这也是为什么我想要写 ...
随机推荐
- Spring Aware获取Bean和其他对象
Spring的容器托管了所有bean,实际项目中我们经常会用到容器中的功能资源,这时候就用到了 Spring Aware.简单来说,就是Spring Aware可以帮助你获取到Spring容器中的Be ...
- word,excel,ppt转pdf
第一步 需要下载jar包和jacob-1.14.3-x64.dll * <dependency> * <groupId>net.sf.jacob-project</gro ...
- GitHub Vue项目推荐|Vue+Element实现的电商后台管理系统功能丰富
GitHub Vue项目推荐|mall-admin-web是一个电商后台管理系统的前端项目基于Vue+Element实现 主要包括商品管理.订单管理.会员管理.促销管理.运营管理.内容管理.统计报表. ...
- linux 提升NFS性能
如何修改同时发起的NFS请求数量 阿里云ecs服务器请查看官方文档 linux上NFS性能只有几MB速度.NFS客户端对于同时发起的NFS请求数量进行了控制,默认编译的内核中此参数值为2,严重影响性能 ...
- Hadoop-HA集群搭建-rehl7.4
Hadoop-HA集群搭建-rehl7.4 hadoop 无说明需要登录其它机器操作,都是在集群的HD-2-101上执行的命令. 所有安装包地址:百度网盘,提取码:24oy 1. 基础环境配置 1.1 ...
- 【使用DIV+CSS重写网站首页案例】CSS引入方式
CSS引入方式(3种) *就近原则:行内引入可以覆盖内部引入的效果 内部引入: * type="text/css" 为默认可以不写 例子: <!DOCTYPE h ...
- SQL注入中的WAF绕过
1.大小写绕过 这个大家都很熟悉,对于一些太垃圾的WAF效果显著,比如拦截了union,那就使用Union UnIoN等等绕过. 2.简单编码绕过 比如WAF检测关键字,那么我们让他检测不到就可以了. ...
- MySQL利用IF查询不同条件并分别统计记录数
数据库记录如下: 现在要查询统计出每个'name'的'result'分别为'success'和'fail'的次数: 利用IF条件判断满足条件为1,不满足为0,再用SUM函数求和,最后通过'name'分 ...
- nginx 负载均衡,如何判断某台服务器宕机?
答:使用参数:proxy_connect_timeout: 这个参数是连接的超时时间.设置成1,表示是1秒后超时会连接到另外一台服务器. 配置在 server 部分里.
- POJ1475 Pushing Boxes(BFS套BFS)
描述 Imagine you are standing inside a two-dimensional maze composed of square cells which may or may ...