You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data保存Article后关联Supplier的name无法即时获取问题

问题描述

我有一个简单的Spring应用,包含ArticleDAO和SupplierDAO,二者为@ManyToOne关联(多个Article可指向同一Supplier)。保存Article后立即查询时,仅能获取Supplier的ID,其name字段为null,但后续通过其他方法查询时能获取完整Supplier信息。数据库中该Supplier已存在且name字段有值。相关代码及请求响应如下:

实体类代码

@Getter
@Setter
@NoArgsConstructor
@Entity
@EqualsAndHashCode
@Table(name = "article")
public class ArticleDAO {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @Column(name = "name", unique = true)
    private String name;

    @Column(name = "selling_price")
    private BigDecimal sellingPrice;

    @Column(name = "supplier_price")
    private BigDecimal supplierPrice;

    @Column(name = "user")
    private String user;

    @Column(name = "selling_unit_type")
    @Enumerated(EnumType.STRING)
    private ArticleSellingUnitType sellingUnitType;

    @Column(name = "inventory_amount")
    private Integer inventoryAmount;

    @Column(name = "picture")
    private String picture;

    @Column(name = "color")
    private String color;

    @Column(name = "reference")
    private String reference;

    @Column(name = "bar_code")
    private String barCode;

    @ManyToOne
    @JoinColumn(name = "supplier_id", referencedColumnName = "id")
    private SupplierDAO supplier;

    @Getter
    @Setter
    @NoArgsConstructor
    @Entity
    @EqualsAndHashCode
    @Table(name = "supplier")
    public static class SupplierDAO {
        @Id
        @Column(name = "id")
        private Integer id;

        @Column(name = "name")
        private String name;
    }
}

服务类代码

public class ArticlesServiceImpl implements ArticlesService {
    private final ArticleRepository articleRepository;
    private final ModelMapper modelMapper;

    @Override
    public List<Article> createArticles(final List<Article> articles) {
        List<ArticleDAO> articleDAOS = this.modelMapper.map(articles, new TypeToken<List<ArticleDAO>>(){}.getType());
        this.articleRepository.saveAll(articleDAOS);
        articleDAOS = this.articleRepository.findAll();
        return this.modelMapper.map(articleDAOS, new TypeToken<List<Article>>(){}.getType());
    }
}

Repository代码

@Repository
public interface ArticleRepository extends JpaRepository<ArticleDAO, Integer> {
}

数据库中的Supplier记录

数据库中的Supplier记录

创建Article的请求

[
    {
        "name": "Botellín Mahou Clasica 23334333",
        "sellingPrice": 1.50,
        "supplierPrice": 0.44,
        "sellingUnitType": "WEIGHT",
        "inventoryAmount": 100,
        "picture": "picture.png",
        "color": "blue",
        "barCode": "123123123123",
        "reference": "800",
        "supplier": {
            "id": 6
        }
    }
]

保存后立即查询的响应(Supplier的name缺失)

[
    {
        "name": "Botellín Mahou Clasica 23334333",
        "sellingPrice": 1.50,
        "supplierPrice": 0.44,
        "sellingUnitType": "WEIGHT",
        "inventoryAmount": 100,
        "picture": "picture.png",
        "color": "blue",
        "barCode": "123123123123",
        "reference": "800",
        "supplier": {
            "id": 6,
            "name": null
        }
    }
]

后续查询的响应(Supplier的name正常)

[
    {
        "name": "Botellín Mahou Clasica 23334333",
        "sellingPrice": 1.50,
        "supplierPrice": 0.44,
        "sellingUnitType": "WEIGHT",
        "inventoryAmount": 100,
        "picture": "picture.png",
        "color": "blue",
        "barCode": "123123123123",
        "reference": "800",
        "supplier": {
            "id": 6,
            "name": "VFGBO"
        }
    }
]

问题原因及解决方案

原因分析

  1. Hibernate一级缓存(Session缓存)命中:保存Article时,当前Session中已经存入了仅包含ID的SupplierDAO实例(由ModelMapper从请求映射而来,仅设置了id字段)。调用findAll()时,Hibernate优先从一级缓存中读取Supplier实例,不会触发数据库查询,因此name字段为null。
  2. 后续查询使用新Session:后续查询会开启新的Session,缓存中没有之前的不完整Supplier实例,Hibernate会从数据库查询完整数据,因此能拿到name字段的值。

解决方案

方案1:清空当前Session缓存

注入EntityManager,保存后清空缓存,迫使Hibernate从数据库重新查询数据:

// 注入EntityManager
private final EntityManager entityManager;

@Override
public List<Article> createArticles(final List<Article> articles) {
    List<ArticleDAO> articleDAOS = this.modelMapper.map(articles, new TypeToken<List<ArticleDAO>>(){}.getType());
    this.articleRepository.saveAll(articleDAOS);
    // 清空Session缓存
    entityManager.clear();
    articleDAOS = this.articleRepository.findAll();
    return this.modelMapper.map(articleDAOS, new TypeToken<List<Article>>(){}.getType());
}

方案2:修改关联为即时加载

在@ManyToOne注解中设置fetch = FetchType.EAGER,查询Article时会立即关联查询完整的Supplier数据:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "supplier_id", referencedColumnName = "id")
private SupplierDAO supplier;

注意:即时加载可能在数据量大时引发性能问题,需根据业务场景评估使用。

方案3:提前查询完整的Supplier实例

保存Article前,先通过ID从数据库获取完整的Supplier实例,替换Article中的Supplier对象:

// 注入SupplierRepository
private final SupplierRepository supplierRepository;

@Override
public List<Article> createArticles(final List<Article> articles) {
    List<ArticleDAO> articleDAOS = this.modelMapper.map(articles, new TypeToken<List<ArticleDAO>>(){}.getType());
    // 替换为数据库中的完整Supplier实例
    articleDAOS.forEach(article -> {
        if (article.getSupplier() != null && article.getSupplier().getId() != null) {
            SupplierDAO fullSupplier = supplierRepository.findById(article.getSupplier().getId()).orElse(null);
            article.setSupplier(fullSupplier);
        }
    });
    this.articleRepository.saveAll(articleDAOS);
    articleDAOS = this.articleRepository.findAll();
    return this.modelMapper.map(articleDAOS, new TypeToken<List<Article>>(){}.getType());
}

方案4:使用JPQL强制关联查询

在Repository中定义自定义查询,显式关联Supplier:

@Repository
public interface ArticleRepository extends JpaRepository<ArticleDAO, Integer> {
    @Query("SELECT a FROM ArticleDAO a JOIN FETCH a.supplier")
    List<ArticleDAO> findAllWithSupplier();
}

服务方法中调用该方法替代findAll():

articleDAOS = this.articleRepository.findAllWithSupplier();

内容的提问来源于stack exchange,提问作者Ulises CT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 21:54:56