«1. Обзор

Apache DeltaSpike — это проект, предоставляющий набор расширений CDI для проектов Java; для этого требуется, чтобы реализация CDI была доступна во время выполнения.

Конечно, он может работать с другой реализацией CDI — JBoss Weld или OpenWebBeans. Он также протестирован на многих серверах приложений.

В этом уроке мы сосредоточимся на одном из самых известных и полезных — модуле данных.

2. Настройка модуля данных DeltaSpike

Модуль Apache DeltaSpike Data используется для упрощения реализации шаблона репозитория. Это позволяет сократить шаблонный код, предоставляя централизованную логику для создания и выполнения запросов.

Это очень похоже на проект Spring Data. Чтобы запросить базу данных, нам нужно определить объявление метода (без реализации), которое соответствует определенному соглашению об именах или содержит аннотацию @Query. Реализация будет сделана за нас расширением CDI.

В следующих подразделах мы расскажем, как настроить модуль Apache DeltaSpike Data в нашем приложении.

2.1. Требуемые зависимости

Чтобы использовать модуль данных Apache DeltaSpike в приложении, нам необходимо настроить необходимые зависимости.

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

<dependency>
    <groupId>org.apache.deltaspike.modules</groupId>
    <artifactId>deltaspike-data-module-api</artifactId>
    <version>1.8.2</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.apache.deltaspike.modules</groupId>
    <artifactId>deltaspike-data-module-impl</artifactId>
    <version>1.8.2</version>
    <scope>runtime</scope>
</dependency>

Когда мы используем Gradle:

runtime 'org.apache.deltaspike.modules:deltaspike-data-module-impl'
compile 'org.apache.deltaspike.modules:deltaspike-data-module-api'

Apache DeltaSpike Артефакты модуля данных доступны на Maven Central:

    deltaspike- data-module-impl deltaspike-data-module-api

Чтобы запустить приложение с модулем данных, нам также нужны реализации JPA и CDI, доступные во время выполнения.

Хотя Apache DeltaSpike можно запустить в приложении Java SE, в большинстве случаев он будет развернут на сервере приложений (например, Wildfly или WebSphere).

Серверы приложений имеют полную поддержку Jakarta EE, поэтому нам больше ничего делать не нужно. В случае приложения Java SE мы должны предоставить эти реализации (например, путем добавления зависимостей к Hibernate и JBoss Weld).

Далее мы рассмотрим необходимую конфигурацию для EntityManager.

2.2. Конфигурация Entity Manager

Модуль данных требует внедрения EntityManager через CDI.

Этого можно добиться с помощью производителя CDI:

public class EntityManagerProducer {

    @PersistenceContext(unitName = "primary")
    private EntityManager entityManager;

    @ApplicationScoped
    @Produces
    public EntityManager getEntityManager() {
        return entityManager;
    }
}

В приведенном выше коде предполагается, что у нас есть первичная единица персистентности, определенная в файле persistence.xml.

Давайте посмотрим ниже в качестве примера определения:

<persistence-unit name="primary" transaction-type="JTA">
   <jta-data-source>java:jboss/datasources/baeldung-jee7-seedDS</jta-data-source>
   <properties>
      <property name="hibernate.hbm2ddl.auto" value="create-drop" />
      <property name="hibernate.show_sql" value="false" />
   </properties>
</persistence-unit>

Блок сохраняемости в нашем примере использует тип транзакции JTA, что означает, что мы должны предоставить стратегию транзакции, которую мы собираемся использовать.

2.3. Стратегия транзакций

Если мы используем тип транзакций JTA для нашего источника данных, мы должны определить стратегию транзакций, которая будет использоваться в репозиториях Apache DeltaSpike. Мы можем сделать это в файле apache-deltaspike.properties (в каталоге META-INF):

globalAlternatives.org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy=org.apache.deltaspike.jpa.impl.transaction.ContainerManagedTransactionStrategy

Мы можем определить четыре типа стратегии транзакций:

    BeanManagedUserTransactionStrategy ResourceLocalTransactionStrategy ContainerManagedTransactionStrategy EnvironmentAwareTransactionStrategy

Все они реализуются org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy.

Это была последняя часть конфигурации, необходимой для нашего модуля данных.

Далее мы покажем, как реализовать классы шаблонов репозитория.

3. Классы репозитория

Когда мы используем модуль данных Apache DeltaSpike, любой абстрактный класс или интерфейс может стать классом репозитория.

Все, что нам нужно сделать, это добавить аннотацию @Repository с атрибутом forEntity, который определяет сущность JPA, которую должен обрабатывать наш репозиторий:

@Entity
public class User {
    // ...
}  

@Repository(forEntity = User.class) 
public interface SimpleUserRepository { 
    // ... 
}

или с абстрактным классом:

@Repository(forEntity = User.class)
public abstract class SimpleUserRepository { 
    // ... 
}

Модуль данных обнаруживает классы (или интерфейсы) с такой аннотацией, и он будет обрабатывать методы, которые находятся внутри.

Есть несколько возможностей определить выполняемый запрос. Мы рассмотрим один за другим в следующих разделах.

4. Запрос из имени метода

Первая возможность определить запрос — использовать имя метода, которое соответствует определенному соглашению об именах.

Это выглядит так:

(Entity|Optional<Entity>|List<Entity>|Stream<Entity>) (prefix)(Property[Comparator]){Operator Property [Comparator]}

Далее мы сосредоточимся на каждой части этого определения.

4.1. Тип возврата

«Тип возвращаемого значения в основном определяет, сколько объектов может вернуть наш запрос. Мы не можем определить один тип объекта в качестве возвращаемого значения, если наш запрос может вернуть более одного результата.

Следующий метод вызовет исключение, если имеется более одного пользователя с заданным именем:

public abstract User findByFirstName(String firstName);

Обратное неверно — мы можем определить возвращаемое значение как коллекцию, даже если результат будет просто единое целое.

public abstract Collection<User> findAnyByFirstName(String firstName);

Префикс имени метода, который предлагает одно значение в качестве возвращаемого типа (например, findAny), подавляется, если мы определяем возвращаемое значение как Collection.

Приведенный выше запрос вернет всех пользователей с совпадающим именем, даже если префикс имени метода предполагает что-то другое.

Таких комбинаций (тип возврата коллекции и префикс, который предполагает возврат одного единственного значения) следует избегать, потому что код становится неинтуитивным и трудным для понимания.

В следующем разделе приведены более подробные сведения о префиксе имени метода.

4.2. Префикс для метода запроса

Префикс определяет действие, которое мы хотим выполнить в репозитории. Наиболее полезным является поиск объектов, соответствующих заданным критериям поиска.

Для этого действия существует множество префиксов, таких как findBy, findAny, findAll. Подробный список см. в официальной документации Apache DeltaSpike:

public abstract User findAnyByLastName(String lastName);

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

public abstract int count();

Также существует шаблон метода удаления, который мы можем добавить в наш репозиторий:

public abstract void remove(User user);

Поддержка префиксов метода countBy и removeBy будет добавлена ​​в следующей версии Апач ДельтаСпайк 1.9.0.

В следующем разделе показано, как мы можем добавить дополнительные атрибуты к запросам.

4.3. Запрос со многими свойствами

В запросе мы можем использовать множество свойств в сочетании с операторами и.

public abstract Collection<User> findByFirstNameAndLastName(
  String firstName, String lastName);
public abstract Collection<User> findByFirstNameOrLastName(
  String firstName, String lastName);

Мы можем комбинировать столько свойств, сколько захотим. Также доступен поиск вложенных свойств, которые мы покажем далее.

4.4. Запрос с вложенными свойствами

Запрос также может использовать вложенные свойства.

В следующем примере сущность User имеет свойство адреса типа Address, а сущность Address имеет свойство city:

@Entity
public class Address {
private String city;
    // ...
}
@Entity
public class User {
    @OneToOne 
    private Address address;
    // ...
}
public abstract Collection<User> findByAddress_city(String city);

4.5. Порядок в запросе

DeltaSpike позволяет нам определить порядок, в котором должен быть возвращен результат. Мы можем определить как восходящий, так и нисходящий порядок:

public abstract List<User> findAllOrderByFirstNameAsc();

Как показано выше, все, что нам нужно сделать, это добавить часть к имени метода, которая содержит имя свойства, по которому мы хотим сортировать, и короткое имя для направление заказа.

Мы можем легко комбинировать множество ордеров:

public abstract List<User> findAllOrderByFirstNameAscLastNameDesc();

Далее мы покажем, как ограничить размер результата запроса.

4.6. Ограничение размера результата запроса и нумерации страниц

Существуют случаи использования, когда мы хотим получить несколько первых строк из всего результата. Это так называемый лимит запросов. Это также просто с модулем данных:

public abstract Collection<User> findTop2OrderByFirstNameAsc();
public abstract Collection<User> findFirst2OrderByFirstNameAsc();

First и top могут использоваться взаимозаменяемо.

Затем мы можем включить разбивку запросов на страницы, указав два дополнительных параметра: @FirstResult и @MaxResult:

public abstract Collection<User> findAllOrderByFirstNameAsc(@FirstResult int start, @MaxResults int size);

Мы уже определили множество методов в репозитории. Некоторые из них являются общими и должны быть определены один раз и использоваться каждым репозиторием.

Apache DeltaSpike предоставляет несколько базовых типов, которые мы можем использовать, чтобы иметь множество готовых методов.

В следующем разделе мы сосредоточимся на том, как это сделать.

5. Основные типы репозиториев

Чтобы получить некоторые базовые методы репозитория, наш репозиторий должен расширять базовый тип, предоставляемый Apache DeltaSpike. Есть некоторые из них, такие как EntityRepository, FullEntityRepository и т. д.:

@Repository
public interface UserRepository 
  extends FullEntityRepository<User, Long> {
    // ...
}

Или с использованием абстрактного класса:

@Repository
public abstract class UserRepository extends AbstractEntityRepository<User, Long> {
    // ...
}

Приведенная выше реализация дает нам множество методов без написания дополнительных строк кода, поэтому мы получили то, что мы хотели — мы значительно сократили шаблонный код.

В случае, если мы используем базовый тип репозитория, нет необходимости передавать дополнительное значение атрибута forEntity в нашу аннотацию @Repository.

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

«Абстрактные базовые классы репозитория, например, AbstractEntityRepository дают нам доступ к полям (через геттеры) или служебные методы, которые мы можем использовать для создания запроса:

public List<User> findByFirstName(String firstName) {
    return typedQuery("select u from User u where u.firstName = ?1")
      .setParameter(1, firstName)
      .getResultList();
}

В приведенном выше примере мы использовали служебный метод typedQuery для создания пользовательская реализация.

Последняя возможность создать запрос — использовать аннотацию @Query, которую мы покажем далее.

6. Аннотация @Query

Выполняемый SQL-запрос также может быть определен с помощью аннотации @Query. Это очень похоже на решение Spring. Мы должны добавить аннотацию к методу с SQL-запросом в качестве значения.

По умолчанию это запрос JPQL:

@Query("select u from User u where u.firstName = ?1")
public abstract Collection<User> findUsersWithFirstName(String firstName);

Как и в приведенном выше примере, мы можем легко передать параметры в запрос через индекс.

В случае, если мы хотим передать запрос через нативный SQL вместо JPQL, нам нужно определить дополнительный атрибут запроса – isNative со значением true:

@Query(value = "select * from User where firstName = ?1", isNative = true)
public abstract Collection<User> findUsersWithFirstNameNative(String firstName);

7. Заключение

В этой статье мы рассмотрели базовое определение Apache DeltaSpike, и мы сосредоточились на захватывающей части — модуле данных. Это очень похоже на Spring Data Project.

Мы рассмотрели, как реализовать шаблон репозитория. Мы также представили три возможности определения запроса для выполнения.

Как всегда, полные примеры кода, используемые в этой статье, доступны на Github.