Eine Anleitung zum DeltaSpike-Datenmodul

1. Übersicht

Apache DeltaSpike ist ein Projekt, das eine Sammlung von CDI-Erweiterungen für Java-Projekte bereitstellt . Für die Laufzeit muss eine CDI-Implementierung verfügbar sein.

Natürlich kann es mit der unterschiedlichen Implementierung von CDI - JBoss Weld oder OpenWebBeans funktionieren. Es wurde auch auf vielen Anwendungsservern getestet.

In diesem Tutorial konzentrieren wir uns auf eines der bekanntesten und nützlichsten - das Datenmodul .

2. DeltaSpike-Datenmodul einrichten

Das Apache DeltaSpike Data-Modul wird verwendet, um die Implementierung des Repository-Musters zu vereinfachen . Es ermöglicht die Reduzierung eines Boilerplate-Codes durch Bereitstellung einer zentralisierten Logik für die Erstellung und Ausführung von Abfragen .

Es ist dem Spring Data-Projekt sehr ähnlich. Um eine Datenbank abzufragen, müssen wir eine Methodendeklaration (ohne Implementierung) definieren, die der definierten Namenskonvention folgt oder die @Query- Annotation enthält . Die Implementierung erfolgt für uns über die CDI-Erweiterung.

In den nächsten Unterabschnitten erfahren Sie, wie Sie das Apache DeltaSpike Data-Modul in unserer Anwendung einrichten.

2.1. Erforderliche Abhängigkeiten

Um das Apache DeltaSpike Data-Modul in der Anwendung zu verwenden, müssen die erforderlichen Abhängigkeiten eingerichtet werden.

Wenn Maven unser Build-Tool ist, müssen wir Folgendes verwenden:

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

Wenn wir Gradle verwenden:

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

Artefakte des Apache DeltaSpike-Datenmoduls sind in Maven Central verfügbar:

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

Um eine Anwendung mit Datenmodul auszuführen, benötigen wir zur Laufzeit auch JPA- und CDI-Implementierungen .

Obwohl es möglich ist, Apache DeltaSpike in einer Java SE-Anwendung auszuführen, wird es in den meisten Fällen auf dem Anwendungsserver (z. B. Wildfly oder WebSphere) bereitgestellt.

Anwendungsserver bieten volle Unterstützung für Jakarta EE, sodass wir nichts weiter tun müssen. Im Fall einer Java SE-Anwendung müssen wir diese Implementierungen bereitstellen (z. B. durch Hinzufügen von Abhängigkeiten zu Hibernate und JBoss Weld).

Als Nächstes behandeln wir auch die erforderliche Konfiguration für EntityManager .

2.2. Entity Manager-Konfiguration

Für das Datenmodul muss EntityManager über CDI injiziert werden .

Dies können wir mit einem CDI-Produzenten erreichen:

public class EntityManagerProducer { @PersistenceContext(unitName = "primary") private EntityManager entityManager; @ApplicationScoped @Produces public EntityManager getEntityManager() { return entityManager; } }

Der obige Code setzt voraus, dass in der Datei persistence.xml eine Persistenzeinheit mit dem primären Namen definiert ist .

Sehen wir uns unten ein Beispiel für die Definition an:

 java:jboss/datasources/baeldung-jee7-seedDS     

Die Persistenz-Einheit in unserem Beispiel verwendet den Transaktionstyp JTA. Dies bedeutet, dass wir eine Transaktionsstrategie bereitstellen müssen, die wir verwenden werden.

2.3. Transaktionsstrategie

Wenn wir den JTA-Transaktionstyp für unsere Datenquelle verwenden, müssen wir die Transaktionsstrategie definieren, die in den Apache DeltaSpike-Repositorys verwendet wird . Wir können dies in der Datei apache-deltaspike.properties (im META-INF- Verzeichnis) tun :

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

Es gibt vier Arten von Transaktionsstrategien, die wir definieren können:

  • BeanManagedUserTransactionStrategy
  • ResourceLocalTransactionStrategy
  • ContainerManagedTransactionStrategy
  • EnvironmentAwareTransactionStrategy

Alle implementieren org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy .

Dies war der letzte Teil der Konfiguration, der für unser Datenmodul erforderlich war.

Als Nächstes zeigen wir, wie die Repository-Musterklassen implementiert werden.

3. Repository-Klassen

Wenn wir das Apache DeltaSpike-Datenmodul verwenden, kann jede abstrakte Klasse oder Schnittstelle zu einer Repository- Klasse werden.

Alles, was wir tun müssen, ist, eine @ Repository- Annotation mit einem forEntity- Attribut hinzuzufügen , das die JPA-Entität definiert, die unser Repository verarbeiten soll:

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

oder mit einer abstrakten Klasse:

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

Das Datenmodul erkennt Klassen (oder Schnittstellen) mit einer solchen Annotation und verarbeitet darin enthaltene Methoden.

Es gibt nur wenige Möglichkeiten, die auszuführende Abfrage zu definieren. Wir werden in den folgenden Abschnitten kurz nacheinander darauf eingehen.

4. Abfrage vom Methodennamen

Die erste Möglichkeit , eine Abfrage zu definieren, besteht darin, einen Methodennamen zu verwenden, der einer definierten Namenskonvention folgt .

Es sieht wie folgt aus:

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

Als nächstes konzentrieren wir uns auf jeden Teil dieser Definition.

4.1. Rückgabetyp

Der Rückgabetyp definiert hauptsächlich, wie viele Objekte unsere Abfrage zurückgeben kann . Wir können keinen einzelnen Entitätstyp als Rückgabewert definieren, falls unsere Abfrage möglicherweise mehr als ein Ergebnis zurückgibt.

Die folgende Methode löst eine Ausnahme aus, wenn mehr als ein Benutzer mit einem bestimmten Namen vorhanden ist:

public abstract User findByFirstName(String firstName);

The opposite isn't true – we can define a return value as a Collection even though the result will be just a single entity.

public abstract Collection findAnyByFirstName(String firstName);

The method name prefix which suggests one value as a return type (e.g., findAny) is suppressed in case we define return value as Collection.

The above query will return all Users with a first name matching even the method name prefix suggests something different.

Such combinations (Collection return type and a prefix which suggests one single value return) should be avoided because the code becomes not intuitive and hard to understand.

The next section shows more details about method name prefix.

4.2. Prefix for Query Method

Prefix defines the action we want to execute on the repository. The most useful one is to find entities which match given search criteria.

There are many prefixes for this action like findBy, findAny, findAll. For the detailed list, please check official Apache DeltaSpike documentation:

public abstract User findAnyByLastName(String lastName);

However, there are also other method templates which are used for counting and removing entities. We can count all rows in a table:

public abstract int count();

Also, remove method template exists which we can add in our repository:

public abstract void remove(User user);

Support for countBy and removeBy method prefixes will be added in the next version of Apache DeltaSpike 1.9.0.

The next section shows how we can add more attributes to the queries.

4.3. Query With Many Properties

In the query, we can use many properties combined with and operators.

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

We can combine as many properties as we want. Search for nested properties is also available which we'll show next.

4.4. Query With Nested Properties

The query can also use nested properties.

In the following example User entity has an address property of type Address and Address entity has a city property:

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

4.5. Order in the Query

DeltaSpike allows us to define an order in which result should be returned. We can define both – ascending and descending order:

public abstract List findAllOrderByFirstNameAsc();

As shown above all we have to do is to add a part to the method name which contains property name we want to sort by and the short name for the order direction.

We can combine many orders easily:

public abstract List findAllOrderByFirstNameAscLastNameDesc(); 

Next, we'll show how to limit the query result size.

4.6. Limit Query Result Size and Pagination

There are use cases when we want to retrieve few first rows from the whole result. It's so-called query limit. It's also straightforward with Data module:

public abstract Collection findTop2OrderByFirstNameAsc(); public abstract Collection findFirst2OrderByFirstNameAsc();

First and top can be used interchangeably.

We can then enable query pagination by providing two additional parameters: @FirstResult and @MaxResult:

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

We defined already a lot of methods in the repository. Some of them are generic and should be defined once and use by each repository.

Apache DeltaSpike provides few basic types which we can use to have a lot of methods out of the box.

In the next section, we'll focus on how to do this.

5. Basic Repository Types

To get some basic repository methods, our repository should extend basic type provided by Apache DeltaSpike. There are some of them like EntityRepository, FullEntityRepository, etc.:

@Repository public interface UserRepository extends FullEntityRepository { // ... }

Or using an abstract class:

@Repository public abstract class UserRepository extends AbstractEntityRepository { // ... } 

The above implementation gives us a lot of methods without writing additional lines of code, so we gained what we wanted – we reduce boilerplate code massively.

In case we're using base repository type there's no need to pass an additional forEntity attribute value to our @Repository annotation.

When we're using abstract classes instead of interfaces for our repositories we get an additional possibility to create a custom query.

Abstract base repository classes, e.g., AbstractEntityRepository gives us an access to fields (via getters) or utility methods which we can use to create a query:

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

In the above example, we used a typedQuery utility method to create a custom implementation.

The last possibility to create a query is to use @Query annotation which we will show next.

6. @Query Annotation

The SQL query to execute can also be defined with the @Query annotation. It's very similar to the Spring solution. We have to add an annotation to the method with SQL query as a value.

By default this is a JPQL query:

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

As in the above example, we can easily pass parameters to the query via an index.

In case we want to pass query via native SQL instead of JPQL we need to define additional query attribute – isNative with true value:

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

7. Conclusion

In this article, we covered the basic definition of Apache DeltaSpike, and we focused on the exciting part – Data module. It's very similar to the Spring Data Project.

We explored how to implement the repository pattern. We also introduced three possibilities how to define a query to execute.

Wie immer sind die vollständigen Codebeispiele in diesem Artikel auf Github verfügbar.