Spring 事务


事务基础知识

MySQL 事务和锁


Spring 事务核心对象

J2EE 开发使用分层设计的思想:

  • 对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别。
  • 当业务中包含多个数据层的调用时,需要在业务层开启事务,即对数据层中多个操作进行组合并归属于同一个事务进行处理。

Spring 为业务层提供了整套的事务解决方案:

  1. PlatformTransactionManager
  2. TransactionDefinition
  3. TransactionStatus

1)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 事务中。

PlatformTransactionManager 接口定义了事务的基本操作:

// 获取事务
TransactionStatus getTransaction(TransactionDefinition definition)

// 提交事务
void commit(TransactionStatus status) 

// 回滚事务
void rollback(TransactionStatus status)

2)TransactionDefinition

此接口定义了事务的基本信息:

// 获取事务定义名称
String getName()

// 获取事务的读写属性
boolean isReadOnly()

// 获取事务隔离级别
int getIsolationLevel()

// 获事务超时时间
int getTimeout()

// 获取事务传播行为特征
int getPropagationBehavior()

3)TransactionStatus

此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作:

image


事务控制方式

案例说明

银行转账业务说明

  • 银行转账操作中,涉及从 A 账户到 B 账户的资金转移操作。

  • 本案例的数据层仅提供单条数据的基础操作,未涉及多账户间的业务操作。

案例环境:本案例环境基于 Spring、Mybatis 整合。

  • 业务层接口提供转账操作:
/**
* 转账操作
* @param outName     出账用户名
* @param inName      入账用户名
* @param money       转账金额
*/
public void transfer(String outName, String inName, Double money);
  • 业务层实现提供转账操作:
public void transfer(String outName, String inName, Double money){
    accountDao.inMoney(outName, money);                                                       accountDao.outMoney(inName, money);
}
  • 数据层提供对应的入账与出账操作:
<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>

1)编程式事务

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 通知,利用环绕通知运行期动态织入:

public Object tx(ProceedingJoinPoint pjp) throws Throwable {
    
    DataSourceTransactionManager dstm = new DataSourceTransactionManager();
    dstm.setDataSource(dataSource);
    TransactionDefinition td = new DefaultTransactionDefinition();
    TransactionStatus ts = dstm.getTransaction(td);
    Object ret = pjp.proceed(pjp.getArgs());
    dstm.commit(ts);
    
    return ret;
}

配置 AOP 通知类,并注入 dataSource:

<bean id="txAdvice" class="com.aop.TxAdvice">
    <property name="dataSource" ref="dataSource"/>
</bean>

使用环绕通知将通知类织入到原始业务对象执行过程中:

<aop:config>
    <aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
    <aop:aspect ref="txAdvice">
        <aop:around method="tx" pointcut-ref="pt"/>
    </aop:aspect>
</aop:config>

2)声明式事务(XML)

思考:AOP 配置事务是否具有特例性?如不同的读写操作配置不同的事务类型。

public Object tx(ProceedingJoinPoint pjp) throws Throwable {
    DataSourceTransactionManager dstm = new DataSourceTransactionManager();
    dstm.setDataSource(dataSource);
    TransactionDefinition td = new DefaultTransactionDefinition();
    TransactionStatus ts = dstm.getTransaction(td);
    Object ret = pjp.proceed(pjp.getArgs());
    dstm.commit(ts);

    return ret;
}
<bean id="txAdvice" class="com.aop.TxAdvice">
    <property name="dataSource" ref="dataSource"/>
</bean>

使用 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:advice

  • 类型:标签

  • 归属:beans 标签

  • 作用:专用于声明式事务通知

  • 格式:

<beans>
    <tx:advice id="txAdvice" transaction-manager="txManager">
    </tx:advice>
</beans>
  • 基本属性:

    • id:用于配置 aop 时指定通知器的 id

    • transaction-manager:指定事务管理器 bean

tx:attributes

  • 类型:标签

  • 归属:tx:advice 标签

  • 作用:定义通知属性

  • 格式:

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
    </tx:attributes>
</tx:advice>
  • 基本属性:

tx:method

  • 类型:标签

  • 归属:tx:attribute 标签

  • 作用:设置具体的事务属性

  • 格式:

<tx:attributes>
    <tx:method name="*" read-only="false" />
    <tx:method name="get*" read-only="true" />
</tx:attributes>
  • 说明:

    • 通常事务属性会配置多个,包含 1 个读写的全事务属性,1 个只读的查询类事务属性。
  • 基本属性:

image


3)声明式事务(注解)

@Transactional

  • 类型:方法注解、类注解、接口注解

  • 位置:方法定义上方、类定义上方、接口定义上方

  • 作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性

  • 范例:

@Transactional(
    readOnly = false,
    timeout = -1,
    isolation = Isolation.DEFAULT,
    rollbackFor = {ArithmeticException.class, IOException.class},
    noRollbackFor = {},
    propagation = Propagation.REQUIRES_NEW
)
void 接口/类名称();

方式一:配置开启注解驱动(tx:annotation-driven)

  • 类型:标签

  • 归属:beans 标签

  • 作用:开启事务注解驱动,并指定对应的事务管理器

  • 范例:

<tx:annotation-driven transaction-manager="txManager"/>

方式二:纯注解驱动

  • 名称:@EnableTransactionManagement

  • 类型:类注解

  • 位置:Spring 注解配置类上方

  • 作用:开启注解驱动,等同XML格式中的注解驱动

  • 范例:

// Spring 核心配置类
@Configuration
@ComponentScan("com")
@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);
    }
}

事务传播行为

事务传播行为描述的是事务协调员对事务管理员所携带事务的处理态度。

image


事务传播行为的类型

image


事务传播行为的应用

场景 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 需要新事务)


Spring 模板对象

常见的 Spring 模块对象

  • TransactionTemplate

  • JdbcTemplate

  • RedisTemplate

  • RabbitTemplate

  • JmsTemplate

  • HibernateTemplate

  • RestTemplate


RedisTemplate

image

public void changeMoney(Integer id, Double money) {
    redisTemplate.opsForValue().set("account:id:" + id,money);
}
public Double findMondyById(Integer id) {
    Object money = redisTemplate.opsForValue().get("account:id:" + id);
    return new Double(money.toString());
}

JdbcTemplate

提供标准的 SQL 语句操作 API 。

public void save(Account account) {
    String sql = "insert into account(name,money)values(?,?)";
    jdbcTemplate.update(sql,account.getName(),account.getMoney());
}

NamedParameterJdbcTemplate

提供标准的 SQL 语句操作 API 。

public void save(Account account) {
    String sql = "insert into account(name,money)values(:name,:money)";
    Map pm = new HashMap();
    pm.put("name", account.getName());
    pm.put("money", account.getMoney());
    jdbcTemplate.update(sql, pm);
}

Spring 事务底层原理

策略模式(Strategy Pattern):根据不同的策略对象来实现不同的行为方式,策略对象的变化导致行为的变化。

image

示例:装饰模式应用

  • JdbcTemplate
  • NamedParameterJdbcTemplate
posted @ 2021-12-10 11:46  Juno3550  阅读(77)  评论(0编辑  收藏  举报