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记录

创建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" } } ]
问题原因及解决方案
原因分析
- Hibernate一级缓存(Session缓存)命中:保存Article时,当前Session中已经存入了仅包含ID的
SupplierDAO实例(由ModelMapper从请求映射而来,仅设置了id字段)。调用findAll()时,Hibernate优先从一级缓存中读取Supplier实例,不会触发数据库查询,因此name字段为null。 - 后续查询使用新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
相关产品推荐
相关产品推荐

