Spring Boot获取产品列表时productImages数组为空的解决方案咨询
问题:获取产品接口返回的productImages为空,不修改实体关联关系的解决办法
问题背景
测试「获取全部产品」接口时,返回结果中的productImages数组始终为空。相关代码如下:
Product实体类
public class Product implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @NotNull @Size(max = 100) @Column(name = "title", length = 100, nullable = false) private String title; @NotNull @DecimalMin(value = "0") @DecimalMax(value = "99999") @Column(name = "day_price", precision = 21, scale = 2, nullable = false) private BigDecimal dayPrice; @Lob @Column(name = "description", nullable = false) private String description; @NotNull @Column(name = "position", nullable = false) private String position; @Min(value = 0L) @Max(value = 100L) @Column(name = "week_discount") private Long weekDiscount; @Min(value = 0L) @Max(value = 100L) @Column(name = "month_discount") private Long monthDiscount; @Min(value = 0L) @Column(name = "refundable_deposit") private Long refundableDeposit; @Column(name = "enabled") private Boolean enabled; @Column(name = "deleted") private Boolean deleted; @Column(name = "created_at") private Instant createdAt; @Column(name = "updated_at") private Instant updatedAt; @Column(name = "deleted_at") private LocalDate deletedAt; @Enumerated(EnumType.STRING) @Column(name = "currency") private Currency currency; @OneToMany(mappedBy = "product") @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @org.springframework.data.annotation.Transient @JsonIgnoreProperties(value = { "productBooking", "user", "invoice", "product" }, allowSetters = true) private Set<Booking> bookings = new HashSet<>(); @OneToMany(mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.ALL) @Fetch(value = FetchMode.SELECT) // don't remove this line @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) @org.springframework.data.annotation.Transient @JsonIgnoreProperties(value = { "product" }, allowSetters = true) @BatchSize(size = 20) private Set<ProductImages> productImages = new HashSet<>(); @OneToMany(mappedBy = "product") @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @org.springframework.data.annotation.Transient @JsonIgnoreProperties(value = { "user", "product" }, allowSetters = true) private Set<ProductReview> productReviews = new HashSet<>(); @ManyToOne(optional = false) @NotNull private User owner; @ManyToOne(optional = false) @NotNull @JsonIgnoreProperties(value = { "products", "productBookings", "productCategories", "parentId" }, allowSetters = true) private ProductCategory productCategory; }
ProductImages实体类
public class ProductImages implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Lob @Column(name = "image", nullable = false) private byte[] image; @NotNull @Column(name = "image_content_type", nullable = false) private String imageContentType; @Column(name = "description") private String description; @ManyToOne(optional = false, fetch = FetchType.EAGER) @JoinColumn(name = "product_id", referencedColumnName = "id", nullable = false) @NotNull @JsonIgnoreProperties(value = { "bookings", "productImages", "productReviews", "owner", "productCategory" }, allowSetters = true) private Product product; }
Service层代码
@Transactional(readOnly = true) public Page<Product> findAll(Pageable pageable) { return productRepository.findAll(pageable); }
需在不修改实体间关联关系的前提下解决productImages为空的问题。
可行解决方案
1. 自定义Repository查询,主动关联查询ProductImages
在ProductRepository中添加带关联查询的自定义方法,通过JPQL主动加载productImages:
public interface ProductRepository extends JpaRepository<Product, Long> { @Query("SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.productImages WHERE p.deleted = false") @QueryHints(value = @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false")) Page<Product> findAllWithProductImages(Pageable pageable); }
然后修改Service层调用该方法:
@Transactional(readOnly = true) public Page<Product> findAll(Pageable pageable) { return productRepository.findAllWithProductImages(pageable); }
注:添加
DISTINCT和HINT_PASS_DISTINCT_THROUGH是为了解决分页时的重复结果问题。
2. 使用EntityGraph指定加载关联属性
利用JPA的EntityGraph显式指定查询时加载productImages,无需修改实体FetchType:
public interface ProductRepository extends JpaRepository<Product, Long> { @EntityGraph(attributePaths = {"productImages"}) Page<Product> findAll(Pageable pageable); }
Spring Data JPA会自动生成关联查询SQL,主动加载productImages集合,Service层代码无需改动。
3. Service层手动初始化懒加载集合
在事务范围内,用Hibernate的initialize()方法手动触发懒加载集合的加载:
@Transactional(readOnly = true) public Page<Product> findAll(Pageable pageable) { Page<Product> productPage = productRepository.findAll(pageable); productPage.getContent().forEach(product -> { Hibernate.initialize(product.getProductImages()); }); return productPage; }
注:该方式会触发N+1查询,但你已在实体中配置
@BatchSize(size = 20),可有效减少查询次数。
内容的提问来源于stack exchange,提问作者Charfeddine Mohamed Ali
相关产品推荐
相关产品推荐

