最近看了比较多Spring的东西,脑中概念知识比较杂乱,借助周六周天的闲暇时间,写一些内容,梳理一下自己脑中的概念,也以此作为记录,希望自己以后能够方便查阅,也希望能够对各位看官有所帮助。
第一个Spring相关主题是Spring的事务管理,借助官方文档的一句话,Spring提供了一致的事物管理抽象模型,能够让大家在不同的事务API(JTA、JDBC、Hibernate、JPA、JDO等)之间有一致的编程体验,Spring提供了声明式事务管理和编程事务管理两种事务管理方式,前者借鉴了EJB CMT中事务管理方式,后者则简化了JTA的事务异常编程模型,从这个角度来看,Spring事务提供了全面简洁的一致性事务管理方案。
熟悉Java EE编程的人应该知道,事务分为全局(global)和本地(local)两种事务;全局事务提供了在多个事务源之间操作保证原子性的能力,典型的事务源是数据库,在一些企业集成领域还包括MQ等中间件,在Java EE体系中通过JTA来对全局事务提供支持,事务管理功能由应用服务器提供支持,在实际操作时需要通过JNDI进行事务对象的引用,这就存在两种耦合绑定,特定查找协议和特定应用容器绑定,这是不是不良的组织形式要看应用的需求,但在大多数时候,大多数应用为了获取全局事务的能力而付出这种耦合的代价都是比较不合算的;本地事务是资源特定的,例如使用JDBC connection进行的事务操作,本地事务中的事物管理功能是应用程序进行控制的,非容器托管,因此其能摆脱容器绑定的副作用,但其局限性也很明显,因为是资源特定的,其不能支持跨事务源的操作,所幸我们大部分应用都没有跨事务源的需求,本地事务在这种情况下比较适用;关于全局事务这里多补充一点,因为分布式事务本身的复杂性,其比较重量级,考虑的事务模型可能也比较复杂,除非必须使用,否则还是敬而远之为好,即使因为水平扩展、高可用、负载均衡等需求而需要添置额外的事务源,也应该尽量避免,例如使用Oracle RAC,将分布式事务的复杂性封装在数据库产品层面,简化应用级别的事务操作;实际上,全局事务操作不一定非需要绑定应用容器,借助独立的事务管理器如
Atomikos Transactions
和 JOTM也能提供JTA全局事务的功能,当然,还是如上所述,并不推荐使用。
Spring提供了一致的事务抽象,而这抽象的核心就是PlatformTransactionManager接口,接口定义如下
该接口有三个方法,第一个方法根据TransationDefinition对象获取事务(TransactionStatus),TransationDefinition也是一个接口,定义如下
通过这个定义,我们知道TransationDefinition提供了事务传播策略以及事务隔离级别的常量定义,同时提供了默认隔离和默认事务超时的常量定义,方法上提供了获取事务隔离级别、事务传播策略、事务超时时间、是否只读事务以及事务名称信息,基本上事务的基本概念都包含在了这个接口定义中;TransactionStatus提供了简单的控制事务以及查询事务状态的方法,定义如下
因为接口定义比较明了,这里不再展开赘述;
PlatformTransactionManager接口是Spring事务管理的核心,其类级结构如下
通过上面的结构图,我们看到Spring为每一种常用的具体的事务操作API提供了具体的实现,例如我们常用的基于纯JDBC的DataSourceTransactionManager和常用ORM hibernate的HibernateTransactionManager,JTA API特供了通用的实现以及针对Weblogic、WebSphere应用服务器提供的特定实现;
PlatformTransactionManager定义了基本的事务操作模型,而AbstractPlatformTransactionManager抽象类实现了该接口,该抽象类定义了一些所有具体实现类共有的属性和方法,同时定义了统一的事务处理流程,这是设计模式模板模式非常经典的应用,因为该类方法定义较多,这里我们就不在贴出该类的定义,我们来看看该类如何定义了统一的事务处理流程,该类实现了PlatformTransactionManager接口的commit方法,同时将该方法设为final,使其子类不能够重写,commit方法如下:
该方法定义了基本的提交处理,我们看到实际处理是processXXX等方法,processCommit部分代码如下
我们看到这个方法负责了具体事务提交的相关操作,最后实际提交操作是doCommit,而doCommit为一个抽象方法,这就是该类给子类实现留下的“钩子”,具体的提交操作由子类来去实现,这是一个典型的模板模式应用,接下来我们找一个具体子类来看看doCommit的实际操作,我们看看比较熟悉的HibernateTransactionManager,doCommit如下:
很明显,看到了Hibernate事务操作典型的用法,其他实现类似,这里我们不在赘述,大家可以自己看看。
关于具体的Spring事务配置,虽然比较基础,但我还是写在这里吧,在事务配置过程中,我穿插着描述一些原理性的东西,因为头脑比较混乱,条理不清,还望大家海涵。
事务管理配置的核心是各种具体
PlatformTransactionManager的配置,其次就是各种事务管理器如何作用在具体操作方法上的切面配置,因为事务管理器到具体方法的切面配置都是一致的,这里我们给出各种PlatformTransactionManager的配置,最后在来一个具体的事务切面配置,这里我们提供DataSourceTransactionManager、JtaTransactionManager以及HibernateTransactionManager的配置,关于用XML配置还是注解配置,这里我们先提供XML的配置,然后在后面给出一个完全注解的配置实例。
PlatformTransactionManager配置
1. DataSourceTransactionManager
首先需要配置JDBC的DataSource,毕竟这些本地事务是资源特定的嘛,DataSource定义如下
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
接着就是事务管理器的配置,DataSourceTransactionManager定义如下
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
这里事务管理器就配置完了,非常简单。
2. JtaTransactionManager
JtaTransactionManager配置更为简单,因为其是全局事务,为容器托管事务,其不需要知道特定的资源(DataSource),配置如下
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
但是注意JtaTransactionManager不需要知道DataSource,并不意味着不需要配置,Spring Data模块对资源的操作还是需要DataSource的,在这种场景下DataSource由应用容器托管,所以使用JNDI进行查找引用,如下
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
3.HibernateTransactionManager
Hibernate的配置比较多一些,首先是配置DataSource,和1中一致,这里不再赘述,接着就是SessionFactory的配置,配置如下
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">
<list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
</value>
</property>
</bean>
注意这里的配置比较老,如果使用基于注解的实体配置,请将mappingResources替换为
<property name="packagesToScan">
<list>
<value>com.app</value>
</list>
</property>
接着就是HibernateTransactionManager的配置了,配置如下
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
注意实际的持久化技术例如JDBC、JPA、JDO、Hibernate等和实际使用的事务管理器之间的区别,其没有必要一一对应,在使用JDBC时可以使用JTA全局事务管理器,同样,使用Hibernate也可以,只需将最后一步的事务管理器替换为JtaTransactionManager即可。
事务管理器到特定方法配置
接下来就是具体事务管理器如何作用在具体方法上的配置了,不过在这之前我们描述一下这个使用背景,如前所述,Spring提供了两种事务管理方式,一种是声明式事务管理(Declarative transaction management),另一种则是编程式事务管理(Programmatic transaction management),这两种方式显然声明式比较简单,而在事务管理需求比较少的时候,想比较声明式比较繁琐的配置,编程式则比较简单,这里大家根据自己的需求进行选择,Spring官方推荐使用声明式事务管理,后面的论述也会分别给出声明式事务和编程式事务相关主题。
声明式配置
声明式事务管理的基本原理是利用在Spring中应用比较广泛的面向切面编程(AOP),通过将事务处理放在切面对象中来进行一致性的管理,减少冗余代码,提升简洁性,原理图大致如下
其中的AOP proxy,Spring定义了TransactionInterceptor类,该类结构如下
通过这个结构,我们看到实际的方法都在TransactionInterceptor的父类TransactionAspectSupport中,TransactionInterceptor的关键方法是invoke方法,而这个方法内部调用了父类的invokeWithinTransaction方法,这是这里事务处理的核心,invokeWithinTransaction方法片段如下
这看起来是一个典型的切面代理方法,我们按照顺序讲下主要流程,首先270行获取该方法的事务属性,这些属性包括事务隔离级别、事务传播策略、只读、回滚等属性信息,然后271行获取平台事务管理器,因为Spring支持多事务管理器特性,这里需要配合实际配置来决定具体的事务管理器,注意276行,这一行获取了事务信息,同时又在必要情况下开启了事务,后面281行是具体方法执行,291行提交事务。
上面说了些原理,这里给出声明式事务的一些具体应用配置,在Spring中声明式事务大体有两种用法,一种是纯粹的切面配置,一种是基于@Transactional的注解配置,这里都会给出具体应用实例。
1.基于切面的配置
基于界面的配置比较简单,核心是Transaction 的Advice的配置,然后将这Advice配置在具体pointcut上就可以了,我们先看看Advice的配置,配置实例如下:
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
如上,这里的配置比较清晰明了,引用具体的事务管理器(如果事务管理器名称为transactionManager,那么可以不需要明确引用事务管理器),然后通配符匹配特定方法,配置特定事务属性;配置完Advice,接下来是将Advice关联到pointcut的配置,如下
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
这里的配置比较简单,不在赘述
关于事务属性的配置,可配置属性如下
属性 |
是否必须 |
默认值 |
描述 |
name |
是 |
|
方法名,可适用通配符(*) |
propagation |
否 |
REQUIRED |
事务传播策略 |
isolation |
否 |
DEFAULT |
事务隔离级别 |
timeout |
否 |
-1(无限制) |
事务超时时间 |
read-only |
否 |
false |
是否是只读事务 |
rollback-for |
否 |
|
事务回滚的异常 |
no-rollback-for |
否 |
|
事务不回滚的异常 |
除方法名之外,其他属性都是可选的,根据具体的应用场景进行定制,事务传播策略定义了不同事务的存在关系,可选属性列表如下(注意Spring事务概念中逻辑事务和物理事务的区别,物理事务就是指数据库层面的事务,而逻辑事务是应用层面的,可以具有更丰富的行为特性,Spirng中的事务就是特指逻辑事务,要注意)
传播策略 |
描述 |
REQUIRED |
支持当前上下文中存在事务,如果没有事务就创建一个新的,如果有,则在当前事务下执行 |
SUPPORTS |
支持当前上下文中存在事务,如果没有事务,就在无事务上下文下执行 |
MANDATORY |
支持当前上下文中存在事务,如果没有事务就抛出异常 |
REQUIRES_NEW |
产生一个新的事务,如果当前上下文中有事务就挂起原来事务 |
NOT_SUPPORTED |
不支持当前上下文存在事务,总是在无事务上下文下执行,存在的事务会进行挂起操作 |
NEVER |
不支持当前上下文存在事务,如果存在事务则抛出异常 |
NESTED |
如果当前上下文中存在事务就执行一个内嵌的事务 |
事务隔离级别这个属性是数据库层面的,可选属性列表如下
传播策略 |
避免问题 |
不能避免问题 |
READ_UNCOMMITTED |
第一更新丢失 |
脏读、不可重复读、幻读 |
READ_COMMITTED |
第一更新丢失、脏读 |
不可重复读、幻读 |
REPEATABLE_READ |
第一更新丢失、脏读、不可重复读 |
幻读
|
SERIALIZABLE |
第一更新丢失、脏读、不可重复读、幻读 |
|
具体这些问题以及每个隔离级别的具体特征有机会在进行叙述吧,不然太多了,这里注意不同的数据库对不同隔离级别支持是不同的,例如Oracle只能支持读提交和序列化,不可重复读的问题通过额外的乐观锁实现,其中存在只读事务,隔离级别实际是序列化,在配置事务只读时要注意这里的特性;Mysql支持比较丰富,但其可重复读能够解决部分幻读问题,这是与其实现有关的,等有机会把它展开叙述,一般应用场景下设置读提交就能满足要求,读未提交隔离过低,而其他两个隔离级别又太重量级,使用的话会严重降低应用性能,对于一些并发问题的容忍性,第一更新丢失、脏读、不可重复读(特殊场景为第二更新丢失)是不能容忍的,这里可以使用读提交隔离级别+乐观锁来屏蔽第二更新丢失的问题,这是一个权衡场景需要和性能情况下做出的综合性方案。
事务超时设置可以根据需要设置,一般情况下,为了防止数据库锁阻塞,所有方法都应该有事务超时时间。
只读事务设置注意其性能优化的价值,一般情况下,只读操作可能都不需要事务,这时候降低事务隔离级别或者干脆不要事务都能够提升应用性能。
事务异常回滚,关于受检异常和非受检异常的争论这里就不在论述了,Spring事务采用了JTA的惯例,默认情况下只用运行时异常及其子类会导致事务回滚,受检异常不会导致事务回滚,后面两个选项就是关于回滚与不回滚异常设置的,这里可以根据需要进行设置。
2.基于@Transactional的事务管理
说完了切面设置的事务管理,这里说一下基于@Transactional的事务管理,我们看下该注解的一些定义,如下
通过上面的定义,我们知道这个注解可以放在类上和特定方法(只能为public方法,protected、private、package-visible都不可以)上,放在类上的话,该类所有的public方法都有了事务,放在特定方法上可以定制这个方法执行的事务属性,为了启用这种注解式的事务管理方式,需要在xml中添加如下信息
<tx:annotation-driven transaction-manager="txManager"/>
这时候就可以把之前事务配置中切面的部分删掉了,关于该注解上的属性,因为和上面切面形式中类似,这里就不在赘述了
上面的配置还是基于XML的,这里给出一个完全基于注解的实例
注解配置类如下
这里开启bean扫描,开启基于@Transactional的事务管理
其中的一些Bean定义,DataSource
SessionFactory
TransactionManager
在初始化的部分使用AnnotationConfigApplicationContext初始化就可以了
编程式事务管理
说完了声明式的事务管理,接下来讲讲编程式事务管理,Spring提供了两种编程方式,一种是使用TransactionTemplate,一种是直接使用具体PlatformTransactionManager,官方推荐第一种方式,接下来我们分别进行叙述。
1. 使用TransactionTemplate
TransactionTemplate采用一种回调的方式将要执行方法包含在事务中,其初始化需要指定特定的事务管理器,实际使用代码片段如下
transactionTemplate.execute(new TransactionCallback() {
// the code in this method executes in a transactional context
public Object doInTransaction(TransactionStatus status) {
updateOperation1();
return resultOfUpdateOperation2();
}
})
上例是有返回结果的,可以不需要返回结果,如下
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
})
可以在方法体内回滚事务,设置status.setRollbackOnly()即可
TransactionTemplate提供了一些事务属性设置接口,可以根据需要进行设置。
2.使用PlatformTransactionManager
这个比较简单,关键是定义TransactionDefinition,代码如下
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
代码很清晰明了,这里就不在赘述了。
写不下去了,算是做了一次梳理吧,有时间在进行详细叙述。