Статьи

Использование JINQ с JPA и H2

Несколько дней назад я прочитал интересное интервью с Ming-Yee Iu о JINQ . JINQ, как видно из названия, является попыткой предоставить что-то похожее на LINQ для Java. Основная идея заключается в том, чтобы закрыть семантический разрыв между объектно-ориентированным кодом, который выполняет запросы в реляционной модели данных. Запросы для модели реляционной базы данных должны быть легко интегрированы в код таким образом, чтобы он казался более естественным.

Исследование LINQ пришло к выводу, что алгоритмы, преобразующие код в запросы реляционной базы данных, лучше всего работают с функциональным кодом. Поскольку Java 8 поставляется с потоковым API, автор использует его для реализации идей своего доктора философии в Java.

Чтобы запачкать руки, мы начнем с простого проекта, который использует Hibernate поверх JPA вместе с базой данных H2 и JINQ:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<dependencies>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>${jee.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>${h2.version}</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>${hibernate.version}</version>
    </dependency>
    <dependency>
        <groupId>org.jinq</groupId>
        <artifactId>jinq-jpa</artifactId>
        <version>1.8.10</version>
    </dependency>
</dependencies>

Чтобы использовать потоки JINQ, мы должны создать провайдера, который получает EntityManagerFactory качестве аргумента:

1
2
EntityManagerFactory factory = Persistence.createEntityManagerFactory("PersistenceUnit");
JinqJPAStreamProvider streams = new JinqJPAStreamProvider(factory);

Вставив несколько человек в нашу базу данных, мы можем легко запросить их:

1
2
3
4
List<String> firstNames = streams.streamAll(entityManager, Person.class)
        .map(Person::getFirstName)
        .collect(toList());
firstNames.forEach(System.out::println);

Использование метода streamAll() ранее созданного JinqJPAStreamProvider дает нам доступ ко всем лицам в базе данных. В этом простом примере мы хотим вывести только имя каждого человека; следовательно, мы map список и collect все результаты в List . Этот список печатается с использованием метода forEach() и ссылки на метод println() .

Взглянув на сгенерированный код SQL, мы видим, что все столбцы выбраны:

1
2
3
4
5
6
7
select
    person0_.id as id1_4_,
    person0_.FIRST_NAME as FIRST_NA2_4_,
    person0_.ID_CARD_ID as ID_CARD_4_4_,
    person0_.LAST_NAME as LAST_NAM3_4_,
from
    T_PERSON person0_

Конечно, мы можем уточнить оператор, используя метод select() :

1
2
3
4
5
List<String> firstNames = streams.streamAll(entityManager, Person.class)
        .select(Person::getFirstName)
        .where(p -> p.equals("Homer"))
        .collect(toList());
firstNames.forEach(System.out::println);

Кроме того, мы также добавили предикат ( where firstName = 'Homer' ):

1
2
3
4
5
6
select
    person0_.FIRST_NAME as FIRST_NA2_4_
from
    T_PERSON person0_
where
    person0_.FIRST_NAME='Homer'

Оставляя этот простой пример, мы теперь хотим создать запрос, который выбирает всех вундеркиндов с именем «Christian», которые работают во временном и материальном проектах:

1
2
3
4
5
6
7
List<String> geeks = streams.streamAll(entityManager, Project.class)
        .where(p -> p.getProjectType() == Project.ProjectType.TIME_AND_MATERIAL)
        .joinList(Project::getGeeks)
        .where(g -> g.getTwo().getFirstName().equals("Christian"))
        .map(p -> p.getTwo().getFirstName())
        .collect(toList());
geeks.forEach(System.out::println);

Как видно из приведенного выше кода, мы используем первое предложение where() для выбора всех временных и материальных проектов. joinList() присоединяется к таблице geek, в то время как последующее предложение where() также ограничивается выбором только вундеркиндов с именем «Christian». Et voila, это созданный SQL-запрос:

01
02
03
04
05
06
07
08
09
10
11
12
13
select
    geek2_.FIRST_NAME as col_0_0_
from
    T_PROJECT project0_
inner join
    T_GEEK_PROJECT geeks1_
        on project0_.id=geeks1_.PROJECT_ID
inner join
    T_GEEK geek2_
        on geeks1_.GEEK_ID=geek2_.id
where
    project0_.projectType='TIME_AND_MATERIAL'
    and geek2_.FIRST_NAME='Christian' limit ?

Заключение . Поработав с API критериев JPA некоторое время назад, я должен сказать, что первые шаги с JINQ более интуитивны и их легче записать. JINQ действительно помогает сократить разрыв между миром реляционных баз данных, используя потоки в Java 8.

Ссылка: Использование JINQ с JPA и H2 от нашего партнера по JCG Мартина Моиса в блоге Martin’s Developer World .