1. Overview
Apache DeltaSpike is a project which provides a collection of CDI extensions for Java projects; it requires a CDI implementation to be available at runtime.
Of course, it can work with the different implementation of CDI – JBoss Weld or OpenWebBeans. It’s also tested on many application servers.
In this tutorial, we’ll focus on one of the best known and useful – Data module.
2. DeltaSpike Data Module Setup
Apache DeltaSpike Data module is used to simplify implementation of the repository pattern. It allows reducing a boilerplate code by providing centralized logic for queries creation and execution.
It’s very similar to the Spring Data project. To query a database, we need to define a method declaration (without implementation) which follows defined naming convention or which contains @Query annotation. The implementation will be done for us by the CDI extension.
In the next subsections, we’ll cover how to setup Apache DeltaSpike Data module in our application.
2.1. Required Dependencies
To use Apache DeltaSpike Data module in the application, we need to setup required dependencies.
When Maven is our build tool we have to use:
<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>
When we’re using Gradle:
runtime 'org.apache.deltaspike.modules:deltaspike-data-module-impl'
compile 'org.apache.deltaspike.modules:deltaspike-data-module-api'
Apache DeltaSpike Data module artifacts are available over on Maven Central:
To run an application with Data module, we also need a JPA and CDI implementations available at runtime.
Although it’s possible to run Apache DeltaSpike in Java SE application, in most cases, it will be deployed on the Application Server (e.g., Wildfly or WebSphere).
Application Servers have full Jakarta EE support, so we don’t have to do anything more. In case of Java SE application, we have to provide these implementations (e.g., by adding dependencies to the Hibernate and JBoss Weld).
Next, we’ll also cover required configuration for EntityManager.
2.2. Entity Manager Configuration
The Data module requires EntityManager to be injected over CDI.
We can achieve this by using a CDI producer:
public class EntityManagerProducer {
@PersistenceContext(unitName = "primary")
private EntityManager entityManager;
@ApplicationScoped
@Produces
public EntityManager getEntityManager() {
return entityManager;
}
}
The above code assumes that we have persistence unit with name primary defined in the persistence.xml file.
Let’s see below as an example of definition:
<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>
The persistence unit in our example uses JTA transaction type which means we have to provide a Transaction Strategy we’re going to use.
2.3. Transaction Strategy
In case we’re using JTA transaction type for our data source then we have to define Transaction Strategy that will be used in the Apache DeltaSpike repositories. We can do it inside apache-deltaspike.properties file (under META-INF directory):
globalAlternatives.org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy=org.apache.deltaspike.jpa.impl.transaction.ContainerManagedTransactionStrategy
There are four types of transaction strategy we can define:
- BeanManagedUserTransactionStrategy
- ResourceLocalTransactionStrategy
- ContainerManagedTransactionStrategy
- EnvironmentAwareTransactionStrategy
All of them implement org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy.
This was the last part of the configuration required for our data module.
Next, we’ll show how to implement the repository pattern classes.
3. Repository Classes
When we’re using Apache DeltaSpike data module any abstract class or interface can become a repository class.
All we have to do is to add an @Repository annotation with a forEntity attribute which defines JPA entity that our repository should handle:
@Entity
public class User {
// ...
}
@Repository(forEntity = User.class)
public interface SimpleUserRepository {
// ...
}
or with an abstract class:
@Repository(forEntity = User.class)
public abstract class SimpleUserRepository {
// ...
}
Data module discovers classes (or interfaces) with such an annotation and it’ll process methods which are inside.
There are few possibilities to define the query to execute. We’ll cover one by one shortly in the following sections.
4. Query From Method Name
The first possibility to define a query is to use method name which follows a defined naming convention.
It looks like below:
(Entity|Optional<Entity>|List<Entity>|Stream<Entity>) (prefix)(Property[Comparator]){Operator Property [Comparator]}
Next, we’ll focus on each part of this definition.
4.1. Return Type
The return type mainly defines how many objects our query might return. We cannot define single entity type as a return value in case our query might return more than one result.
The following method will throw an exception in case there is more than one User with given name:
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<User> 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<User> findByFirstNameAndLastName(
String firstName, String lastName);
public abstract Collection<User> 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<User> 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<User> 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<User> 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<User> findTop2OrderByFirstNameAsc();
public abstract Collection<User> 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<User> 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<User, Long> {
// ...
}
Or using an abstract class:
@Repository
public abstract class UserRepository extends AbstractEntityRepository<User, Long> {
// ...
}
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<User> 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<User> 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<User> 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.
As always, the complete code examples used in this article are available over on Github.