Spring企业应用之事务处理

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 —— 嵌入式事务和它的父事务是相依的,它的提交是要等和它的父事务一块提交的。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×