Spring Data JPA в паре с Hibernate дает вам массу возможностей прямо из коробки. Вы можете написать запросы на простом языке , которые будут выполнять такие действия, как найти все сущности()
или Найти конкретное свойство (значение свойства)
. Вы даже можете запрашивать по связанным свойствам сущности, таким как найти все с соответствующим свойством конкретной сущности (значение свойства)
.
Если вы не можете найти способ сделать это с помощью ключевых слов запроса, вы даже можете написать свои собственные рукописные запросы . Они отлично подходят, если вы хотите выполнять определенные соединения и ссылки, которые вы обычно не используете.
Но что, если вы хотите использовать какое-то ключевое слово/функцию, специфичную для конкретной платформы? Тот, который недостаточно универсален, чтобы Hibernate реализовал его на всех своих диалектах.
Для конкретного случая использования у нас был разработчик, который хотел использовать LISTAGG
, который является специфичным для Oracle методом это было весьма полезно… но не доступен из коробки. О нееет, что теперь будет?
Что ж, мы просмотрели список доступных функций по умолчанию, объявленных на готовом диалекте гибернации, вы можете посмотреть полный список здесь . Куча всякой всячины, верно? Но, нет LISTAGG
.
Расширение диалекта
Из-за того, как работает наследование в Java, я могу просто расширить этот класс, а затем добавить свое собственное объявление LISTAGG
.
package com.sethkellas.dialectextensions.dialects; import static org.hibernate.type.StandardBasicTypes.STRING; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.function.SQLFunctionTemplate; public class H2CustomDialect extends H2Dialect { public H2CustomDialect() { super(); registerFunction("LISTAGG", new SQLFunctionTemplate(STRING, "LISTAGG(distinct ?1, ',') WITHIN GROUP(ORDER BY ?1)")); } }
Конфигурации
Да. Это так просто.
Другой разработчик потратил целый день на написание собственного запроса, чтобы использовать LISTAGG
непосредственно против базы данных Oracle, на которую мы указывали. Но поскольку это был собственный запрос, когда я вернулся, чтобы реорганизовать конечную точку, чтобы разрешить разбивку на страницы… это было не так уж хорошо.
Итак, мы наконец-то нашли минутку и рассмотрели описанную выше реализацию. Расширяя этот класс, а затем показывая Spring, как использовать диалект в нашем application.yml
.
spring: jpa: database-platform: com.sethkellas.dialectextensions.dialects.H2CustomDialect
И тогда мы, очевидно, должны это проверить, потому что… что такое статья без доказательств.
Сущности
@Entity @Data @Builder(toBuilder = true) @AllArgsConstructor(access = AccessLevel.PACKAGE) @NoArgsConstructor public class Professor { @Id @GeneratedValue private Long id; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @Default @OneToMany(fetch = FetchType.EAGER, mappedBy = "professor") private Setlectures = new HashSet<>(); }
@Entity @Data @Builder(toBuilder = true) @AllArgsConstructor(access = AccessLevel.PACKAGE) @NoArgsConstructor public class Lecture { @Id @GeneratedValue private Long id; @Column(name = "title") private String title; @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "professor_id") private Professor professor; }
Репозиторий/Запрос
@Repository public interface ProfessorRepository extends JpaRepository{ @Query( value = "SELECT new com.sethkellas.dialectextensions.views.ProfessorAndLectureView(" + " CONCAT(p.firstName, ' ', p.lastName)" + " , LISTAGG(l.title, ',')" + ")" + " FROM Professor p" + " LEFT JOIN Lecture l on l.professor = p" + " GROUP BY p.firstName", countQuery = "SELECT COUNT(*) FROM Professor") Set findProfessorsWithLecturers(); }
Тест
@Test void shouldReturnViews() { // Given Professor testProfessor = professorRepo.save(Professor.builder().firstName(randomAlphabetic(8)).lastName(randomAlphabetic(12)).build()); Lecture lectureAlpha = lectureRepo.save(Lecture.builder().title(randomAlphabetic(16)).professor(testProfessor).build()); Lecture lectureBeta = lectureRepo.save(Lecture.builder().title(randomAlphabetic(24)).professor(testProfessor).build()); // When Setprofessors = professorRepo.findProfessorsWithLecturers(); // Then softly.assertThat(professors).as("Matches Full Name") .extracting("fullName").contains(format("%s %s", testProfessor.getFirstName(), testProfessor.getLastName())); softly.assertThat(professors).as("Contains First Lecture Name") .extracting("lectureList").asString().contains(lectureAlpha.getTitle()); softly.assertThat(professors).as("Contains Second Lecture Name") .extracting("lectureList").asString().contains(lectureBeta.getTitle()); }
Мы видим, что можем объединить соответствующих лекторов, когда запрашиваем объект ProfessorView
response. Единственный способ пройти этот тест – это если у нас есть это application.yml
файл изменен на месте. Если мы полагаемся на готовый диалект, мы получаем немедленный Исключение из незаконного состояния
когда мы пытаемся запустить приложение Spring Boot.
Это уродливо.
НО мы сделали это! Мы можем использовать наш собственный диалект и использовать любые пользовательские функции, которые мы объявляем. Мы можем пофантазировать там… или мы можем просто использовать некоторые специфические функции СУБД, которые изначально недоступны.
Время истины.
Я пишу все это, потому что не хочу забывать, как это делать в будущем. Один из других разработчиков был немного расстроен, когда я показал ему, насколько незначительным было изменение кода, чтобы обойти @NativeQuery
, который он должен был написать.
Всегда помните, что вы можете расширить “черную магию”, которую использует Spring. Команда Spring проделала огромную работу, чтобы предложить вам 80% функций, которые вам понадобятся, и вы должны расширить их работу и выполнить эти последние 20%.
Спасибо за чтение! Доступен полный исходный код @ ГитХаб .
Оригинал: “https://dev.to/skellas/extending-hibernate-dialects-5aml”