Java原生方式使用事务处理
前面的文章Spring企业应用之数据库连接JDBC中有介绍过如何使用Java原生方式来使用JDBC,但是没有涉及到事务,事务是数据库中的一个概念,任何SQL在数据库的执行都需要1个事务,即使不显示声明事务,也会当作1个事务来处理。如何用Java原生方式使用事务呢,核心还是java.sql包中的Connection对象,1个Connection对象就是1个数据库连接,通过这个连接可以进行事务的commit提价和rollback回滚。
例如可以使用下面代码来进行事务提交:
Connection con = DriverManager.getConnection("jdbc:mysql://ip:port/name", username, password);
//执行若干sql语句
//提交事务
con.commit();
当程序出现异常时,需要回滚,则使用下面的代码:
Connection con = DriverManager.getConnection("jdbc:mysql://ip:port/name", username, password);
//执行若干sql语句
//回滚事务
con.rollback();
由于大多数业务逻辑都需要事务的支持,如果每个逻辑都使用上面的方法来实现事务的提交和回滚,业务逻辑的实现都要冗余这部分事务代码,因此可以Spring来进行事务的管理。
Spring事务处理
Spring支持众多的事务API,包括Java事务API的JTA,Hibernate和Java Persistence API的JPA等,这里只讨论的是对JDBC事务API的支持。Spring中对JDBC事务支持的核心类是DataSourceTransactionManager类,通过数据源DataSource来进行事务的管理。DataSourceTransactionManager类实现了PlatformTransactionManager接口,PlatformTransactionManager接口是事务实现的顶层接口,定义了事务的基本方法,除了DataSourceTransactionManager类外,还有JpaTransactionManager、JtaTransactionManager、HibernateTransactionManager等类实现这个接口来对相应的事务API提供支持。这里有一个例外是TransactionTemplate类,这个类并没有实现PlatformTransactionManager接口,而是以组合的方式持有PlatformTransactionManager实例对象,进而实现编程式事务管理。
编程式事务处理
TransactionTemplate的实现,是Spring常用的模板模式实现,这个类有1个核心execute方法,它接收1个TransactionCallback回调接口对象,用于传递具体的业务逻辑实现。
代码如下:
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
业务代码通过匿名实现TransactionCallback回调接口的方式来实现doInTransaction方法,并根据TransactionStatus入参来管理实务状态。
1个简单的示例代码如下:
public class SimpleService implements Service {
private TransactionTemplate transactionTemplate;
// 构造注入PlatformTransactionManager,一般是DataSourceTransactionManager
public SimpleService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public Object someServiceMethod() {
return transactionTemplate.execute(new TransactionCallback() {
// 匿名实现接口
public Object doInTransaction(TransactionStatus status) {
Object result = null;
try {
//执行业务逻辑
result = updateOperation1();
} catch (SomeBusinessException ex) {
//事务回滚
status.setRollbackOnly();
}
return result;
}
});
}
}
Spring将事务的逻辑处理封装到了TransactionTemplate类的execute方法中,同时也通过TransactionStatus入参对象给了业务逻辑手动处理事务的能力,在需要比较灵活处理事务的场景中可以使用这种方式。
声明式事务处理
声明式事务处理是最常用的事务处理方式,借助Spring Aop的能力能够将事务的处理与业务处理分离,减少对业务代码的侵入,对应用程序代码的影响最小。声明式事务的核心类是TransactionInterceptor,业务方法运行时会被Aop所代理,Aop代理会调用对应的Advisor类来实现代理逻辑,BeanFactoryTransactionAttributeSourceAdvisor类是Advisor接口的实现类,这个类持有TransactionInterceptor的实例,最后调用TransactionInterceptor类的invoke方法完成整个事务的逻辑。
使用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:context="http://www.springframework.org/schema/context"
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-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${initialSize}" />
<!-- 连接池最大数量 -->
<property name="maxActive" value="${maxActive}" />
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${maxIdle}" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${minIdle}" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${maxWait}" />
</bean>
<!-- 编程式事务管理 -->
<!-- 配置事务管理器 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第一步:配置事务管理器 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第二步:配置事务增强 -->
<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
<!-- 做事务操作 -->
<tx:attributes>
<!-- 设置进行事务操作的方法匹配规则 -->
<!-- account开头的所有方法 -->
<!--
propagation:事务传播行为;
isolation:事务隔离级别;
read-only:是否只读;
rollback-for:发生那些异常时回滚
timeout:事务过期时间
-->
<tx:method name="account*" propagation="REQUIRED"
isolation="DEFAULT" read-only="false" rollback-for="" timeout="-1" />
</tx:attributes>
</tx:advice>
<!-- 第三步:配置切面 切面即把增强用在方法的过程 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* cn.itcast.service.OrdersService.*(..))"
id="pointcut1" />
<!-- 切面 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut1" />
</aop:config>
<!-- 对象生成及属性注入 -->
<bean id="ordersService" class="cn.itcast.service.OrdersService">
<property name="ordersDao" ref="ordersDao"></property>
</bean>
<bean id="ordersDao" class="cn.itcast.dao.OrdersDao">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
OrdersService.java(业务逻辑处理)
public class OrdersService {
private OrdersDao ordersDao;
public void setOrdersDao(OrdersDao ordersDao) {
this.ordersDao = ordersDao;
}
// 调用dao的方法
// 业务逻辑,写转账业务
public void accountMoney() {
// 小马多1000
ordersDao.addMoney();
// 加入出现异常如下面int i=10/0(银行中可能为突然停电等。。。);结果:小马账户多了1000而小王账户没有少钱
// 解决办法是出现异常后进行事务回滚
int i = 10 / 0;// 事务管理配置后异常已经解决
// 小王 少1000
ordersDao.reduceMoney();
}
}
使用注解配置的方式来配置声明式事务处理
<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:context="http://www.springframework.org/schema/context"
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-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${initialSize}" />
<!-- 连接池最大数量 -->
<property name="maxActive" value="${maxActive}" />
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${maxIdle}" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${minIdle}" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${maxWait}" />
</bean>
<!-- 编程式事务管理 -->
<!-- 配置事务管理器 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第一步:配置事务管理器 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 第二步: 开启事务注解 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
<!-- 对象生成及属性注入 -->
<bean id="ordersService" class="cn.itcast.service.OrdersService">
<property name="ordersDao" ref="ordersDao"></property>
</bean>
<bean id="ordersDao" class="cn.itcast.dao.OrdersDao">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
OrdersService.java(业务逻辑处理)
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class OrdersService {
private OrdersDao ordersDao;
public void setOrdersDao(OrdersDao ordersDao) {
this.ordersDao = ordersDao;
}
// 调用dao的方法
// 业务逻辑,写转账业务
public void accountMoney() {
// 小马多1000
ordersDao.addMoney();
// 加入出现异常如下面int i=10/0(银行中可能为突然停电等。。。);结果:小马账户多了1000而小王账户没有少钱
// 解决办法是出现异常后进行事务回滚
// int i = 10 / 0;// 事务管理配置后异常已经解决
// 小王 少1000
ordersDao.reduceMoney();
}
}
使用@Transactional注解来标注接口、类、方法,Spring会在提取事务标签时,依次从方法->类->接口来获取提取查看是否存在事务声明。
Spring默认只会对RuntimeException异常进行回滚,通过设置rollbackFor参数来支持其他异常的回滚。
声明式事务支持多种传播方式,适用不同的业务场景:
-
PROPAGATION_REQUIRED —— 支持当前事务,如果当前没有事务,则新建一个事务,这是最常见的选择,也是 Spring 默认的一个事务传播属性。
-
PROPAGATION_SUPPORTS —— 支持当前事务,如果当前没有事务,则以非事务方式执行。
-
PROPAGATION_MANDATORY —— 支持当前事务,如果当前没有事务,则抛出异常。
-
PROPAGATION_REQUIRES_NEW —— 新建事务,如果当前存在事务,把当前事务挂起。
-
PROPAGATION_NOT_SUPPORTED —— 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
-
PROPAGATION_NEVER —— 以非事务方式执行,如果当前存在事务,则抛出异常。
-
PROPAGATION_NESTED —— 嵌入式事务和它的父事务是相依的,它的提交是要等和它的父事务一块提交的。