【问题标题】:Hibernate - Limit size of nested collectionHibernate - 限制嵌套集合的大小
【发布时间】:2020-08-25 21:41:00
【问题描述】:

我有以下型号:

@Entity
@Data
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String author;
    private String title;
    @OneToMany(cascade = CascadeType.MERGE)
    private List<Comment> comments;
}

@Entity
@Data
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String body;
    private LocalDateTime timestamp;
}

我正在努力实现的是获取所有带有分页的书籍,但所有书籍都应该将其 cmets 收藏限制为 5 个最新条目。我想出了以下本机查询(mssql):

select *
from book b
         left join book_comments bc on bc.book_id = b.id and bc.comments_id in (
    select top 5 c2.id
    from comment c2
             join book_comments bc2 on c2.id = bc2.comments_id
             join book b2 on bc2.book_id = b2.id
    where bc2.book_id = b.id
    order by c2.timestamp desc
)
         left join comment c
                   on bc.comments_id = c.id

当我在控制台中运行此查询时,它会返回正确的结果集,但当它由应用程序运行时,如下所示:

public interface BookRepository extends JpaRepository<Book, Long> {
    @Query(value = "select * " +
            "from book b " +
            "         left join book_comments bc on bc.book_id = b.id and bc.comments_id in ( " +
            "    select top 5 c2.id " +
            "    from comment c2 " +
            "             join book_comments bc2 on c2.id = bc2.comments_id " +
            "             join book b2 on bc2.book_id = b2.id " +
            "    where bc2.book_id = b.id " +
            "    order by c2.timestamp desc " +
            ") " +
            "         left join comment c " +
            "                   on bc.comments_id = c.id",
            nativeQuery = true)
    Page<Book> findAll(Pageable pageable);
}

抛出语法错误:

    "localizedMessage": "Incorrect syntax near 'id'.",
    "message": "Incorrect syntax near 'id'.",
    "suppressed": []
},
"localizedMessage": "could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet",

我观察到执行计数查询时会出现此语法错误。我还尝试提供 countQuery 属性(相同的查询,但不是 * 有 count(*))。这样我没有语法错误,但它返回不正确的结果集 - book 被重复 N 次,其中 N 是 cmets 集合的大小。

我该如何解决?这个案子有没有更好的办法?

编辑:

计数查询:

select count(*)
from book b
         left join book_comments bc on bc.book_id = b.id and bc.comments_id in (
    select top 5 c2.id
    from comment c2
             join book_comments bc2 on c2.id = bc2.comments_id
             join book b2 on bc2.book_id = b2.id
    where bc2.book_id = b.id
    order by c2.timestamp desc
)
         left join comment c
                   on bc.comments_id = c.id

编辑(获取查询):

select comments0_.book_id as book_id1_1_0_, comments0_.comments_id as comments2_1_0_, comment1_.id as id1_2_1_, comment1_.body as body2_2_1_, comment1_.timestamp as timestam3_2_1_ from book_comments comments0_ inner join comment comment1_ on comments0_.comments_id=comment1_.id where comments0_.book_id=?

【问题讨论】:

  • 可以提供count查询吗?
  • @Erwin 当然,我刚刚更新了问题
  • 也许你应该通过设置 hibernate.show_sql=true 来检查 hibernate 运行的查询并检查日志
  • @Erwin 我把它打开了。我再次更新了问题并提供了获取查询,该查询针对页面上的每本书执行(20 次)。那是错误的,因为我已经在本机查询中获取了 cmets。如何防止这种行为或用我自己的替换获取查询?
  • 什么是 Page/Pageable 类?是hibernate的吗?

标签: java sql spring hibernate spring-data


【解决方案1】:

这是Blaze-Persistence Entity Views 的完美用例。

Blaze-Persitence 是基于 JPA 的查询构建器,它支持 JPA 模型之上的许多高级 DBMS 功能。我在它之上创建了实体视图,以允许在 JPA 模型和自定义接口定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您以您喜欢的方式定义您的目标结构,并通过 JPQL 表达式将属性(getter)映射到实体模型。由于属性名称用作默认映射,因此您通常不需要显式映射,因为 80% 的用例是拥有作为实体模型子集的 DTO。

您的模型的映射可能如下所示简单

@EntityView(Book.class)
interface BookDto {
  @IdMapping
  Long getId();
  String getAuthor();
  String getTitle();
  @Limit(limit = 5, order = { "timestamp DESC", "id DESC"})
  List<CommentDto> getComments();
}
@EntityView(Comment.class)
interface CommentDto {
  @IdMapping
  Long getId();
  String getBody();
}

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

BookDto dto = entityViewManager.find(entityManager, BookDto.class, id);

但是 Spring Data 集成允许您几乎像 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/1.4/entity-view/manual/en_US/#spring-data-features

@Limit 注释是几周前引入的,它将成为新版本 1.5.0-Alpha2 的一部分,该版本将在接下来的几天内发布。如果您愿意,可以同时使用 1.5.0-SNAPSHOT。 生成的SQL大致是这样的

select b.id, b.author, b.title, c.id, c.body
from book b
cross apply (
  select c.id, c.body, c.timestamp
  from book_comments bc 
  join comment c on bc.comments_id = c.id
  where bc.book_id = b.id
  order by timestamp desc, id desc
  limit 5
) c

您也可以使用大致如下所示的查询构建器手动编写该查询

criteriaBuilderFactory.create(entityManager, Tuple.class)
  .from(Book.class, "b")
  .leftJoinLateralEntitySubquery("b.comments", "c", "subC")
    .orderByDesc("subC.timestamp")
    .orderByDesc("subC.id")
    .setMaxResults(5)
  .end()
  .getResultList();

【讨论】:

    【解决方案2】:

    您可以简单地在外键属性上使用@size 注释来给出上限和下限,并给出您想要的顺序。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-20
      • 1970-01-01
      • 2012-02-22
      相关资源
      最近更新 更多