【棘手问题】Spring JPA一级缓存导致获取不到数据库表中的最新数据,对象地址不发生改变

【棘手问题】Spring JPA一级缓存导致获取不到数据库表中的最新数据,对象地址不发生改变

  • 一、问题背景
  • 二、解决步骤
    • [2.1 debug](#2.1 debug)
    • [2.2 原因分析](#2.2 原因分析)
      • [2.2.1 数据步骤](#2.2.1 数据步骤)
      • [2.2.2 大模型解释](#2.2.2 大模型解释)
      • [2.2.3 解释举例](#2.2.3 解释举例)
      • [2.2.4 关键函数](#2.2.4 关键函数)
    • [2.3 解决方案](#2.3 解决方案)
  • [三、Spring JPA一级缓存](#三、Spring JPA一级缓存)

一、问题背景

项目的数据可以通过前端表单进行增删改查,亦可以通过Excel表格上传的方式进行项目数据增改。两种方式对数据库数据进行操作。

项目可以通过字段 项目ID项目标识字段均能够查找到唯一项目记录。

问题发生于:

修改项目的负责人数据时,与其关联的资产数据的负责人要同步跟随更新。就在判断来自Excel表中的项目负责人数据库表中的负责人是否一致时,出现了异常,不一致的分支条件始终没进入。

二、解决步骤

2.1 debug

这种问题,必须debug看其所调用的函数内部对象数据的变化。

首先看一段代码,核心函数是createOrUpdateAssetProjectByExcel(assetProject),功能顾名思义。

java 复制代码
 public ResponseEntity<Object> importExcel(List<AssetProjectDto> assetProjectDtoList, boolean updateSupport) {

        if (CollectionUtils.isEmpty(assetProjectDtoList)){
            throw new BadRequestException("导入的表格为空");
        }

        try {

            assetProjectDtoList.stream()
//                    .filter(assetProjectDto -> ObjectUtil.isNotNull(assetProjectDto.getProjectNumber()))
                    .filter(assetProjectDto -> ObjectUtil.isNotNull(assetProjectDto.getUserId()))
                    .map(assetProjectDto -> BeanUtil.toBeanIgnoreError( assetProjectDto, AssetProject.class))
                    .forEach((assetProject -> {
                        try{

                            createOrUpdateAssetProjectByExcel(assetProject);

                        }catch (Exception e){
                            log.error("项目创建或更新失败,原因是[{}]",ThrowableUtil.getMessage(e));
                        }
                    }));

        }catch (Exception e){
            throw new BadRequestException("导入失败,原因是:" + e.getMessage());
        }

        return new ResponseEntity<>("导入成功", HttpStatus.OK);
    }

接着看一下该函数,通过判断项目是否是新项目对该条数据进行更新或者创建的操作。

进入到更新函数中,就出现了resourceassetProject 对象始终是相等的问题。

2.2 原因分析

2.2.1 数据步骤

排除了事务、属性复制的原因,最后发现问题出现在resourceassetProject对象都是从数据库中查询出来的,而且经过属性拷贝。

  1. resource对象通过项目标识查询出来;
  2. excel 数据对象的属性复制给resource对象;
  3. assetProject对象通过项目ID查询出赖;
  4. assetProject 对象和resource对象一致。

在第3步中,查询出的项目对象走了Spring JPA一级缓存导致,第4步的结果。

2.2.2 大模型解释

在 JPA 中,findByProjectItem 函数是一个自定义的查询方法,它根据实体的某个属性(在这个例子中是 projectItem)来检索实体。这个方法的名字是任意的,但它后面的参数定义了查询的条件。
findById 是一个标准的方法,它根据实体的主键(ID)来检索实体。这是一个 JPA 定义的通用查询方法,它总是根据 ID 来查询。

如果 findByProjectItem 查询返回的实体 ID 和 findById 查询返回的实体 ID 相同,这意味着根据 projectItem 属性的查询条件找到了一个实体,这个实体的 ID 恰好也是 findById 查询所指定的 ID。

在这种情况下,由于 JPA 的唯一性保证,这两个查询将会返回同一个实体实例。这是因为 JPA 确保了基于主键的查询总是返回唯一的实体实例,而且主键是实体的唯一标识符。因此,如果两个查询都指向同一个实体 ID,那么它们将会返回内存中地址相同的那个实体对象。

这里有一个重要的点:

即使两个查询的条件不同,如果它们都返回同一个实体 ID,那么 JPA 会认为它们是相同的实体,并且返回同一个对象实例。这是 JPA 规范的行为,也是 Hibernate(JPA 的一个实现)的行为。

总结来说,如果 findByProjectItem 查询和 findById 查询返回相同的实体 ID,那么它们将会返回同一个对象实例。这是因为 JPA 规范要求实体 ID 是唯一的,并且基于 ID 的查询总是返回唯一的实体。

在 JPA 中,实体的唯一性是通过实体类的 @Id 注解定义的主键来实现的。每个实体类都必须有一个或多个 @Id 注解的属性,这些属性共同构成了实体的主键。主键的值是唯一的,JPA 使用这个值来识别和检索实体。

在 Hibernate(JPA 的一个流行实现)中,这种唯一性是通过 EntityManagerfind 方法和 getReference 方法来保证的。当您使用这些方法根据主键查询实体时,Hibernate 会检查主键是否已经存在于当前的持久化上下文中。如果主键存在,它将返回已经存在的实体;如果不存在,它将创建一个新的实体实例并将其持久化。

2.2.3 解释举例

以下是一个简单的示例,在 JPA 实体类中定义主键:

java 复制代码
@Entity
public class AssetProject {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // 其他属性
    public Long getId() {
        return id;
    }
    // 构造器、getter 和 setter
}

在这个例子中,id 属性被定义为实体的主键,并且使用了 @GeneratedValue 注解来指定主键值的生成策略。通常,当您使用 JPA 提供的 EntityManager 进行持久化操作时,JPA 会处理实体的唯一性检查。

在 Spring Data JPA 中,通常不需要直接处理这些底层细节。Spring Data JPA 会为提供简化的接口和方法,如 findById,它会隐式地处理实体的唯一性检查。当调用 findById 方法并提供一个 ID 时,Spring Data JPA 会确保返回的实体是唯一的,并且与提供的 ID 相匹配。

如果您想深入了解 JPA 和 Hibernate 是如何实现这种唯一性的,您可以查看它们的源代码。JPA 的核心规范可以在 EclipseLink、Hibernate、TopLink 等实现中找到。这些实现中的 EntityManager 类和相关的持久化方法负责处理实体的唯一性检查和检索。

2.2.4 关键函数

2.3 解决方案

在查询前,清除缓存。

三、Spring JPA一级缓存

相关推荐
数据智能老司机31 分钟前
CockroachDB权威指南——SQL调优
数据库·分布式·架构
数据智能老司机33 分钟前
CockroachDB权威指南——应用设计与实现
数据库·分布式·架构
数据智能老司机1 小时前
CockroachDB权威指南——CockroachDB 模式设计
数据库·分布式·架构
数据智能老司机19 小时前
CockroachDB权威指南——CockroachDB SQL
数据库·分布式·架构
数据智能老司机20 小时前
CockroachDB权威指南——开始使用
数据库·分布式·架构
松果猿20 小时前
空间数据库学习(二)—— PostgreSQL数据库的备份转储和导入恢复
数据库
无名之逆20 小时前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
s91236010120 小时前
rust 同时处理多个异步任务
java·数据库·rust
数据智能老司机21 小时前
CockroachDB权威指南——CockroachDB 架构
数据库·分布式·架构
hzulwy21 小时前
Redis常用的数据结构及其使用场景
数据库·redis