简体   繁体   English

使用方法名称中的 spring-data 查询重写 JPQL 查询

[英]Rewrite JPQL-query with spring-data query from method names

I have two entities with many-to-many relationship:我有两个具有多对多关系的实体:

@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
}

and

@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
}

Let's say, that we have in the database 1 author with author_name ' Stephen King ' with related 3 books with titles ' book-title-1 ', ' book-title-2 ', ' book-title-3 '.假设我们在数据库中有 1 位作者,其作者姓名为“斯蒂芬金”,相关 3 本书的书名分别为“ book-title-1 ”、“ book-title-2 ”、“ book-title-3 ”。

I need to get author by name with his books with a certain titles, eg:我需要用他的书的名字来获取作者的名字,例如:

find an author named 'Stephen King' with books named 'book-title-1' or 'book-title-2'查找名为“Stephen King”的作者,其书籍名为“book-title-1”或“book-title-2”

With following query as below, it works, namely I'm getting an author with a set of two Book-elements.使用下面的查询,它可以工作,即我得到了一个有两个Book 元素集的作者。

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);
}

Hibernate query for method above (simplified):上述方法的休眠查询(简化):

 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 (? , ?))

Question: Is there a way to rewrite it with query from method names?问题:有没有办法用方法名称的查询重写它?

I tried smth like this:我试过这样的:

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

but it returns author and all related books instead of two books, so hibernate makes two following queries (second hibernate query is made when the Set<Book> books is accessed for the first time):但它返回作者和所有相关书籍而不是两本书,因此 hibernate 进行以下两个查询(第二次 hibernate 查询在第一次访问Set<Book> 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=?

I suggested that probably it would work with @ManyToMany(fetch = FetchType.EAGER) , but hibernate again made two queries like above, with the only difference that the second query is made before the first call to the set of books.我建议它可能会与@ManyToMany(fetch = FetchType.EAGER) ,但是 hibernate 再次进行了两个如上所述的查询,唯一的区别是第二个查询是在第一次调用书籍集之前进行的。

If you need any clarification, let me know and thanks in advance.如果您需要任何说明,请提前告诉我并致谢。

I wouldn't recommend you to use the method name convention as that is only a tool for quick prototyping AFAIU.我不建议您使用方法名称约定,因为它只是用于快速制作 AFAIU 原型的工具。 As soon as the requirements change you might have to switch back to a query and then you will have fun to decipher what this method name convention really means.一旦需求发生变化,您可能必须切换回查询,然后您将很高兴破译此方法名称约定的真正含义。

But even your JPQL/HQL query isn't really safe as you are returning managed entities with filtered collections.但是即使您的 JPQL/HQL 查询也不是真正安全的,因为您返回带有过滤集合的托管实体。 A simple change to the collection could lead to deletion of all filtered out books.对集合的简单更改可能会导致删除所有过滤掉的书籍。 I would recommend you use DTOs instead to avoid all of the issues.我建议您改用 DTO 来避免所有问题。

I think this is a perfect use case forBlaze-Persistence Entity Views .我认为这是Blaze-Persistence Entity Views的完美用例。

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids.我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义的模型之间轻松映射,例如类固醇上的 Spring Data Projections。 The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:使用 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();
    }
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.查询是将实体视图应用于查询的问题,最简单的只是按 id 查询。

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

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features 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);

The best part is, it will only fetch the state that is actually necessary!最好的部分是,它只会获取实际需要的状态!

In your particular case, you could use this:在您的特定情况下,您可以使用这个:

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)
         );
    });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM