Статьи

Как JPA 2.1 стал новым EJB 2.0

Красота лежит в глазах смотрящего. Так же как и «легкость»:

Узнайте больше о сопоставлениях наборов результатов SQL и с легкостью обработайте результаты собственных запросов: http://t.co/WH4BTlClIP #JPA #Java #JavaEE

— Торбен Янссен (@ thjanssen123) 15 апреля 2015 г.

Торбен пишет очень хорошие и полезные статьи о JPA , и недавно он начал отличную серию о новых возможностях JPA 2.1. Среди которых: отображение набора результатов. Вы можете узнать соответствие набора результатов с таких сайтов, как CTMMC или annotatiomania.com . Мы можем суммировать эту процедуру отображения следующим образом:

а) определить отображение

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@SqlResultSetMapping(
    name = "BookAuthorMapping",
    entities = {
        @EntityResult(
            entityClass = Book.class,
            fields = {
                @FieldResult(name = "id", column = "id"),
                @FieldResult(name = "title", column = "title"),
                @FieldResult(name = "author", column = "author_id"),
                @FieldResult(name = "version", column = "version")}),
        @EntityResult(
            entityClass = Author.class,
            fields = {
                @FieldResult(name = "id", column = "authorId"),
                @FieldResult(name = "firstName", column = "firstName"),
                @FieldResult(name = "lastName", column = "lastName"),
                @FieldResult(name = "version", column = "authorVersion")})})

Приведенное выше отображение довольно простое. Он определяет, как столбцы базы данных должны быть сопоставлены с полями сущностей и с сущностями в целом. Затем вы даете этому отображению имя ( "BookAuthorMapping" ), которое затем можете повторно использовать в вашем приложении, например, с собственными запросами JPA.

Мне особенно нравится тот факт, что Торбен тогда пишет:

Если вам не нравится добавлять такой огромный блок аннотаций к вашей сущности, вы также можете определить отображение в XML-файле.

… Итак, мы вернулись к замене огромных блоков аннотаций огромными блоками XML — метод, который многие из нас хотели избежать, используя аннотации… 🙂

б) применить сопоставление

После того как сопоставление статически определено для некоторого типа Java, вы можете извлечь эти сущности, применив вышеупомянутое BookAuthorMapping

01
02
03
04
05
06
07
08
09
10
11
12
13
List<Object[]> results = this.em.createNativeQuery(
    "SELECT b.id, b.title, b.author_id, b.version, " +
    "       a.id as authorId, a.firstName, a.lastName, " +
    "       a.version as authorVersion " +
    "FROM Book b " +
    "JOIN Author a ON b.author_id = a.id",
    "BookAuthorMapping"
).getResultList();
 
results.stream().forEach((record) -> {
    Book book = (Book)record[0];
    Author author = (Author)record[1];
});

Обратите внимание, что вам все еще нужно помнить типы Book и Author и приводить их явно, поскольку никакая информация о проверяемых типах действительно ни к чему не прикреплена.

Определение «сложный»

Теперь в статье утверждается, что это «сложное» отображение, и, без сомнения, я бы согласился. Этот очень простой запрос с простым соединением уже вызывает такой беспорядок аннотаций, если вы действительно хотите отобразить свои сущности через JPA. Вы не хотите видеть аннотации отображения Торбена, как только запросы станут немного более сложными. И помните, @SqlResultSetMapping — это отображение (нативных!) Результатов SQL, поэтому мы больше не на земле постоянных объектов-графов, мы на земле SQL , где массовая выборка, денормализация, агрегирование и другие «причудливые» SQL вещи король

Проблема здесь:

Java 5 представила аннотации. Изначально аннотации предназначались для использования в качестве «искусственных модификаторов», то есть таких вещей, как static , final , protected (достаточно интересно, что Цейлон знает только аннотации, но не модификаторы ). Это имеет смысл. Разработчики языка Java могли бы вводить новые модификаторы / «ключевые слова», не нарушая существующий код, поскольку «настоящие» ключевые слова — это зарезервированные слова, которые сложно ввести в язык. Помните enum ?

Итак, хорошие варианты использования для аннотаций (а их всего несколько):

  • @Override
  • @Deprecated (хотя атрибут комментария был бы интересен)
  • @FunctionalInterface

JPA (и другие API Java EE, а также Spring) полностью отказались от использования аннотаций. Повторяй за мной:

Ни один язык до или после Java никогда не злоупотреблял аннотациями так, как Java

При прочтении вышесказанного во мне ощущается сильная дежавю. Вы помните следующее?

Ни один язык до или после Java никогда не злоупотреблял проверенными исключениями так, как Java

Мы все будем глубоко сожалеть о Java-аннотациях к 2020 году.

Аннотации — большая проблема в системе типов Java. Они имеют крайне ограниченное обоснованное использование, и то, что мы, разработчики Java Enterprise, делаем в наши дни, абсолютно не в рамках «оправданного». Мы злоупотребляем ими для настройки вещей, для которых нам действительно нужно писать код .

Вот как вы можете выполнить тот же запрос с jOOQ (или любым другим API, который использует обобщенные значения и безопасность типов для SQL):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
Book b = BOOK.as("b");
Author a = AUTHOR.as("a");
 
DSL.using(configuration)
   .select(b.ID, b.TITLE, b.AUTHOR_ID, b.VERSION,
           a.ID, a.FIRST_NAME, a.LAST_NAME,
           a.VERSION)
   .from(b)
   .join(a).on(b.AUTHOR_ID.eq(a.ID))
   .fetch()
   .forEach(record -> {
       BookRecord book = record.into(b);
       AuthorRecord author = record.into(a);
   });

Этот пример объединяет аннотации JPA 2.1 и запросы. Вся мета-информация о спроецированных «объектах» уже содержится в запросе и, следовательно, в Result который создается методом fetch() . Но это не имеет значения, дело в том, что это лямбда-выражение …

1
2
3
4
record -> {
    BookRecord book = record.into(b);
    AuthorRecord author = record.into(a);
}

… это может быть что угодно! Как и более сложные примеры, которые мы показали в предыдущих сообщениях в блоге:

Картирование может быть определено на лету, используя функции. Функции являются идеальными преобразователями, потому что они принимают входные данные, производят выходные данные и полностью не сохраняют состояния. И самое лучшее в функциях Java 8 — они компилируются компилятором Java и могут использоваться для проверки типов в вашем отображении. И вы можете назначать функции объектам, что позволяет вам повторно использовать функции, когда данный алгоритм отображения может использоваться несколько раз.

Фактически само предложение SQL SELECT является такой функцией. Функция, которая преобразует входные кортежи / строки в выходные кортежи / строки, и вы можете адаптировать эту функцию на лету, используя дополнительные выражения.

Нет абсолютно никакого способа проверить что-либо в предыдущем операторе SQL для JPA 2.1 и в примере @SqlResultSetMapping . Представьте себе изменение названия столбца:

1
2
3
4
5
6
7
8
9
List<Object[]> results = this.em.createNativeQuery(
    "SELECT b.id, b.title as book_title, " +
    "       b.author_id, b.version, " +
    "       a.id as authorId, a.firstName, a.lastName, " +
    "       a.version as authorVersion " +
    "FROM Book b " +
    "JOIN Author a ON b.author_id = a.id",
    "BookAuthorMapping"
).getResultList();

Вы заметили разницу? b.title был переименован в book_title . В строке SQL. Который взрывается во время выполнения ! Как помнить, что вы должны также адаптироваться

1
@FieldResult(name = "title", column = "title")

… быть

1
@FieldResult(name = "title", column = "book_title")

И наоборот, как запомнить, что после того, как вы переименуете column в своем @FieldResult , вам также нужно будет проверить, где используется этот "BookAuthorMapping" , а также изменить имена столбцов в этих запросах.

1
2
3
4
@SqlResultSetMapping(
    name = "BookAuthorMapping",
    ...
)

Аннотации злые

Вы можете соглашаться или не соглашаться с некоторыми из вышеперечисленных. Вам может понравиться или не понравиться jOOQ как альтернатива JPA , это прекрасно. Но очень трудно не согласиться с тем, что:

  • Java 5 представила очень полезные аннотации
  • Java EE / Spring сильно злоупотребляет этими аннотациями для замены XML
  • Теперь у нас есть система параллельных юниверсов в Java
  • Эта параллельная система типов юниверсов совершенно бесполезна, потому что компилятор не может ее проанализировать
  • Java SE 8 представляет функциональное программирование и множество выводов типов
  • Java SE 9-10 представит более удивительные возможности языка
  • Теперь становится ясно, что конфигурация (XML или аннотации) должна была быть в первую очередь кодом
  • JPA 2.1 стал новым EJB 2.0: устарел

Как я сказал. Трудно не согласиться. Или другими словами:

Код гораздо лучше выражает алгоритмы, чем конфигурация

Я лично встречался с Торбеном несколько раз на конференциях. Этот напыщенный разговор не имел в виду лично, Торбен 🙂 Ваши статьи о JPA очень интересны. Если вы, читатели этой статьи, используете JPA, пожалуйста, загляните в блог Торбена: http://www.viousts-on-java.org .

В то же время, я хотел бы выдвинуть кандидатуру Торбена на почетное звание « Аннототиомания года 2015 ».

Ссылка: Как JPA 2.1 стал новым EJB 2.0 от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и JOOQ .