1.1 为什么用spring事务管理?
在没有spring事务管理器之前,java开发者通常有两种事务管理器可供选择:本地事务和全局事务。本地事务针对的是某一个具体资源(例如JDBC),使用起来也比较方便,但当程序需要处理多个事务资源时,本地事务则无法胜任了。全局事务解决了这个问题,一般都会采用JTA(JAVA transaction API)来实现全局事务。但全局事务的限制也很多,如必须使用JNDI数据源、需要支持XA协议的数据库、依赖于容器、性能低下、只支持单应用多数据源,不支持多应用多数据源等。
针对不同环境、不同事务策略,spring提供了一个事务管理器的抽象层,开发者通过这个抽象事务管理器进行管理事务,之后配置一个具体的事务实现层,就可以实现代码写一次,可以适用于所有的具体事务管理器API。
1.2 理解spring抽象事务管理器
spring抽象事务管理器采用的是策略模式,理解策略模式的关键点就是看它的策略。org.springframework.transaction.PlatformTransactionManager接口定义了相关方法
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager提供了3个方法,getTransaction根据TransactionDefinition中定义的相关信息创建或者返回一个已经存在的事务。返回的TransactionStatus代表着一个事务。commit和rollback分别表示提交事务和回滚事务。TransactionDefinition细节如下:
public interface TransactionDefinition {
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
String getName();
}
getPropagationBehavior获取事务的传播特性,可选值如下:
传播特性 | 值 | 解释 |
---|---|---|
PROPAGATION_REQUIRED | 0 | 支持当前事务环境,如果没有则创建一个 |
PROPAGATION_SUPPORTS | 1 | 支持当前事务,没有不创建事务 |
PROPAGATION_MANDATORY | 2 | 支持当前事务,没有则报错 |
PROPAGATION_REQUIRES_NEW | 3 | 挂起当前事务,新建一个事务 |
PROPAGATION_NOT_SUPPORTED | 4 | 如果存在事务则挂起 |
PROPAGATION_NEVER | 5 | 如果存在事务则报错 |
PROPAGATION_NESTED | 6 | 开启一个嵌套事务,很多数据库都不支持 |
事务的七种传播特性中,需要注意的一个点:用了事务管理器之后连接将由事务管理器管理了,例如用了PROPAGATION_NOT_SUPPORTED之后,第一次操作会获取一个新的connection,但之后的操作都是基于已获取的connection,如果用的是AbstractRoutingDataSource,在NOT_SUPPORTED传播特性的方法内切换数据源是无效的,因为选择数据源是在getConnection的时候确定的。
-- 切换数据源后,只有获取连接才会调用determineTargetDataSource使切换后的数据源生效
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
getIsolationLevel获取事务的隔离级别,可选值如下:
隔离级别 | 值 | 解释 |
---|---|---|
ISOLATION_DEFAULT | -1 | 使用数据源提供的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 1 | 允许一个事务读取另一个事务还没提交的数据 |
ISOLATION_READ_COMMITTED | 2 | 禁止脏读 |
ISOLATION_REPEATABLE_READ | 4 | 禁止不可重读 |
ISOLATION_SERIALIZABLE | 8 | 禁止幻读 |
一般数据库的默认级别都会设置成READ_COMMITTED,即禁止脏读。不可重读的意思是当一个事务内两次查询同一条数据,在第二次查询数据之前有个事务更改了这条数据并提交了事务,则第二次查询的数据是更改之后的数据,与第一次查询的不一致,即为不可重复读。设置成REPEATABLE_READ之后,更新那条数据的事务会等待查询那条数据的事务提交后才可以提交。幻读的意思是当 一个事务两次查询一个范围的数据, 在第二次查询之前有个事务插入了一条符合查询条件的事务,则会造成两次查询结果不一致。设置成禁止幻读之后,插入符合第一个查询条件的事务必须等查询事务提交之后,才可以被提交。可以看到,禁止不可重读和禁止幻读会对性能带来极大影响,一般数据库默认隔离级别都设置成禁止脏读。
getTimeout方法获取事务超时值,超过这个时间之后将会被回滚。isReadOnly标明这个事务是不是只读事务,如果有写操作会抛出异常。getName返回事务名字,便于在开发事务管理后台的时候区分事务,默认会设置成类全路径.方法名。
TransactionStatus像其它事务接口一样,提供了一些简单方式来控制事务执行和查询事务状态,详细如下:
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
1.3 声明spring事务管理器
无论使用声明式事务还是编程式事务,都需要指定具体的事务管理器实现类,一般我们采用依赖注入来指定具体的事务管理器。通常根据我们项目工作的环境来选择具体的事务管理器。
使用JDBC数据源的步骤:
<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>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
使用JTA数据源的步骤:
<?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:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd">
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
</beans>
使用hibernate数据源
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.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>
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
1.4 使用spring事务管理器
声明式事务需要与AOP相结合才可以使用,AOP会给要进行事务管理的方法生成代理,在TransactionInterceptor的invoke方法中,选择具体的PlatformTransactionManager 。可以在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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
</beans>
XML配置的一般是业务方法的默认事务行为。通常对于特殊方法,会采用@Transactional注解来配置事务的属性,需要使用tx:annotation-driven来启用事务注解支持。
<?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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
属性名 | 默认值 | 解析 |
---|---|---|
transaction-manager | transactionManager | 事务管理器的名字 |
mode | proxy | 代理模式,还可以是aspectj |
proxy-target-class | false | 仅在mode="proxy"时生效,即默认使用jdk接口代理,true的话则使用类代理 |
order | Ordered.LOWEST_PRECEDENCE | 事务委托器的优先级 |
默认的代理模式是运行时织入,所以如果你用注解的方法不是public的,或者你不是通过代理对象而是直接方法内部调用,及时你用了@Transaction注解,也是不会生效的。Transaction注解有个比较重要的属性value,当有多个事务管理器时,可以通过设置这个属性选择具体的事务管理器
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
}
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="account"/>
</bean>
1.5 手动获取connection连接
如果某些特殊场景下,你需要用connection直接执行些sql,可以采取下面的方法。如果当前是在一个事务环境,则会返回对应的connection,否则会为你创建一个connection。针对JPA和Hibernate使用的是EntityManagerFactoryUtils和SessionFactoryUtils两个类。
Connection conn = DataSourceUtils.getConnection(dataSource);
1.6 编码方式使用事务管理器
如果你的应用只有几个方法需要用到事务,为了避免使用代理带来了性能损耗,可以采用编码方式来管理事务
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
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);