1、开篇陈述mysql
1.1 写文原因 spring
最近在系统学习spring框架IoC、AOP、Transaction相关的知识点,准备写三篇随笔记录学习过程当中的感悟。这是第一篇,记录spring Transaction的使用及部分原理。spring的学习过程应该是从IoC到AOP再到Transaction,这里写随笔的顺序没有按照学习路线。sql
1.2 预备技能数据库
学习spring事务使用最好具有基础的Java知识,掌握spring IoC和spring AOP,并对各种数据库事务的概念有所了解。固然,这些都不是必须的,若是你相信你的理解力。笔者在学习spring 事务以前有不少疑问:如为何数据库在被事务中的操做改变以后,事务还能够进行回滚,数据库能够像没有操做过同样?事务进行过程当中,其它读写数据库的操做看到怎样的结果,会不会看到事务中非完整的操做结果?带着这些问题,开始学习spring事务的使用吧。express
2、基本概念和主要接口编程
2.1 基本概念框架
为何须要事务:应用中须要保证用户的操做的可靠性和完整性,有些操做必须做为一组原子操做(如转帐、下单减库存等)提交到数据库,若是其中的一个操做失败,其它操做也不该该生效,这就是数据库事务的概念(下单过程当中减了库存,随后的下单记录添加失败,那么库存就不该该减,因此应该将这个步骤做为一个事务提交到数据库,以保证数据完整性)。ide
事务的一些属性:事务有一些属性来描述,其中最重要的有事务的隔离级别、事务的传播属性、事务的超时时间和事务的只读属性。学习
隔离级别:隔离级别是指多个事务同时执行时,各个事务之间的影响相互隔离的程度。主要有以下几个级别:测试
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(事务状态:表明一个新的或已经存在的事务,控制事务执行和查询事务状态)
3、编程式事务和声明式事务
所谓编程式事务就是在业务代码中显示编写事务逻辑,而声明式事务则是在配置文件中声明事务,基本不在代码中影响bean的工做方式。
3.1 编程式事务
1)基于底层API的编程式事务管理
这种方式直接使用PlatformTransactionManager、TransactionDefinition、TransactionStatus三个核心接口编程实现事务。示例代码如清单一、2所示:
清单1:业务逻辑
1 @Service("testDao") 2 3 public class TestDaoImpl implements TestDao { 4 5 6 7 @Resource(name="dataSource") 8 9 private DataSource dataSource; 10 11 12 13 @Resource(name="txDefinition") 14 15 private TransactionDefinition txDefinition; 16 17 18 19 @Resource(name="txManager") 20 21 private PlatformTransactionManager txManager; 22 23 24 25 public void insert(String key, Object value) { 26 27 TransactionStatus txStatus = txManager.getTransaction(txDefinition); 28 29 System.out.println("trans status: " + txStatus.isNewTransaction() + txStatus.isRollbackOnly() + txStatus.isCompleted()); 30 31 try { 32 33 JdbcTemplate jt = new JdbcTemplate(dataSource); 34 35 int ret1 = jt.update("insert into kv (k, v)" 36 37 + " values('" + key + "', '" + value + "')"); 38 39 System.out.println("insert first time. ret1 = " + ret1); 40 41 int ret2 = jt.update("insert into kv (k, v)" 42 43 + " values('" + key + "', '" + value + "')"); 44 45 System.out.println("insert second time.ret2 = " + ret2); 46 47 48 49 txManager.commit(txStatus); 50 51 System.out.println("is completed: " + txStatus.isCompleted()); 52 53 } catch (Exception e) { 54 55 txManager.rollback(txStatus); 56 57 System.out.println("is rollback: " + txStatus.isRollbackOnly()); 58 59 System.out.println("caught exception: --------"); 60 61 e.printStackTrace(); 62 63 64 65 } 66 67 } 68 69 }
清单2:主程序
1 public class App 2 3 { 4 5 private static ApplicationContext context = new ClassPathXmlApplicationContext("spring/spring.xml"); 6 7 8 9 public static void main( String[] args ) 10 11 { 12 13 TestDao testDao = (TestDao)context.getBean("testDao"); 14 15 try { 16 17 testDao.insert("i am JUNE", "June is excellent!"); 18 19 } catch (Exception e) { 20 21 System.out.println("out side caughter -----"); 22 23 e.printStackTrace(); 24 25 26 27 } 28 29 System.out.println( "Hello World!" ); 30 31 } 32 33 }
清单3:配置文件
1 <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 2 <property name="driverClassName" value="com.mysql.jdbc.Driver" /> 3 <property name="url" value="jdbc:mysql://10.13.49.201:3306/database" /> 4 <property name="username" value="dmp" /> 5 <property name="password" value="test" /> 6 </bean> 7 <bean id="txDefinition" class="org.springframework.transaction.support.DefaultTransactionDefinition"> 8 <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property> 9 <property name="timeout" value="10000"></property> 10 </bean> 11 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 12 <property name="dataSource" ref="dataSource" /> 13 </bean>
如上清单1所示,示例中配置了三个bean(数据源dataSource、事务定义txDefinition以及事务管理器txManager),编程式事务开始于txManager.getTransaction,终止于txManager.commit(事务过程当中没有异常,事务被提交)或txManager.rollback(事务过程当中抛出异常,事务被回滚)。示例中用于测试的事务向数据库kv表中插入两条相同的数据,因为kv表中对key字段作了惟一性约束,因此在插入第二条数据的时候会抛出异常,若是没有事务保证,数据库会被插入一条数据,而第二条数据不能插入,示例将两次插入放入事务中,当第二次插入时会抛出异常,事务被回滚,第一条数据也不会真正插入到数据库。编写这类事务须要注意在合适的地方进行事务提交或回滚。
2)基于TransactionTemplate的编程式事务管理
从上面的例子能够看出,那种方式的事务管理存在不少样板代码,如事务开始、捕获异常、事务提交和事务回滚,这些代码严重破坏了业务代码的结构,spring提供一个改进的方式编程实现事务。示例代码如清单4所示:
清单4:基于TransactionTemplate实现的事务逻辑
1 @Service("testDaoTemplate") 2 3 public class TestDaoTemplateImpl implements TestDao{ 4 5 6 7 @Resource 8 9 private DataSource dataSource; 10 11 12 13 @Resource 14 15 private TransactionTemplate txTemplate; 16 17 18 19 public void insert(final String key, final Object value) { 20 21 txTemplate.execute(new TransactionCallback() { 22 23 24 25 public Object doInTransaction(TransactionStatus status) { 26 27 System.out.println("trans status: " 28 29 + status.isNewTransaction() 30 31 + status.isRollbackOnly() 32 33 + status.isCompleted()); 34 35 try { 36 37 JdbcTemplate jt = new JdbcTemplate(dataSource); 38 39 int ret1 = jt.update("insert into kv (k, v)" 40 41 + " values('" + key + "', '" + value + "')"); 42 43 System.out.println("insert first time. ret1 = " + ret1); 44 45 int ret2 = jt.update("insert into kv (k, v)" 46 47 + " values('" + key + "', '" + value + "')"); 48 49 System.out.println("insert second time.ret2 = " + ret2); 50 51 52 53 System.out.println("is completed: " + status.isCompleted()); 54 55 } catch (Exception e) { 56 57 status.setRollbackOnly(); 58 59 System.out.println("is rollback: " + status.isRollbackOnly()); 60 61 62 63 System.out.println("caught exception: --------"); 64 65 e.printStackTrace(); 66 67 68 69 } 70 71 72 73 return null; 74 75 } 76 77 78 79 }); 80 81 } 82 83 }
清单5:配置文件
1 <bean id="txTemplate" class="org.springframework.transaction.support.TransactionTemplate"> 2 3 <property name="transactionManager" ref="txManager"></property> 4 5 </bean>
这种方式只是将这些模板代码封装到TransactionTemplate中,其业务逻辑写在一个TransactionCallback内部类中,做为txTemplate的参数传递进去。默认规则是执行回调方法的过程当中抛出unchecked异常或显示调用setRollbackOnly方法,事务将被回滚,不然(未抛异常或抛出异常被捕获而没有显示调用setRollbackOnly)提交事务。这类方式比底层API的方式稍微简便些,可是仍然破坏了业务代码的结构。
3.2 声明式事务
声明式事务创建在AOP(事务管理自己就是一个典型的横切逻辑)的基础之上,本质是对方法进行拦截,方法开始前加入事务,方法执行完后根据状况提交或回滚事务。声明式事务最大的优势就是不须要在业务逻辑中掺琐事务管理的代码,只在配置文件中作相关的事务规则声明(大型项目中严重建议使用声明式事务)。声明式事务的缺点就是事务的最细粒度只能做用到方法级别。
1) 基于TransactionInterceptor类实现的声明式事务
清单6: 基于TransactionInterceptor的事务业务逻辑
1 @Service("testDaoInterceptor") 2 3 public class TestDaoInterceptor implements TestDao{ 4 5 6 7 @Resource 8 9 private DataSource dataSource; 10 11 12 13 public void insert(String key, Object value) throws Exception { 14 15 try { 16 17 JdbcTemplate jt = new JdbcTemplate(dataSource); 18 19 int ret1 = jt.update("insert into kv (k, v)" 20 21 + " values('" + key + "', '" + value + "')"); 22 23 System.out.println("insert first time. ret1 = " + ret1); 24 25 int ret2 = jt.update("insert into kv (k, v)" 26 27 + " values('" + key + "', '" + value + "')"); 28 29 System.out.println("insert second time.ret2 = " + ret2); 30 31 32 33 } catch (Exception e) { 34 35 36 37 System.out.println("caught exception: --------"); 38 39 e.printStackTrace(); 40 41 throw e; 42 43 } 44 45 } 46 47 }
清单7:配置文件
1 <bean id="txInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> 2 3 <property name="transactionManager" ref="txManager"></property> 4 5 <property name="transactionAttributes"> 6 7 <props> 8 9 <prop key="insert">PROPAGATION_REQUIRED</prop> 10 11 </props> 12 13 </property> 14 15 </bean> 16 17 <bean id="testDaoInterceptorBean" class="org.springframework.aop.framework.ProxyFactoryBean"> 18 19 <property name="target" ref="testDaoInterceptor"/> 20 21 <property name="interceptorNames"> 22 23 <list> 24 25 <idref bean="txInterceptor"/> 26 27 </list> 28 29 </property> 30 31 </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的配置文件
1 <bean id="testDaoTxBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 2 3 <property name="target" ref="testDaoInterceptor"/> 4 5 <property name="transactionManager" ref="txManager"></property> 6 7 <property name="transactionAttributes"> 8 9 <props> 10 11 <prop key="*">PROPAGATION_REQUIRED</prop> 12 13 </props> 14 15 </property> 16 17 </bean>
这种配置被称为spring经典的声明式事务管理,虽然这种方式比起使用ProxyFactoryBean并无什么大的改进,其实这两种方式对于配置声明式事务已经足够简单。
3) 基于<tx>命名空间的声明式事务管理
前面的两种方式已经很好的使用了AOP来实现事务管理,此外,spring还提供了一种引入<tx>命名空间,结合使用<aop>命名空间,带给开发人员配置声明式事务的全新体验(这个太扯蛋了,没什么意思嘛)。
清单9:基于<tx>命名空间的事务管理配置文件
1 <tx:advice id="daoAdvice" transaction-manager="txManager"> 2 3 <tx:attributes> 4 5 <tx:method name="insert" propagation="REQUIRED"></tx:method> 6 7 </tx:attributes> 8 9 </tx:advice> 10 11 <aop:config> 12 13 <aop:pointcut expression="execution(* *.insert(..))" id="daoPointcut"/> 14 15 <aop:advisor advice-ref="daoAdvice" pointcut-ref="daoPointcut"/> 16 17 </aop:config>
这种方式有点好处,就是不须要指定具体的被代理的业务类,这样就只须要合理的配置切点的表达式,而后只要知足条件的业务类都将被代理。
4) 基于@Transactional注解的声明式事务管理
Spring中最简便的配置方式固然要属注解方式,声明式事务管理也不例外,spring使用@Transactional注解做用在接口、接口方法、类和类方法上,通常使用@Transactional在业务类public方法上,这种方式简单明了,没有学习成本。
总的来讲,这四种声明式事务管理只是使用形式不一样,其后台的实现方式是相同的。
4、结篇总结
4.1 遇到问题
使用ProxyFactoryBean和TransactionProxyFactoryBean实现声明式事务管理类时,发现事务不生效。屡次试验后发现示例中的业务逻辑代码把异常都捕获并处理,相对于事务来讲,业务没有抛出异常,因此事务会被提交而插入了一条数据。使用这种方式处理事务时切记要将异常捕获放开,让事务检测到异常并针对性的处理事务。
4.2 知识总结
1)编程式事务使用的三个接口。
2)两种编程式事务实现和四种声明式事务实现。