Как перехватить и изменить сгенерированный JPA SQL-оператор?

#jpa #spring-data-jpa #jpql

#jpa #spring-data-jpa #jpql

Вопрос:

Интересно, есть ли способ оптимизировать процесс генерации JPA-запроса (JPQL) вместо использования собственного.

для простоты моя модель выглядит следующим образом:

 @Entity
@Table(name = "b_books")
public class Book {
    
    @Id
    @Column(name = "book_id", updatable = false, nullable = false, unique = true)
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="book_generator")   
    @SequenceGenerator(name="book_generator", sequenceName = "books_seq", allocationSize=1)
    private Integer id;
    
    @Column(name = "book_titl_tx")
    private String title;
    
    private int yearOfPublication;
    
    @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
    @JoinTable(name = "b_book_authors",
               joinColumns = @JoinColumn(name = "book_id"), 
               inverseJoinColumns = @JoinColumn(name = "auth_id"))
    private List<Author> authors = new ArrayList<Author>();

    //getter, setters, etc
}


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

    @Id
    @Column(name = "auth_id", nullable = false, unique = true)
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="authors_generator")
    @SequenceGenerator(name="authors_generator", sequenceName = "authors_seq", allocationSize=1)
    private Integer id;

    @Column(name = "auth_last_name_tx")
    private String lastName;

    @ManyToMany(mappedBy = "authors")
    private List<Book> books = new ArrayList<Book>();

    //getters, setters, etc
}
 

На стороне базы данных у меня есть три таблицы:

  • B_BOOKS
  • B_AUTHORS
  • B_BOOK_AUTHORS, который содержит отношение ManyToMany

Я хотел бы запросить в БД количество книг для каждого автора

 public interface AuthorRepository extends JpaRepository<Author, Integer>{

@Query(value = "SELECT a.lastName AS lastName, "   
               "       COUNT(a) AS booksCount"  
               "  FROM Author AS a "  
               "  LEFT JOIN a.books AS b"  
               " GROUP BY a.lastName")
List<IAuthorInfo> getAuthorInfo();
}
 

Я использую проекцию на основе интерфейса

 public interface IAuthorInfo {

    String getLastName();
    Integer getBooksCount();
}
 

А теперь к главному вопросу: в этой модели сгенерированный JPA запрос имеет одно избыточное соединение, как от него избавиться?

АКТУАЛЬНО:

 SELECT
    author0_.auth_last_name_tx   AS col_2_0_,
    COUNT(author0_.auth_id) AS col_3_0_
FROM
    admin.b_authors        author0_
    LEFT OUTER JOIN admin.b_book_authors   books1_ ON author0_.auth_id = books1_.auth_id
    LEFT OUTER JOIN admin.b_books          book2_ ON books1_.book_id = book2_.book_id
GROUP BY
    author0_.auth_last_name_tx
 

ОЖИДАЕМЫЙ

 SELECT
    author0_.auth_last_name_tx   AS col_2_0_,
    COUNT(author0_.auth_id) AS col_3_0_
FROM
    admin.b_authors        author0_
    LEFT OUTER JOIN admin.b_book_authors   books1_ ON author0_.auth_id = books1_.auth_id
GROUP BY
    author0_.auth_last_name_tx
 

Ответ №1:

Вы можете использовать size() функцию:

 SELECT a.lastName AS lastName, size(a.books) AS booksCount FROM Author AS a GROUP BY a.lastName
 

Я не тестировал, но я бы предположил, что удаление LEFT JOIN a.books AS b из вашего запроса решит эту проблему.

Ответ №2:

Спасибо, это LEFT JOIN a.books AS b действительно удалено.

Теперь он генерирует SQL:

 SELECT author0_.auth_last_name_tx AS col_2_0_,
       (SELECT COUNT(books1_.auth_id)
          FROM b_book_authors books1_
         WHERE author0_.auth_id = books1_.auth_id) AS col_3_0_
  FROM b_authors author0_
 GROUP BY author0_.auth_last_name_tx;
 

что удивительно работает с базой данных H2 (mem) для модульных тестов
, но для Oracle требуется добавление Author.Идентификатор в JPQL

 SELECT author0_.auth_id,
       author0_.auth_last_name_tx AS col_2_0_,
       (SELECT COUNT(books1_.auth_id)
          FROM b_book_authors books1_
         WHERE author0_.auth_id = books1_.auth_id) AS col_3_0_
  FROM b_authors author0_
 GROUP BY author0_.auth_id,
       author0_.auth_last_name_tx;