十一、Spring事务管理机制
在spring中事务是自动提交的,但是我们在操作数据的时候,总有些业务流程需要事务控制。在实际开发中,操作数据库时还会涉及到事务管理的问题,为此Spring提供了专门用于事务处理的API,Spring事务管理简化了传统的事务管理流程,并在一定程度上减少了开发者的工作量。
在项目中,业务层(Service层)既是处理业务的地方,业务层编写又是管理数据库事务的地方,要对事务进行测试,首先创建业务层,并在业务层编写相关的业务代码。
以银行转账为例:张三账户上有100块钱,李四账户上有1块钱,当张三账户向李四账户转账时,在没有事务管理的情况下,转账双方无论转出和转入是否同时成功,这个程序都会运行,这样就导致了张三账户上的钱没有减少而李四账户上的钱增加了,很明显,吃亏的只能是银行,所以银行在做管理系统的时候,都会考虑到任何业务的执行情况,添加事务管理机制,并且配置正确,那么在转账时,只要有任何一方在执行业务的时候出现异常,事务立马回滚,保证事务同事成功或失败!
11.1 事务管理的核心接口
- PlatformTransactionManager
PlatformTransactionManager接口是Spring提供的平台事务管理器,主要用于管理事务。
- 事务操作的方法:
- 接口的不同实现类:
- TransactionDefinition
TransactionDefinition接口是事务定义(描述)的对象,该对象中定义了事务规则,并提供了获取事务相关信息的方法:
这些方法中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务,传播行为有很多种:
参数 | 描述 |
---|
REQUIRED | 表示当前方法必须运行在一个事务环境当中,如果当前方法已处于事务环境中,则可以直接使用该方法;否则会开启一个新事物后执行该方法,这个可以作为首选的事务传播行为 | SUPPORT | 如果当前方法处于事务环境中,则使用当前事务,否则不使用事务(即以非事务方法执行) | MANDATORY | 表示调用该方法的线程必须处于当前事务环境中,否则将抛出异常 | REQUIRES_NEW | 要求方法在新的事务环境中执行。如果当前方法已在事务环境中,则先暂停当前事务,在启动新的事务后执行该方法;如果当前方法不在事务环境中,则会启动一个新的事务后执行方法 | NOT_SUPPORTED | 不支持当前事务,总是以非事务状态执行,如果调用该方法的线程处于事务环境中,则先暂停当前事务,然后执行该方法 | NEVER | 不支持当前事务,如果调用该方法的线程处于事务环境中,将抛异常 | NESTED | 即使当前执行的方法处于事务环境中,依然会启动一个新的事务,并且方法在嵌套的事务里执行;即使当前执行的方法不在事务环境中,也会启动一个新事物,然后执行该方法 |
小贴士: 在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务,通常情况下,数据的查询不会影响源数据的改变,所以不需要进行事务管理,而对于数据的插入、更新和删除操作,必须进行事务管理。如果没有指定事件的传播行为,Spring默认传播行为是REQUIRED。
- TransactionStatus
TransactionStatus接口是事务的状态,它面搜狐了某一时间点上事务的状态信息,该接口中的方法:
11.2 事务管理的方式
Spring事务管理分为两种方式:一种是传统的编程式事务管理,另一种是声明式事务管理:
- 编程式事务管理
是通过编写代码实现的事务管理,包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚。 - 声明式事务管理
是通过AOP技术实现的事务管理,其主要思想是将事务管理作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”代码织入到业务目标类中。
声明式事务管理最大的优点在于开发者无需通过编程的方式来管理事务,只需在配置文件汇总进行相关的事务规则声明,就可以将事务规则应用到业务逻辑中。这使得开发人员可以更加专注于核心业务逻辑代码的编写,在一定程度上减少了工作量,提供了开发效率,所以在实际开发中,通常都推荐使用是鞥明是事务管理。
11.3 配置声明式事务的核心问题
对哪些方法,采取什么样的事务策略。
11.4 基于XML方式的声明式事务管理
基于XML方式的声明式事务管理是通过在配置文件中配置事务的相关声明来实现的。
以银行转账为例:
实体类Account.java
package cn.pojo;
public class Account implements java.io.Serializable
{
private String accountid;
private String accoutname;
private float accountmoney;
public String getAccountid() {
return accountid;
}
public void setAccountid(String accountid) {
this.accountid = accountid;
}
public String getAccoutname() {
return accoutname;
}
public void setAccoutname(String accoutname) {
this.accoutname = accoutname;
}
public float getAccountmoney() {
return accountmoney;
}
public void setAccountmoney(float accountmoney) {
this.accountmoney = accountmoney;
}
}
dao层接口AccountMapper.java
package cn.dao;
import org.apache.ibatis.annotations.Param;
public interface AccountMapper {
int transMoney1(@Param("accountid")String accountid,@Param("money")float money);
int transMoney2(@Param("accountid")String accountid,@Param("money")float money);
}
业务逻辑层接口及实现类AccountService.java和AccountServiceImpl.java
package cn.service;
import org.apache.ibatis.annotations.Param;
public interface AccountService {
int transMoney(@Param("accountid1")String accountid1,@Param("accountid2")String accountid2,@Param("moeny")float money);
}
package cn.service.impl;
import cn.dao.AccountMapper;
import cn.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
AccountMapper accountMapper;
@Override
public int transMoney(String accountid1, String accountid2, float money) {
int r1 = accountMapper.transMoney1(accountid1,money);
int r2 = accountMapper.transMoney2(accountid2,money);
return 1;
}
}
MyBatis配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="log4j"/>
<setting name="autoMappingBehavior" value="FULL"/>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<package name="cn.pojo"/>
</typeAliases>
</configuration>
Spring核心配置文件applicationContext.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:p="http://www.springframework.org/schema/p"
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-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<context:property-placeholder location="database.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" 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}"/>
<property name="filters" value="${jdbc.filters}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
<property name="initialSize" value="${jdbc.initialSize}"/>
<property name="maxWait" value="${jdbc.maxWait}"/>
<property name="minIdle" value="${jdbc.minIdle}"/>
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="${jdbc.validationQuery}"/>
<property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
<property name="testOnBorrow" value="${jdbc.testOnBorrow}"/>
<property name="testOnReturn" value="${jdbc.testOnReturn}"/>
<property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="${jdbc.maxPoolPreparedStatementPerConnectionSize}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="mybatis-config.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.dao"/>
</bean>
<context:component-scan base-package="cn.service"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="trans*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="get*" propagation="SUPPORTS"/>
<tx:method name="find*" propagation="SUPPORTS"/>
<tx:method name="search*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethod" expression="execution(* cn.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
</aop:config>
</beans>
11.5 基于Annotation方式的声明式事务
Spring的声明式事务管理还可以通过注解方式实现,这种方式下我们只需做两件事情:
- 在Spring容器中注册事务注解驱动;
- 在需要使用事务的Spring Bean类或者Bean类的方法上添加注解@Transactional。如果将注解添加在Bean类上,则表示事务的社会对整个Bean类的所有方法都起作用;如果将注解添加在Bean类的某个方法上,则表示事务的设置只对该方法有效。
在上述例子中,我们只需在xml文件中添加自动注解,在service中添加事务注解即可:
Spring核心配置文件applicationContext.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:p="http://www.springframework.org/schema/p"
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-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<context:property-placeholder location="database.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" 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}"/>
<property name="filters" value="${jdbc.filters}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
<property name="initialSize" value="${jdbc.initialSize}"/>
<property name="maxWait" value="${jdbc.maxWait}"/>
<property name="minIdle" value="${jdbc.minIdle}"/>
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="${jdbc.validationQuery}"/>
<property name="testWhileIdle" value="${jdbc.testWhileIdle}"/>
<property name="testOnBorrow" value="${jdbc.testOnBorrow}"/>
<property name="testOnReturn" value="${jdbc.testOnReturn}"/>
<property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="${jdbc.maxPoolPreparedStatementPerConnectionSize}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="mybatis-config.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.dao"/>
</bean>
<context:component-scan base-package="cn.service"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
业务层实现类AccountServiceImpl.java
package cn.service.impl;
import cn.dao.AccountMapper;
import cn.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
@Autowired
AccountMapper accountMapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int transMoney(String accountid1, String accountid2, float money) {
int r1 = accountMapper.transMoney1(accountid1,money);
int r2 = accountMapper.transMoney2(accountid2,money);
return 1;
}
}
也就是说:
|