首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >0108 spring的申明式事务

0108 spring的申明式事务

作者头像
李福春
发布于 2025-07-01 16:10:59
发布于 2025-07-01 16:10:59
3230
举报

背景

互联网的金融和电商行业,最关注数据库事务。

业务核心

说明

金融行业-金融产品金额

不允许发生错误

电商行业-商品交易金额,商品库存

不允许发生错误

面临的难点:

高并发下保证:数据一致性,高性能;

spring对事物的处理:

采用AOP技术提供事务支持,申明式事务,去除了代码中重复的try-catch-finally代码;

两个场景的解决方案:

场景

解决办法

库存扣减,交易记录,账户金额的数据一致性

数据库事务保证一致性

批量处理部分任务失败不影响批量任务的回滚

数据库事务传播行为

jdbc处理事务

代码

代码语言:javascript
复制
package com.springbootpractice.demo.demo_jdbc_tx.biz;
import lombok.SneakyThrows;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Optional;
/**
 * 说明:代码方式事务编程 VS 申明式事务编程
 * @author carter
 * 创建时间:2020年01月08日 11:02 上午
 **/
@Service
public class TxJdbcBiz {
    private final JdbcTemplate jdbcTemplate;

    public TxJdbcBiz(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @SneakyThrows
    public int insertUserLogin(String username, String note) {
        Connection connection = null;
        int result = ;
        try {
            connection = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection();

            connection.setAutoCommit(false);

            connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

            final PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO user_login(user_name,password,sex,note)  VALUES(?,?,?,?)");

            preparedStatement.setString(, username);
            preparedStatement.setString(, "abc123");
            preparedStatement.setInt(, );
            preparedStatement.setString(, note);

            result = preparedStatement.executeUpdate();

            connection.commit();
        } catch (Exception e) {
            Optional.ofNullable(connection)
                    .ifPresent(item -> {
                        try {
                            item.rollback();
                        } catch (SQLException ex) {
                            ex.printStackTrace();
                        }
                    });
            e.printStackTrace();
        } finally {
            Optional.ofNullable(connection)
                    .filter(this::closeConnection)
                    .ifPresent(item -> {
                        try {
                            item.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    });
        }
        return result;
    }
    private boolean closeConnection(Connection item) {
        try {
            return !item.isClosed();
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
    }
    @Transactional
    public int insertUserLoginTransaction(String username, String note) {
        String sql = "INSERT INTO user_login(user_name,password,sex,note)  VALUES(?,?,?,?)";
        Object[] params = {username, "abc123", , note};
        return jdbcTemplate.update(sql, params);
    }
}

测试代码

代码语言:javascript
复制
package com.springbootpractice.demo.demo_jdbc_tx;
import com.springbootpractice.demo.demo_jdbc_tx.biz.TxJdbcBiz;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.TransactionManager;
import org.springframework.util.Assert;
@SpringBootTest
class DemoJdbcTxApplicationTests {
    @Autowired
    private TxJdbcBiz txJdbcBiz;
    @Autowired
    private TransactionManager transactionManager;

    @Test
    void testInsertUserTest() {
        final int result = txJdbcBiz.insertUserLogin("monika.smith", "xxxx");
        Assert.isTrue(result > , "插入失败");
    }

    @Test
    void insertUserLoginTransactionTest() {
        final int result = txJdbcBiz.insertUserLoginTransaction("stefan.li", "hello transaction");
        Assert.isTrue(result > , "插入失败");
    }

    @Test
    void transactionManagerTest() {
        System.out.println(transactionManager.getClass().getName());
    }
}

代码中有一个很讨厌的地方,就是 try-catch-finally;

流程图

整体流程跟AOP的流程非常的相似,使用AOP,可以把执行sql的步骤抽取出来单独实现,其它的固定流程放到通知里去做。

jdbc使用事物编程代码点我!

申明式事务

通过注解@Transaction来标注申明式事务,可以标准在类或者方法上;

@Tranaction使用位置

说明

类上或者接口上

类中所有的 公共非静态方法 都将启用事务,spring推荐放在实现类上,否则aop必须基于接口的代理生效的时候才能生效

方法上

本方法

@Transaction的源码和配置项

代码语言:javascript
复制
package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

说明:

属性

说明

isolation

事务的隔离级别

propagation

传播行为

rollbackFor,rollbakcForClassName

哪种异常会触发事务回滚

value

事务管理器

timeout

事务超时时间

readOnly

是否是只读事务

noRollbackFor,noRollbackForClassName

哪些异常不会触发事务回滚

事务的安装过程:

springIOC容器启动的时候,会把@Transactional注解的配置信息解析出来,然后存到事务定义器(TransactionDefinition),并记录哪些类的方法需要启动事务,采取什么策略去执行事务。 我们要做的只是标注@Transactional和配置属性即可;

流程如图:

图片
图片

使用方式大大简化;

代码

代码语言:javascript
复制
 @Transactional
    public int insertUserLoginTransaction(String username, String note) {
        String sql = "INSERT INTO user_login(user_name,password,sex,note)  VALUES(?,?,?,?)";
        Object[] params = {username, "abc123", , note};
        return jdbcTemplate.update(sql, params);
    }

事务管理器

事务的打开,提交,回滚都是放在事务管理器上的。TransactionManager;

TransactionManager代码

代码语言:javascript
复制

package org.springframework.transaction;

public interface TransactionManager {
}

这是一个空接口,实际起作用的是PlatfromTransactionManager;

PlatfromTransactionManager代码:

代码语言:javascript
复制
package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;

    void commit(TransactionStatus var1) throws TransactionException;

    void rollback(TransactionStatus var1) throws TransactionException;
}

3个架子的事务管理器对比:

架子

事务管理

说明

spring-jdbc

DatasourceTransactionManager

jpa

JpaTransactionManager

mybatis

DatasourceTransactionManager

mybatis的代码实例点我!

事务隔离级别

场景:电商行业的库存扣减,时刻都是多线程的环境中扣减库存,对于数据库而言,就会出现多个事务同事访问同一记录,这样引起的数据不一致的情况,就是数据库丢失更新。

数据库事务4个特性

即ACID

事务的特性

英文全称

说明

原子性

Atomic

一个事务中包含多个步骤操作A,B,C,原子性是标识这些操作要目全部成功,要么全部失败,不会出现第三种情况

一致性

Consistency

在事务完成后,所有的数据都保持一致状态

隔离性

Isolation

多个线程同时访问同一数据,每个线程处在不同的事务中,为了压制丢失更新的产生,定了隔离级别,通过隔离性的设置,可以压制丢失更新的发生,这里存在一个选择的过程

持久性

Durability

事务结束后,数据都会持久化,断电重启后也是可以提供给程序继续使用

隔离级别:

隔离级别

说明

问题

并发性能

读未提交【read uncommitted】

允许事务读取另外一个事务没有提交的数据,事务要求比较高的情况下不适用,适用于对事务要求不高的场景

脏读(单条)

并发性能最高

读已提交【read committed】

一个事务只能读取另外一个事务已经提交的数据

不可重复读(单条)

并发性能一般

可重复读【read repeated】

事务提交的时候也会判断最新的值是否变化

幻想读(多条数据而言)

并发性能比较差

串行化【serializable】

所有的sql都按照顺序执行

数据完全一致

并发性能最差

选择依据

隔离级别

脏读

不可重复读

幻象读

读未提交

是

是

是

读已提交

否

是

是

可重复读

否

否

是

串行化

否

否

否

按照实际场景的允许情况来设置事务的隔离级别; 隔离级别会带来锁的代价;优化方法:

  1. 乐观锁,
  2. redis分布式锁,
  3. zk分布式锁;

数据库

事务隔离级别

默认事务隔离级别

mysql

4种

可重复读

oracle

读已提交,串行化

读已提交

springboot配置应用默认的事务隔离级别:spring.datasource.xxx.default-transaction-isolation=2

数字

对应隔离级别

-1

无

1

读未提交

2

读已提交

4

可重复读

8

串行化

事务传播行为

传播行为是方法之间调用事务采取的策略问题。场景:一个批量任务处在一个事务A中,每个单独是事务都有一个独立的事务Bn; 子任务的回滚不影响事务A的回滚;

传播行为源码

代码语言:javascript
复制
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
    NEVER(TransactionDefinition.PROPAGATION_NEVER),
    NESTED(TransactionDefinition.PROPAGATION_NESTED);
    private final int value;
    Propagation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }
}

列举了7种传播配置属性,下面分别说明:

传播行为

父方法中存在事务子方法行为

父方法中不存在事务子方法行为

REQUIRED

默认传播行为,沿用,

创建新的事务

SUPPORTS

沿用;

无事务,子方法中也无事务

MANDATORY

沿用

抛出异常

REQUIRES_NEW

创建新事务

创建新事务

NOT_SUPPORTED

挂起事务,运行子方法

无事务,运行子方法

NEVER

抛异常

无事务执行子方法

NESTED

子方法发生异常,只回滚子方法的sql,而不回滚父方法中的事务

发生异常,只回滚子方法的sql,跟父方法无关

常用的三种传播行为:

  • REQUIRED
  • REQUIRES_NEW
  • NESTED

代码测试这三种传播行为:

代码点我!

spring使用了save point的技术来让子事务回滚,而父事务不会滚;如果不支持save point,则新建一个事务来运行子事务;

区别点

RequestNew

Nested

传递

拥有自己的锁和隔离级别

沿用父事务的隔离级别和锁

@Transaction自调用失效问题

事务的实现原理是基于AOP,同一个类中方法的互相调用,是自己调用自己,而没有代理对象的产生,就不会用到aop,所以,事务会失效; 解决办法:通过spring的ioc容器得到当前类的代理对象,调用本类的方法解决;

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-08,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 背景
  • jdbc处理事务
  • 申明式事务
    • 事务管理器
    • 事务隔离级别
    • 事务传播行为
    • @Transaction自调用失效问题
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档