【问题标题】:Rewrite JPQL-query with spring-data query from method names使用方法名称的 spring-data 查询重写 JPQL 查询
【发布时间】:2021-10-04 16:46:55
【问题描述】:

我有两个具有多对多关系的实体:

@Entity
@Table(name = "author")
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

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

    @ManyToMany // default FetchType.LAZY
    @JoinTable(name = "author_book",
            joinColumns = @JoinColumn(name = "author_id"),
            inverseJoinColumns = @JoinColumn(name = "book_id"))
    private Set<Book> books;

    // other fields, getters, setters
}

@Entity
@Table(name = "book")
public class Book {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "title", unique = true)
    private String title;
  
    // other fields, getters, setters
}

假设我们在数据库中有 1 位作者,作者名称为“Stephen King”,相关的 3 本书的标题为“book-title-1”,“book-title-2', 'book-title-3'.

我需要通过作者的姓名获取具有特定标题的书籍,例如:

查找名为“Stephen King”的作者,其书籍名为“book-title-1”或“book-title-2”

通过下面的查询,它可以工作,即我得到了一个具有一组 两个 书籍元素的作者。

public interface AuthorRepository extends JpaRepository<Author, Long> {

    @Query("select a from Author a join fetch a.books b " +
            "where a.authorName=:authorName and b.title in :titles")
    Optional<Author> findByNameAndBookTitles(String authorName, Set<String> titles);
}

上述方法的休眠查询(简化):

 select author.id, book.id, author.author_name, book.title, author_book.author_id, author_book.book_id
    from public.author
    inner join public.author_book on author.id= author_book.author_id 
    inner join public.book on author_book.book_id=book.id 
    where author.author_name=? and (book.title in (? , ?))

问题:有没有办法通过查询方法名称来重写它?

我是这样尝试的:

Optional<Author> findByAuthorNameAndBooks_titleIn(String authorName, Set<String> titles);

但它返回作者和所有相关的书籍而不是两本书,所以hibernate会进行以下两次查询(第二次hibernate查询是在第一次访问Set&lt;Book&gt; books时进行的):

Hibernate:
select author.id , author.author_name
from public.author
left outer join public.author_book on author.id = author_book.author_id 
left outer join public.book on author_book.book_id= book.id 
where author_name=? and (book.title in (? , ?))

Hibernate: 
select author_book.author_id, author_book.book_id, book.id, book.title 
from public.author_book
inner join public.book on author_book.book_id=book.id 
where author_book.author_id=?

我建议它可能与 @ManyToMany(fetch = FetchType.EAGER) 一起使用,但 hibernate 再次像上面一样进行了两次查询,唯一的区别是第二次查询是在第一次调用账套之前进行的。

如果您需要任何澄清,请提前告诉我并感谢您。

【问题讨论】:

  • 不应该是findByNameAndTitleIn(String name, Settitles)吗?
  • @MrFisherman 标题是 Book 类的属性,而不是 Author 的属性
  • 对,对不起:)。
  • tou 也可以试试“title”这个词的首字母大写吗?比如 findByAuthorNameAndBooks_TitleIn。互联网上所有类似的查询都以大写字母开头,例如stackoverflow-com.translate.goog/a/…docs-spring-io.translate.goog/spring-data/jpa/docs/current/…
  • @MrFisherman 谢谢,试过了,但行为没有改变,它还获取所有相关书籍

标签: java spring spring-boot hibernate jpa


【解决方案1】:

我不建议您使用方法名称约定,因为它只是用于快速制作 AFAIU 原型的工具。一旦需求发生变化,您可能必须切换回查询,然后您会很高兴地解读此方法名称约定的真正含义。

但是,即使您的 JPQL/HQL 查询也不是很安全,因为您返回的是带有过滤集合的托管实体。对集合的简单更改可能会导致删除所有过滤掉的书籍。我建议您改用 DTO 来避免所有问题。

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

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

@EntityView(Author.class)
public interface AuthorDto {
    @IdMapping
    Long getId();
    String getAuthorName();
    Set<BookDto> getBooks();

    @EntityView(Book.class)
    interface BookDto {
        @IdMapping
        Long getId();
        String getTitle();
    }
}

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

AuthorDto a = entityViewManager.find(entityManager, AuthorDto.class, id);

Spring Data 集成让您可以像使用 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<AuthorDto> findAll(Pageable pageable);

最好的部分是,它只会获取实际需要的状态!

在你的特殊情况下,你可以使用这个:

Optional<AuthorDto> findByAuthorNameAndBooks_titleIn(String authorName, Set<String> titles) {
    return findOne((root, query, cb) -> {
        return cb.and(
            cb.equal(root.get("name"), authorName),
            root.get("books").get("title").in(titles)
         );
    });
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-22
    • 2020-06-05
    • 1970-01-01
    • 2014-03-05
    • 2016-09-25
    • 2017-11-08
    • 2018-06-24
    相关资源
    最近更新 更多