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

使用Blaze Persistence+EntityViews分页查询返回重复数据的解决方法

问题

在将Blaze Persistence API与Spring Data JPA的Specification API结合使用时,通过EntityViewSpecificationExecutor搭配Pageable查询数据库返回了重复数据。场景是按语言过滤课程别名,一门课程可对应同一语言的多个别名,导致分页查询时出现重复的课程记录。

使用的技术版本:SpringBoot 3.3.3、Blaze Persistence 1.6.12、Hibernate 6。

实体定义

Course实体

@Data
@Entity
@Table(name = "courses")
public class Course implements Serializable {

   @Id
   @Column(name = "course_id")
   private Long id;

   @Column(name = "course_name")
   private String courseName; 

   @OneToMany(mappedBy = "anCourse", cascade = CascadeType.ALL)
    private List<AltName> altNameList;

   // 其他字段和关联关系已省略
}

AltName实体

@Data
@Entity
@Table(name = "alt_names")
public class AltName implements Serializable {

   @Id
   @Column(name = "alt_id")
   private Long id;

   @Column(name = "alt_name")
   private String altName; 

   @Column(name = "alt_language")
   String altNameLanguage; // 同一门课程可拥有同一语言的多个别名

   @ManyToOne
   private Course anCourse;

   // 其他多个字段已省略
}

实体视图定义

SearchResultView

@EntityView(Course.class)
public interface SearchResultView extends CourseView {

    @Mapping(fetch = FetchStrategy.MULTISET)
    public List<AltNameView> getAltNameList();

    // 其他多集合映射已省略
}

CourseView

@EntityView(CourseView.class)
public interface CourseView {

    @IdMapping
    public Long getCourseId();

    public String getCourseName(); 
}

AltNameView

@EntityView(AltName.class)
public interface AltNameView {

    @IdMapping
    public int getId();

    public String getAltName();

    public String getAltNameLanguage();

    // 其他字段已省略
}

查询实现

Repository接口

@Transactional(readOnly = true)
public interface CourseBlazeRepository extends EntityViewRepository<SearchResultView, Long>,
        EntityViewSpecificationExecutor<SearchResultView, Course> {

         Page<SearchResultView> findCourses(Specification<Course> courseSpecification, Pageable pageable);

}

过滤Specification

// 以下方法用于查找零个、一个或多个西班牙语课程别名
 public static Specification<Course> filterForSpanishLanguage(){
        return ((root, criteriaQuery, criteriaBuilder) -> {
            Join<Course, AltName> altNames = root.join("altNameList", JoinType.INNER);

             return criteriaBuilder.equal(altNames.get(AltName_.ALT_NAME_LANGUAGE), "Spanish"); 
        });
    }

注意:使用原生JpaSpecificationExecutor时,可通过criteriaQuery.distinct(true)去重,但在EntityViewSpecificationExecutor中使用该语句会抛出运行时异常“Cannot paginate a DISTINCT query”,因为Blaze生成的查询已包含distinct关键字。


解决方案

1. 改用Blaze Persistence的EntityViewSetting配置去重

修改Repository的查询方法,添加EntityViewSetting参数:

Page<SearchResultView> findCourses(Specification<Course> courseSpecification, 
                                   EntityViewSetting<SearchResultView> setting, 
                                   Pageable pageable);

调用时显式配置去重:

EntityViewSetting<SearchResultView> setting = EntityViewSetting.create(SearchResultView.class)
        .distinct(true);
Page<SearchResultView> result = courseBlazeRepository.findCourses(filterForSpanishLanguage(), setting, pageable);

2. 在Specification中使用EXISTS子查询替代关联查询

通过EXISTS子查询过滤符合条件的课程,避免关联查询导致的结果集膨胀:

public static Specification<Course> filterForSpanishLanguage(){
    return ((root, criteriaQuery, criteriaBuilder) -> {
        Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
        Root<AltName> altNameRoot = subquery.from(AltName.class);
        subquery.select(altNameRoot.get("anCourse").get("id"))
                .where(
                    criteriaBuilder.equal(altNameRoot.get("altNameLanguage"), "Spanish"),
                    criteriaBuilder.equal(altNameRoot.get("anCourse").get("id"), root.get("id"))
                );
        return criteriaBuilder.exists(subquery);
    });
}

这种方式不会产生关联后的重复行,从根源上避免重复数据,同时兼容分页逻辑。

3. 配置Blaze Persistence的分页策略

在application.properties中添加配置,让分页基于根实体ID进行:

blaze.persistence.view.page-strategy=id

该配置会让Blaze先查询去重后的根实体ID列表,再根据ID查询完整视图数据,彻底避免重复分页结果。

内容的提问来源于stack exchange,提问作者stretch-a-lot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 14:02:07