Spring事务核心对象
J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别,当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
Spring为业务层提供了整套的事务解决方案:
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus
PlatformTransactionManager
PlatformTransactionManager:平台事务管理器实现类,是一个接口,需要使用它的实现类
-
DataSourceTransactionManager
适用于Spring JDBC或MyBatis -
HibernateTransactionManager
适用于Hibernate3.0及以上版本 -
JpaTransactionManager
适用于JPA -
JdoTransactionManager
适用于JDO -
JtaTransactionManager
适用于JTA -
JPA
(Java Persistence API)Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行 -
JDO(Java Data Object )是Java对象持久化规范,用于存取某种数据库中的对象,并提供标准化API。与JDBC相比,JDBC仅针对关系数据库进行操作,JDO可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强
-
JTA(Java Transaction API)Java EE 标准之一,允许应用程序执行分布式事务处理。与JDBC相比,JDBC事务则被限定在一个单一的数据库连接,而一个JTA事务可以有多个参与者,比如JDBC连接、JDO 都可以参与到一个JTA事务中
此接口定义了事务的基本操作
- 获取事务 :
TransactionStatus getTransaction(TransactionDefinition definition)
- 提交事务 :
void commit(TransactionStatus status)
- 回滚事务 :
void rollback(TransactionStatus status)
TransactionDefinition
此接口定义了事务的基本信息
- 获取事务定义名称
String getName()
- 获取事务的读写属性
boolean isReadOnly()
- 获取事务隔离级别
int getIsolationLevel()
- 获事务超时时间
int getTimeout()
- 获取事务传播行为特征
int getPropagationBehavior()
TransactionStatus
此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作
获取事务是否处于新开启事务状态
boolean isNewTransaction()
获取事务是否处于已完成状态
boolean isCompleted()
获取事务是否处于回滚状态
boolean isRollbackOnly()
刷新事务状态
void flush()
获取事务是否具有回滚存储点
boolean hasSavepoint()
设置事务处于回滚状态
void setRollbackOnly()
事务控制方式
-
编程式
-
声明式(XML)
-
声明式(注解)
案例:
模拟银行转账业务说明,银行转账操作中,涉及从A账户到B账户的资金转移操作。数据层仅提供单条数据的基础操作,未设计多账户间的业务操作。
案例环境(基于Spring、Mybatis整合):
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:property-placeholder location="classpath:*.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="accountService" class="com.itzhuzhu.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.itzhuzhu.domain"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itzhuzhu.dao"/>
</bean>
</beans>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db1
jdbc.username=root
jdbc.password=不告诉你
AccountDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itzhuzhu.dao.AccountDao">
<update id="inMoney">
update account set money = money + #{money} where name = #{name}
</update>
<update id="outMoney">
update account set money = money - #{money} where name = #{name}
</update>
</mapper>
dao包下AccountDao
public interface AccountDao {
void inMoney(@Param("name") String name, @Param("money") Double money);
void outMoney(@Param("name") String name, @Param("money") Double money);
}
domain包下Account
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
}
service包下AccountService
public interface AccountService {
/**
* 转账操作
*
* @param outName 出账用户名
* @param inName 入账用户名
* @param money 转账金额
*/
public void transfer(String outName, String inName, Double money);
}
service.impl包下AccountServiceImpl
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outName, String inName, Double money) {
// 开启事务
PlatformTransactionManager pt = new DataSourceTransactionManager(dataSource);
// 事务定义
TransactionDefinition td = new DefaultTransactionDefinition();
// 事务状态
TransactionStatus ts = pt.getTransaction(td);
accountDao.inMoney(outName, money);
int i = 1 / 0;
accountDao.outMoney(inName, money);
// 提交事务
pt.commit(ts);
}
}
测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) ctx.getBean("accountService");
accountService.transfer("张三","李四",100D);
}
}
编程式事务:
public void transfer(String outName,String inName,Double money){
//创建事务管理器
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置与数据层相同的数据源
dstm.setDataSource(dataSource);
//创建事务定义对象
TransactionDefinition td = new DefaultTransactionDefinition();
//创建事务状态对象,用于控制事务执行
TransactionStatus ts = dstm.getTransaction(td);
accountDao.inMoney(outName,money);
int i = 1/0; //模拟业务层事务过程中出现错误
accountDao.outMoney(inName,money);
//提交事务
dstm.commit(ts);
}
使用AOP控制事务
将业务层的事务处理功能抽取出来制作成AOP通知,利用环绕通知运行期动态织入。
aop包下TxAdvice
public class TxAdvice {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Object transactionManager(ProceedingJoinPoint pjp) throws Throwable {
// 开启事务
PlatformTransactionManager pt = new DataSourceTransactionManager(dataSource);
// 事务定义
TransactionDefinition td = new DefaultTransactionDefinition();
// 事务状态
TransactionStatus ts = pt.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
// 提交事务
pt.commit(ts);
return ret;
}
}
AccountServiceImpl
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String outName, String inName, Double money) {
accountDao.inMoney(outName, money);
int i = 1 / 0;
accountDao.outMoney(inName, money);
}
}
applicationContext.xml
<!--AOP配置-->
<bean id="txAdvice" class="com.itzhuzhu.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="transactionManager" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
声明式事务(XML)
声明式事务是由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: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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:property-placeholder location="classpath:*.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="accountService" class="com.itzhuzhu.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<!-- <property name="dataSource" ref="dataSource"/>-->
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.itzhuzhu.domain"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itzhuzhu.dao"/>
</bean>
<!-- <!–AOP配置–>
<bean id="txAdvice" class="com.itzhuzhu.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="transactionManager" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>-->
<!--TX方式-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--定义事务管理的通知类-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!--定义控制的事务-->
<tx:attributes>
<tx:method name="*" read-only="false"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method
name="transfer"
read-only="false"
timeout="-1"
isolation="DEFAULT"
no-rollback-for=""
rollback-for=""
propagation="REQUIRED"
/>
<!--<tx:method name="transfer" read-only="false"/>-->
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.itzhuzhu.service.*Service.*(..))"/>
<aop:pointcut id="pt2" expression="execution(* com.itzhuzhu.dao.*.b(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt2"/>
</aop:config>
</beans>
使用tx命名空间配置事务专属通知类
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
</tx:attributes>
</tx:advice>
使用aop:advisor在AOP配置中引用事务专属通知类
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
aop:advice与aop:advisor区别:
-
aop:advice配置的通知类可以是普通java对象,不实现接口,也不使用继承关系
-
aop:advisor配置的通知类必须实现通知接口
-
MethodBeforeAdvice
-
AfterReturningAdvice
-
ThrowsAdvice
-
tx配置——tx:advice
-
名称:tx:advice
-
类型:标签
-
归属:beans标签
-
作用:专用于声明事务通知
-
格式:
<beans>
<tx:advice id="txAdvice" transaction-manager="txManager">
</tx:advice>
</beans>
-
基本属性:
-
id :用于配置aop时指定通知器的id
-
transaction-manager :指定事务管理器bean
-
tx配置——tx:attributes
-
名称:tx:attributes
-
类型:标签
-
归属:tx:advice标签
-
作用:定义通知属性
-
格式:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
</tx:attributes>
</tx:advice>
-
基本属性:
- 无
tx配置——tx:method
-
名称:tx:method
-
类型:标签
-
归属:tx:attribute标签
-
作用:设置具体的事务属性
-
格式:
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
</tx:attributes>
-
说明:
通常事务属性会配置多个,包含1个读写的全事务属性,1个只读的查询类事务属性
tx:method属性:
<tx:method
name="transfer" 待添加事务的方法名表达式(支持*号通配符),例如get*、*、......
read-only="false" 待设置事务的读写属性,true为只读,false为读写
timeout="-1" 设置事务超时时长,单位秒
isolation="DEFAULT" 设置事务隔离级别,该隔离级设定是基于spring的设定,非数据库端
no-rollback-for="" 设置事务中不回滚的异常,多个异常间使用逗号分割
rollback-for="" 设置事务中必回滚的异常,多个异常间使用逗号分割
propagation="REQUIRED" 设置事务的传播行为
事务传播行为
-
事务管理员
-
事务协调员
-
事务传播行为描述的是事务协调员对事务管理员所携带事务的处理态度
事务传播应用:
-
场景A:生成订单业务
-
子业务S1:记录日志到数据库表X
-
子业务S2:保存订单数据到数据库表Y
-
子业务S3:……
-
如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
-
(S1需要新事务)
-
-
场景B:生成订单业务
-
背景1:订单号生成依赖数据库中一个专门用于控制订单号编号生成的表M获取
-
背景2:每次获取完订单号,表M中记录的编号自增1
-
子业务S1:从表M中获取订单编号
-
子业务S2:保存订单数据,订单编号来自于表M
-
子业务S3:……
-
如果S2或S3或……事务提交失败,此时S1是否回滚?如何控制?
-
(S1需要新事务)
-
声明式事务(注解)
@Transactional
-
名称:@Transactional
-
类型:方法注解,类注解,接口注解
-
位置:方法定义上方,类定义上方,
接口定义上方
(不要写在实现类,实现类会换,一般写接口上,接口方法写详细的,接口类可以做一个大的控制) -
作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性
-
范例:
@Transactional
public interface AccountService {
/**
* 转账操作
*
* @param outName 出账用户名
* @param inName 入账用户名
* @param money 转账金额
*/
@Transactional(
readOnly = false,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {java.lang.ArithmeticException.class, IOException.class},
noRollbackFor = {},
propagation = Propagation.REQUIRED)
public void transfer(String outName, String inName, Double money);
}
tx:annotation-driven
-
名称:tx:annotation-driven
-
类型:标签
-
归属:beans标签
-
作用:开启事务注解驱动,并指定对应的事务管理器
-
范例:
<tx:annotation-driven transaction-manager="txManager"/>
声明式事务(纯注解驱动)
-
名称:@EnableTransactionManagement
-
类型:类注解
-
位置:Spring注解配置类上方
-
作用:开启注解驱动,等同XML格式中的注解驱动
-
范例:
AccountDao
public interface AccountDao {
@Update("update account set money = money + #{money} where name = #{name}")
void inMoney(@Param("name") String name, @Param("money") Double money);
@Update("update account set money = money - #{money} where name = #{name}")
void outMoney(@Param("name") String name, @Param("money") Double money);
}
AccountServiceImpl
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String outName, String inName, Double money) {
accountDao.inMoney(outName,money);
int i = 1/0;
accountDao.outMoney(inName,money);
}
}
JDBCConfig
public class JDBCConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
@Bean
public PlatformTransactionManager getTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
MyBatisConfig
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.itzhuzhu.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itzhuzhu.dao");
return msc;
}
}
SpringConfig
@Configuration
@ComponentScan("com.itzhuzhu")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class,TransactionManagerConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
public class TransactionManagerConfig {
@Bean
public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}