解决SpringBoot使用@Transactional进行RestTemplate远程调用导致查询数据记录为null的bug

开启事务过程中,如果远程调用查询当前已经开启但没有提交的事务,就会查不到数据。

示例代码

java 复制代码
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Objects;

@Slf4j
@RestController
@RequestMapping("simple")
@RequiredArgsConstructor
public class SimpleController {
    private final SimpleObjectMapper simpleObjectMapper;
    private final RestTemplate restTemplate;

    /**
     * 开启事务过程中,如果远程调用查询当前已经开启但没有提交的事务,就会查不到数据。
     */
    @GetMapping("insert")
    @Transactional(rollbackFor = Exception.class)//1.开启事务
    public void insert() {
        SimpleObject simpleObject = new SimpleObject();
        simpleObject.setId(2);
        simpleObject.setName("name" + System.currentTimeMillis());
        simpleObjectMapper.insert(simpleObject);//2.因为开启了事务,所以这里的 insert 并没有 commit,导致下面远程调用查询数据是空的。
        SimpleObject simpleObject1 = restTemplate.getForEntity("http://localhost:8080/simple/2", SimpleObject.class, simpleObject.getId()).getBody();
        log.info("simpleObject1 (这里会输出null):{}",simpleObject1);//3.这里会输出null,因为事务没有提交,数据库不会新增数据
        if (Objects.isNull(simpleObject1)) {
            throw new RuntimeException("simpleObject1 is null");
        }
    }

    /**
     * 被远程调用的查询方法
     */
    @GetMapping("{id}")
    public SimpleObject selectById(@PathVariable Integer id) {
        return simpleObjectMapper.selectById(id);
    }

}

抛出异常

shell 复制代码
2024-05-23 22:49:44.033  INFO 8668 --- [nio-8080-exec-1] MyBatisSqlParsingPlugin     : Execute SQL:
INSERT INTO simple_object  ( id,name )  VALUES  ( 2,'name1716475783993' )
2024-05-23 22:49:44.119  INFO 8668 --- [nio-8080-exec-2] MyBatisSqlParsingPlugin     : Execute SQL:
SELECT id,name FROM simple_object WHERE id=2 
2024-05-23 22:49:44.149  INFO 8668 --- [nio-8080-exec-1] SimpleController        : simpleObject1 (这里会输出null):null
2024-05-23 22:49:44.161 ERROR 8668 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : 
java.lang.RuntimeException: simpleObject1 is null

解决办法

1、去掉@Transactional注解(显然不可能,除非不影响正常的业务)

2、手动控制事务,但是有些情况下可能不适用,不适用的情况可以使用分布式事务,如:seata等(推荐)

手动控制事务

示例代码

java 复制代码
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Objects;

@Slf4j
@RestController
@RequestMapping("simple")
@RequiredArgsConstructor
public class SimpleController {
    private final SimpleObjectMapper simpleObjectMapper;
    private final RestTemplate restTemplate;
    private final PlatformTransactionManager platformTransactionManager;//事务管理器
    private final TransactionDefinition transactionDefinition;// 事务的一些基础信息,如超时时间、隔离级别、传播属性等

    /**
     * 使用手动事务,远程调用可以查询到数据。
     */
    @GetMapping("insert2")
    public void insert2() {
        //手动事务不能加@Transactional注解,否则优先使用@Transactional注解的事务
        TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);//1、手动获取事务
        SimpleObject simpleObject = new SimpleObject();
        try {
            simpleObject.setId(2);
            simpleObject.setName("name" + System.currentTimeMillis());
            simpleObjectMapper.insert(simpleObject);
            platformTransactionManager.commit(transaction);//2.手动提交事务
        } catch (Exception e) {
            platformTransactionManager.rollback(transaction);//4.发生异常手动回滚事务
            log.error("insert2异常:", e);
            throw new RuntimeException( "insert2异常:" + e.getMessage());
        }
        SimpleObject simpleObject1 = restTemplate.getForEntity("http://localhost:8080/simple/2", SimpleObject.class, simpleObject.getId()).getBody();
        log.info("simpleObject1 (这里会输出simpleObject1):{}",simpleObject1);//3.这里会输出id = 2的 SimpleObject 对象,因为事务提交,数据库会新增数据
        if (Objects.isNull(simpleObject1)) {
            throw new RuntimeException("simpleObject1 is null");
        }
    }

    /**
     * 被远程调用的查询方法
     */
    @GetMapping("{id}")
    public SimpleObject selectById(@PathVariable Integer id) {
        return simpleObjectMapper.selectById(id);
    }

}

不抛出异常,可以正常获取数据

shell 复制代码
2024-05-23 23:02:00.506  INFO 10608 --- [nio-8080-exec-1] c.f.m.plugin.MyBatisSqlParsingPlugin     : Execute SQL:
INSERT INTO simple_object  ( id,
name )  VALUES  ( 2,
'name1716476520466' )
2024-05-23 23:02:00.592  INFO 10608 --- [nio-8080-exec-2] c.f.m.plugin.MyBatisSqlParsingPlugin     : Execute SQL:
SELECT id,name FROM simple_object WHERE id=2 
2024-05-23 23:02:00.641  INFO 10608 --- [nio-8080-exec-1] c.f.m.controller.SimpleController        : simpleObject1 (这里会输出simpleObject1):SimpleObject(id=2, name=name1716476520466)

不使用事务(不推荐)

示例代码

java 复制代码
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Objects;

@Slf4j
@RestController
@RequestMapping("simple")
@RequiredArgsConstructor
public class SimpleController {
    private final SimpleObjectMapper simpleObjectMapper;
    private final RestTemplate restTemplate;

    /**
     * 不使用事务,远程调用可以查询到数据。(不推荐)
     */
    @GetMapping("insert3")
    public void insert3() {
        SimpleObject simpleObject = new SimpleObject();
        simpleObject.setId(2);
        simpleObject.setName("name" + System.currentTimeMillis());
        simpleObjectMapper.insert(simpleObject);//1、因为没有事务,所以这里会新增数据到数据库
        SimpleObject simpleObject1 = restTemplate.getForEntity("http://localhost:8080/simple/2", SimpleObject.class, simpleObject.getId()).getBody();
        log.info("simpleObject1 (这里会输出simpleObject1):{}",simpleObject1);//2.这里会输出id = 2的 SimpleObject 对象,因为事务提交,数据库会新增数据
        if (Objects.isNull(simpleObject1)) {
            throw new RuntimeException("simpleObject1 is null");
        }
    }

    /**
     * 被远程调用的查询方法
     */
    @GetMapping("{id}")
    public SimpleObject selectById(@PathVariable Integer id) {
        return simpleObjectMapper.selectById(id);
    }

}

不抛出异常,可以正常获取数据

shell 复制代码
2024-05-23 23:04:31.889  INFO 10608 --- [nio-8080-exec-6] c.f.m.plugin.MyBatisSqlParsingPlugin     : Execute SQL:
INSERT INTO simple_object  ( id,
name )  VALUES  ( 2,
'name1716476671888' )
2024-05-23 23:04:31.895  INFO 10608 --- [nio-8080-exec-7] c.f.m.plugin.MyBatisSqlParsingPlugin     : Execute SQL:
SELECT id,name FROM simple_object WHERE id=2 
2024-05-23 23:04:31.897  INFO 10608 --- [nio-8080-exec-6] c.f.m.controller.SimpleController        : simpleObject1 (这里会输出simpleObject1):SimpleObject(id=2, name=name1716476671888)
相关推荐
用户685453759776914 小时前
同步成本换并行度:多线程、协程、分片、MapReduce 怎么选才不踩坑
后端
javaTodo14 小时前
Claude Code 记忆机制详解:从 CLAUDE.md 到 Auto Memory,六层体系全拆解
后端
LSTM9714 小时前
使用 C# 和 Spire.PDF 从 HTML 模板生成 PDF 的实用指南
后端
JaguarJack14 小时前
为什么 PHP 闭包要加 static?
后端·php·服务端
BingoGo15 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊15 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说15 小时前
基于Spark的配置化离线反作弊系统
后端
Java编程爱好者15 小时前
虚拟线程深度解析:轻量并发编程的未来趋势
后端
苏三说技术16 小时前
Spring AI 和 LangChain4j ,哪个更好?
后端