blob: 5ea0ed59654b20fe0b48ad1314c7c93705e2a4ee [file] [log] [blame]
:moduledeps: core, jpa, partial-bean
= Data Module
:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at. http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
== Overview
The Data module provides capabilities for implementing repository patterns and thereby simplifying the repository layer. Repository patterns are ideal for simple queries that require boilerplate code, enabling centralization of query logic and consequently reducing code duplication and improving testability.
The code sample below gives you a quick overview on the common usage
scenarios of the data module:
[source,java]
----------------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
List<Person> findByAgeBetweenAndGender(int minAge, int maxAge, Gender gender);
@Query("select p from Person p where p.ssn = ?1")
Person findBySSN(String ssn);
@Query(named=Person.BY_FULL_NAME)
Person findByFullName(String firstName, String lastName);
}
----------------------------------------------------------------------------------
As you see in the sample, there are several usage scenarios outlined
here:
* Declare a method which executes a query by simply translating its name
and parameters into a query.
* Declare a method which automatically executes a given JPQL query
string with parameters.
* Declare a method which automatically executes a named query with
parameters.
The implementation of the method is done automatically by the CDI
extension. A client can declare a dependency to the interface only. The
details on how to use those features are outlined in the following
chapters.
== Project Setup
The configuration information provided here is for Maven-based projects and it assumes that you have already declared the DeltaSpike version and DeltaSpike Core module for your projects, as detailed in <<configure#, Configure DeltaSpike in Your Projects>>. For Maven-independent projects, see <<configure#config-maven-indep,Configure DeltaSpike in Maven-independent Projects>>.
=== 1. Declare Data Module Dependencies
Add the Data module to the list of dependencies in the project `pom.xml` file using this code snippet:
[source,xml]
----
<dependency>
<groupId>org.apache.deltaspike.modules</groupId>
<artifactId>deltaspike-data-module-api</artifactId>
<version>${deltaspike.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.deltaspike.modules</groupId>
<artifactId>deltaspike-data-module-impl</artifactId>
<version>${deltaspike.version}</version>
<scope>runtime</scope>
</dependency>
----
Or if you're using Gradle, add these dependencies to your `build.gradle`:
[source]
----
runtime 'org.apache.deltaspike.modules:deltaspike-data-module-impl'
compile 'org.apache.deltaspike.modules:deltaspike-data-module-api'
----
=== 2. Complete Additional Java Environment Configuration
The Data module requires a JPA implementation to be available in the Java environment where your projects are deployed.
The simplest way using the DeltaSpike Data module is to run your
application in a Java EE container supporting at least the Java EE6 Web
Profile. Other configurations like running it inside Tomcat or even a
Java SE application should be possible - you need to include a JPA
provider as well as a CDI container to your application manually.
As of DeltaSpike v1.4.0, the Data module internally leverages the Proxy module, which wraps ASM 5. No external
dependencies required, and now we have full support for interceptors on partial beans.
=== 3. Complete Additional Project Configuration
DeltaSpike Data requires an `EntityManager` exposed via a CDI producer -
which is common practice in Java EE6 applications.
[source,java]
------------------------------------------------------
public class EntityManagerProducer
{
@PersistenceUnit
private EntityManagerFactory emf;
@Produces // you can also make this @RequestScoped
public EntityManager create()
{
return emf.createEntityManager();
}
public void close(@Disposes EntityManager em)
{
if (em.isOpen())
{
em.close();
}
}
}
------------------------------------------------------
This allows the `EntityManager` to be injected over CDI instead of only
being used with a `@PersistenceContext` annotation. Using multiple
`EntityManager` is explored in more detail in a following section.
If you use a JTA DataSource with your `EntityManager`, you also have to
configure the `TransactionStrategy` your repositories use. Adapt your
`beans.xml` for this:
[source,xml]
----
<beans>
<alternatives>
<class>org.apache.deltaspike.jpa.impl.transaction.BeanManagedUserTransactionStrategy</class>
</alternatives>
</beans>
----
IMPORTANT: Using the DeltaSpike Data module in an EAR deployment is currently restricted to
annotation-based entities.
== Core Concepts
=== Repositories
With the DeltaSpike Data module, it is possible to make a repository out
of basically any abstract class or interface (using a concrete class
will work too, but you will not be able to use most of the CDI extension
features). All that is required is to mark the type as such with a
simple annotation:
[source,java]
----------------------------------------
@Repository(forEntity = Person.class)
public abstract class PersonRepository {
...
}
@Repository(forEntity = Person.class)
public interface PersonRepository {
...
}
----------------------------------------
The `@Repository` annotation tells the extension that this is a
repository for the `Person` entity. Any method defined on the repository
will be processed by the framework. The annotation does not require to
set the entity class (we'll see later why) but if there are just plain
classes or interfaces this is the only way to tell the framework what
entity the repository relates to. In order to simplify this, DeltaSpike
Data provides several base types.
==== The `EntityRepository` Interface
Although mainly intended to hold complex query logic, working with both
a repository and an `EntityManager` in the service layer might
unnecessarily clutter code. In order to avoid this for the most common
cases, DeltaSpike Data provides base types which can be used to replace
the entity manager.
The top base type is the `EntityRepository` interface, providing common
methods used with an `EntityManager`. The following code shows the most
important methods of the interface:
[source,java]
-------------------------------------------------------------------------
public interface EntityRepository<E, PK extends Serializable>
{
E save(E entity);
void remove(E entity);
void refresh(E entity);
void flush();
E findBy(PK primaryKey);
List<E> findAll();
List<E> findBy(E example, SingularAttribute<E, ?>... attributes);
List<E> findByLike(E example, SingularAttribute<E, ?>... attributes);
Long count();
Long count(E example, SingularAttribute<E, ?>... attributes);
Long countLike(E example, SingularAttribute<E, ?>... attributes);
}
-------------------------------------------------------------------------
The concrete repository can then extend this basic interface. For our
Person repository, this might look like the following:
[source,java]
------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
Person findBySsn(String ssn);
}
------------------------------------------------------------------------
TIP: Annotations on interfaces do not inherit. If the `EntityRepository`
interface is extended by another interface adding some more common
methods, it is not possible to simply add the annotation there. It needs
to go on each concrete repository. The same is not true if a base class
is introduced, as we see in the next chapter.
===== The AbstractEntityRepository Class
This class is an implementation of the `EntityRepository` interface and
provides additional functionality when custom query logic needs also to
be implemented in the repository.
[source,java]
-------------------------------------------------------------------------------------
public abstract class PersonRepository extends AbstractEntityRepository<Person, Long>
{
public List<Person> findBySSN(String ssn)
{
return typedQuery("select p from Person p where p.ssn = ?1")
.setParameter(1, ssn)
.getResultList();
}
}
-------------------------------------------------------------------------------------
=== Deactivating Repositories
Repositories can be deactivated creating a <<spi.adoc#_classdeactivator,ClassDeactivator>>.
The `EntityRepository` interface implements the <<core.adoc#_deactivatable,Deactivatable>> interface allowing it to be used in the ClassDeactivator.
If your repository does not implement `EntityRepository` and you want to deactivate it, you will need to implement the <<core.adoc#_deactivatable,Deactivatable>> interface yourself.
[source,java]
----------------------------------------
@Repository(forEntity = Person.class)
public abstract class PersonRepository implements Deactivatable {
...
}
@Repository(forEntity = Person.class)
public interface PersonRepository extends Deactivatable {
...
}
----------------------------------------
=== Support of @TransactionScoped EntityManagers
For using `@TransactionScoped` beans like a `@TransactionScoped`-`EntityManager`,
you need to annotate the Data-repository with @Transactional explicitly or one of the beans in the call-hierarchy.
That's needed, because the context bound to `@TransactionScoped` needs to be active,
before the `@TransactionScoped`-`EntityManager` gets resolved (internally).
The following examples illustrate the described usages:
.@TransactionScoped EntityManager combined with a simple repository
[source,java]
---------------------------------------------------------------------------------------
public class EntityManagerProducer
{
@Produces
@TransactionScoped
public EntityManager create() { ... }
public void close(@Disposes EntityManager em) { ... }
}
@ApplicationScoped
public class MyService
{
@Inject
private MyRepository myRepository;
public void create()
{
//...
this.myRepository.save(...); //executed in a transaction
//...
}
}
@Transactional
@Repository
public interface MyRepository extends EntityRepository<MyEntity, String>
{
//...
}
---------------------------------------------------------------------------------------
.@TransactionScoped EntityManager combined with a simple repository called by a transactional bean
[source,java]
---------------------------------------------------------------------------------------
public class EntityManagerProducer
{
@Produces
@TransactionScoped
public EntityManager create() { ... }
public void close(@Disposes EntityManager em) { ... }
}
@Transactional
@ApplicationScoped
public class MyService
{
@Inject
private MyRepository myRepository;
public void create() //executed in a transaction
{
//...
this.myRepository.save(...);
//...
}
}
@Repository
public interface MyRepository extends EntityRepository<MyEntity, String>
{
//...
}
---------------------------------------------------------------------------------------
=== Using Multiple EntityManagers
While most applications will run just fine with a single
`EntityManager`, there might be setups where multiple data sources are
used. This can be configured with the `EntityManagerConfig` annotation:
[source,java]
--------------------------------------------------------------------------------------------------------------
@Repository
@EntityManagerConfig(entityManagerResolver = CrmEntityManagerResolver.class, flushMode = FlushModeType.COMMIT)
public interface PersonRepository extends EntityRepository<Person, Long>
{
...
}
public class CrmEntityManagerResolver implements EntityManagerResolver
{
@Inject @CustomerData // Qualifier - assumes a producer is around...
private EntityManager em;
@Override
public EntityManager resolveEntityManager()
{
return em;
}
}
--------------------------------------------------------------------------------------------------------------
Again, note that annotations on interfaces do not inherit, so it is not
possible to create something like a base `CrmRepository` interface with
the `@EntityManagerConfig` and then extending / implementing this
interface.
=== Other EntityManager Methods
While the `EntityRepository` methods should cover most interactions
normally done with an `EntityManager`, for some specific cases it might
still be useful to have one or the other method available. For this
case, it is possible to extend / implement the `EntityManagerDelegate`
interface for repositories, which offers most other methods available in
a JPA 2.0 `EntityManager`:
[source,java]
-------------------------------------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>, EntityManagerDelegate<Person>
{
...
}
-------------------------------------------------------------------------------------------------------
Alternatively, you can extend the `FullEntityRepository` interface which is a short-hand for extending
all of `EntityRepository`, `EntityManagerDelegate` and `CriteriaSupport`.
[source,java]
-------------------------------------------------------------------------------------------------------
@Repository
public interface PersonRepository extends FullEntityRepository<Person, Long>
{
...
}
-------------------------------------------------------------------------------------------------------
For abstract classes, there is a convenience base class `AbstractFullEntityRepository` which also
implements `EntityManagerDelegate` and `CriteriaSupport` and thus exposes most `EntityManager` methods:
[source,java]
-------------------------------------------------------------------------------------------------------
@Repository
public abstract PersonRepository extends AbstractFullEntityRepository<Person, Long>
{
...
}
-------------------------------------------------------------------------------------------------------
== Query Method Expressions
Good naming is a difficult aspects in software engineering. A good
method name usually makes comments unnecessary and states exactly what
the method does. And with method expressions, the method name is
actually the implementation!
=== Using Method Expressions
Let's start by looking at a (simplified for readability) example:
[source,java]
------------------------------------------------------------------------
@Entity
public class Person
{
@Id @GeneratedValue
private Long id;
private String name;
private Integer age;
private Gender gender;
}
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
List<Person> findByNameLikeAndAgeBetweenAndGender(String name,
int minAge, int maxAge, Gender gender);
long countByName(String name);
void removeByName(String name);
}
------------------------------------------------------------------------
Looking at the method name, this can easily be read as query all Persons
which have a name like the given name parameter, their age is between a
min and a max age and having a specific gender. The DeltaSpike Data
module can translate method names following a given format and directly
generate the query implementation out of it (in EBNF-like form):
----------------------------------------------------------------------------------
(Entity|Optional<Entity>|List<Entity>|Stream<Entity>) (prefix)(Property[Comparator]){Operator Property [Comparator]}
----------------------------------------------------------------------------------
Or in more concrete words:
* The query method must return an entity, an `Optional` of an entity, a list of entities or a stream of entities.
* It must start with the `findBy` prefix (or related `findOptionalBy`, `findAnyBy`, `findAll`, `findFirst` or `findTop`).
* Followed by a property of the Repository entity and an optional comparator (we'll define this later). The property will be used in the query together with the comparator. Note that the number of arguments passed to the method depend on the comparator.
* You can add more blocks of property-comparator which have to be concatenated by a boolean operator. This is either an `And` or `Or`.
You can also use the same way for delete an entity:
* It must start with the `removeBy` keyword (or related `deleteBy`).
or for counting:
* It must start with the `countBy` keyword.
* It must return a int or long.
Other assumptions taken by the expression evaluator:
* The property name starts lower cased while the property in the expression has an upper cases first character.
Following comparators are currently supported to be used in method
expressions:
[options="header, autowidth"]
|===
| Name |# of Arguments |Description
| Equal |1 | Property must be equal to argument value. If the operator is omitted in the expression, this is assumed as default.
| EqualIgnoreCase |1 | Property must be equal to argument value (case insensitive).
| IgnoreCase |1 | Property must be equal to argument value (case insensitive).
| NotEqual |1 | Property must be not equal to argument value.
| NotEqualIgnoreCase |1 | Property must be not equal to argument value (case insensitive).
| Like |1 | Property must be like the argument value. Use the %-wildcard in the argument.
| LikeIgnoreCase |1 | Property must be like the argument value (case insensitive). Use the %-wildcard in the argument.
| NotLike `*` |1 | Property must be not like the argument value. Use the %-wildcard in the argument.
| GreaterThan |1 | Property must be greater than argument value.
| GreaterThanEquals |1 | Property must be greater than or equal to argument value.
| LessThan |1 | Property must be less than argument value.
| LessThanEquals |1 | Property must be less than or equal to argument value.
| Between |2 | Property must be between the two argument values.
| IsNull |0 | Property must be null.
| IsNotNull |0 | Property must be non-null.
| In `*` |1 | Property must be in the list of values given as a single argument. The argument should be of Collection type (e.g. List, Set, etc.).
| NotIn `*` |1 | Property must be not in the list of values given as a single argument. The argument should be of Collection type (e.g. List, Set, etc.).
| True `*` |0 | Property must be true.
| False `*` |0 | Property must be false.
| Containing `*` |1 | Property must be like the argument value. Don't use the %-wildcard in the argument. The argument value would be automatically wrapped in a pair of %-wildcard.
| StartingWith `*` |1 | Property must begin with the argument value. Don't use the %-wildcard in the argument. A %-wildcard would be added automatically to the end of the argument value.
| EndingWith `*` |1 | Property must end with the argument value. Don't use the %-wildcard in the argument. A %-wildcard would be added automatically to the start of the argument value.
|===
`*` Comparator available since 1.9.1
Note that DeltaSpike will validate those expressions during startup, so
you will notice early in case you have a typo in those expressions.
Also note that as of 1.7, the Entity type returned can either by the
entity defined in this repository, or any other entity in your persistence
unit, or any primitive type supported by your JPA implementation.
=== Query Ordering
Beside comparators it is also possible to sort queries by using the
`OrderBy` keyword, followed by the attribute name and the direction
(`Asc` or `Desc`).
[source,java]
------------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
List<Person> findByLastNameLikeOrderByAgeAscLastNameDesc(String lastName);
}
------------------------------------------------------------------------------
=== Query Limits
Starting with Apache DeltaSpike 1.6.2, you can apply query limits using method
expressions. They can be applied using `findFirst` or `findTop` prefixes in a
method like this:
[source,java]
------------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
List<Person> findFirst2ByLastNameOrderByAgeAscLastNameDesc(String lastName);
List<Person> findTop2ByLastNameOrderByAgeAscLastNameDesc(String lastName);
}
------------------------------------------------------------------------------
The number following the prefix indicates how many to limit by, when setting
the `maxResults` attribute of the underlying query. You can pair this with
a `@FirstResult` parameter to give consistent paging.
Query Limits can be applied even when there is no where clause defined by your
query.
[source,java]
------------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
List<Person> findAllOrderByAgeAsc();
List<Person> findTop20OrderByLastNameDesc();
}
------------------------------------------------------------------------------
The first query finding all entries ordered by age, and the second only 20
ordered by last name.
=== Nested Properties
To create a comparison on a nested property, the traversal parts can be
separated by a `_`:
[source,java]
------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
List<Person> findByCompany_companyName(String companyName);
}
------------------------------------------------------------------------
=== Query Options
DeltaSpike supports query options on method expressions. If you want to
page a query, you can change the first result as well as the maximum
number of results returned:
[source,java]
-----------------------------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
List<Person> findByNameLike(String name, @FirstResult int start, @MaxResults int pageSize);
}
-----------------------------------------------------------------------------------------------
=== Method Prefix
In case the `findBy` prefix does not comply with your team conventions,
this can be adapted:
[source,java]
--------------------------------------------------------------------------------------------------
@Repository(methodPrefix = "fetchWith")
public interface PersonRepository extends EntityRepository<Person, Long>
{
List<Person> fetchWithNameLike(String name, @FirstResult int start, @MaxResults int pageSize);
}
--------------------------------------------------------------------------------------------------
== Query Annotations
While method expressions are fine for simple queries, they will often
reach their limit once things get slightly more complex. Another aspect
is the way you want to use JPA: The recommended approach using JPA for
best performance is over named queries. To help incorporate those use
cases, the DeltaSpike Data module supports also annotating methods for
more control on the generated query.
=== Using Query Annotations
The simplest way to define a specific query is by annotating a method and
providing the JPQL query string which has to be executed. In code, this
looks like the following sample:
[source,java]
------------------------------------------------------------------------
public interface PersonRepository extends EntityRepository<Person, Long>
{
@Query("select count(p) from Person p where p.age > ?1")
Long countAllOlderThan(int minAge);
}
------------------------------------------------------------------------
The parameter binding in the query corresponds to the argument index in
the method.
You can also refer to a named query which is constructed and executed
automatically. The `@Query` annotation has a named attribute which
corresponds to the query name:
[source,java]
--------------------------------------------------------------------------------------------
@Entity
@NamedQueries({
@NamedQuery(name = Person.BY_MIN_AGE,
query = "select count(p) from Person p where p.age > ?1 order by p.age asc")
})
public class Person
{
public static final String BY_MIN_AGE = "person.byMinAge";
...
}
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
@Query(named = Person.BY_MIN_AGE)
Long countAllOlderThan(int minAge);
}
--------------------------------------------------------------------------------------------
Same as before, the parameter binding corresponds to the argument index
in the method. If the named query requires named parameters to be used,
this can be done by annotating the arguments with the `@QueryParam`
annotation.
TIP: Java does not preserve method parameter names (yet), that's why the
annotation is needed.
[source,java]
---------------------------------------------------------------------------------------------
@NamedQuery(name = Person.BY_MIN_AGE,
query = "select count(p) from Person p where p.age > :minAge order by p.age asc")
...
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
@Query(named = Person.BY_MIN_AGE)
Long countAllOlderThan(@QueryParam("minAge") int minAge);
}
---------------------------------------------------------------------------------------------
It is also possible to set a native SQL query in the annotation. The
`@Query` annotation has a native attribute which flags that the query is
not JPQL but plain SQL:
[source,java]
------------------------------------------------------------------------------------
@Entity
@Table(name = "PERSON_TABLE")
public class Person
{
...
}
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
@Query(value = "SELECT * FROM PERSON_TABLE p WHERE p.AGE > ?1", isNative = true)
List<Person> findAllOlderThan(int minAge);
}
------------------------------------------------------------------------------------
=== Annotation Options
Beside providing a query string or reference, the `@Query` annotation
provides also two more attributes:
[source,java]
--------------------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
@Query(named = Person.BY_MIN_AGE, max = 10, lock = LockModeType.PESSIMISTIC_WRITE)
List<Person> findAllForUpdate(int minAge);
}
--------------------------------------------------------------------------------------
[options="header, autowidth"]
|===
| Name | Description
| max | Limits the number of results.
| lock | Use a specific LockModeType to execute the query.
|===
Note that these options can also be applied to method expressions.
=== Query Options
All the query options you have seen so far are more or less static. But
sometimes you might want to apply certain query options dynamically. For
example, sorting criteria could come from a user selection so they
cannot be known beforehand. DeltaSpike allows you to apply query options
at runtime by using the `QueryResult` result type:
[source,java]
------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
@Query("select p from Person p where p.age between ?1 and ?2")
QueryResult<Person> findAllByAge(int minAge, int maxAge);
}
------------------------------------------------------------------------
Once you have obtained a `QueryResult`, you can apply further options to
the query:
[source,java]
-----------------------------------------------------------
List<Person> result = personRepository.findAllByAge(18, 65)
.orderAsc("p.lastName", false)
.orderDesc("p.age", false)
.lockMode(LockModeType.WRITE)
.hint("org.hibernate.timeout", Integer.valueOf(10))
.getResultList();
-----------------------------------------------------------
IMPORTANT: Note that sorting is only applicable to method expressions or non-named
queries. For named queries it might be possible, but is currently only
supported for Hibernate, EclipseLink and OpenJPA.
Note that the `QueryResult` return type can also be used with method
expressions.
IMPORTANT: `QueryResult` is based on our internal understanding of your query.
DeltaSpike expects the alias used in your queries to refer to the entity as `e`
You can disable this behavior by passing in false with your attribute, `.orderDesc("p.age", false)`
which would add descending ordering to your existing query `select p from Person p`
=== Pagination
We introduced the `QueryResult` type in the last chapter, which can also
be used for pagination:
[source,java]
-----------------------------------------------------------
// Query API style
QueryResult<Person> paged = personRepository.findByAge(age)
.maxResults(10)
.firstResult(50);
// or paging style
QueryResult<Person> paged = personRepository.findByAge(age)
.withPageSize(10) // equivalent to maxResults
.toPage(5);
int totalPages = paged.countPages();
-----------------------------------------------------------
=== Bulk Operations
While reading entities and updating them one by one might be fine for
many use cases, applying bulk updates or deletes is also a common usage
scenario for repositories. DeltaSpike supports this with a special
marking annotation `@Modifying`:
[source,java]
------------------------------------------------------------------------------
@Repository
public interface PersonRepository extends EntityRepository<Person, Long>
{
@Modifying
@Query("update Person as p set p.classifier = ?1 where p.classifier = ?2")
int updateClassifier(Classifier current, Classifier next);
}
------------------------------------------------------------------------------
Bulk operation query methods can either return void or int, which counts
the number of entities affected by the bulk operation.
=== Optional Query Results
The JPA spec requires to throw exceptions in case the
`getSingleResult()` method does either return no or more than one
result. This can result in tedious handling with try-catch blocks or
have potential impact on your transaction (as the `RuntimeException`
might roll it back).
DeltaSpike Data gives the option to change this to the way it makes most
sense for the current usecase. While the default behavior is still fully
aligned with JPA, it is also possible to request optional query results.
=== Zero or One Result
With this option, the query returns `null` instead of throwing a
`NoResultException` when there is no result returned. It is usable with
method expressions, `Query` annotations and `QueryResult<E>` calls.
[source,java]
----------------------------------------------------------------------------
@Repository(forEntity = Person.class)
public interface PersonRepository
{
Person findOptionalBySsn(String ssn);
@Query(named = Person.BY_NAME, singleResult = SingleResultType.OPTIONAL)
Person findByName(String firstName, String lastName);
}
----------------------------------------------------------------------------
For method expressions, the `findOptionalBy` prefix can be used. For
`@Query` annotations, the `singleResult` attribute can be overridden
with the `SingleResultType.OPTIONAL` enum.
In case the query returns more than one result, a
`NonUniqueResultException` is still thrown.
=== Any Result
If the caller does not really mind what kind if result is returned, it is
also possible to request any result from the query. If there is no
result, same as for optional queries `null` is returned. In case there
is more than one result, any result is returned, or more concretely the
first result out of the result list.
[source,java]
-----------------------------------------------------------------------
@Repository(forEntity = Person.class)
public interface PersonRepository
{
Person findAnyByLastName(String lastName);
@Query(named = Person.BY_NAME, singleResult = SingleResultType.ANY)
Person findByName(String firstName, String lastName);
}
-----------------------------------------------------------------------
For method expressions, the `findAnyBy` prefix can be used. For `@Query`
annotations, the `singleResult` attribute can be overridden with the
`SingleResultType.ANY` enum.
This option will not throw an exception.
=== Java 8 Semantics
Repositories support returning instances of `java.util.Optional` and `java.util.stream.Stream` for any method.
[source,java]
-----------------------------------------------------------------------
@Repository(forEntity = Person.class)
public interface PersonRepository
{
Optional<Person> findBySsn(String ssn);
Stream<Person> findByLocation(String location);
}
-----------------------------------------------------------------------
Queries returning `Optional<T>` will behave like `SingleResultType.OPTIONAL`, if the data is present, return the single
result, otherwise return `Optional.empty()`. You can override this by using `SingleResultType.ANY` which takes the first
result of the list, or else `empty()`.
Queries returning `Stream<T>` act as a simple wrapper for `query.getResultList().stream()` to give back the results.
=== Entity Graphs
EntityGraphs are a feature added in JPA 2.1. The Data module supports entity graphs for query operations, where the results
will be limited based on a defined graph. This feature is only available if you are using a JPA 2.1 implementation.
`@EntityGraph` can be used for either `fetch` or `load` operations, depending on the `EntityGraphType` used in the annotation. Most queries should use the `FETCH` option.
==== Named Graphs
Entity graphs can be selected by name. A `@NamedEntityGraph` should be defined already within your persistence context to leverage this. When this graph is referenced on a repository method, it will be applied to the query.
==== Dyanmically built graphs
If you want to dynamically build a graph, you can do that via the `paths` attribute of the annotation. The paths specified will be added as graph nodes. Each graph node will be used in the select. The format is the full path to the property, based on the property names.
== Transactions
If you call any method expression, `@Query`-annotated method or a method
from the `EntityRepository`, the repository will figure out if a
transaction is needed or not, and if so, if there is already one
ongoing. The Data module uses the `TransactionStrategy` provided by the
http://deltaspike.apache.org/documentation/jpa.html[JPA Module] for this. See the JPA
module documentation for more details.
IMPORTANT: Some containers do not support `BeanManagedUserTransactionStrategy`! As
JTA has still some portability issues even in Java EE 7, it might be
required that you implement your own `TransactionStrategy`. We will
think about providing an acceptable solution for this.
If you need to open a transaction on a concrete repository method, we
currently recommend creating an extension (see next chapter) which uses
`@Transactional` and might look like the following sample.
[source,java]
---------------------------------------------------------------------------------------
public class TxExtension<E> implements DelegateQueryHandler, TxRepository // this is your extension interface
{
@Inject
private EntityManager em;
@Override @Transactional
public List<E> transactional(ListResultCallback callback)
{
return callback.execute();
}
}
---------------------------------------------------------------------------------------
Repositories can then implement the `TxRepository` interface and call
their queries in the `transactional` method (where the callback
implementation can be, for example, in an anonymous class).
== Extensions
=== Query Delegates
While several base interfaces are defined for repositories, there might still be
the odd convenience method that is missing. This is actually intentional
- things should not get overloaded for each and every use case. That's
why in DeltaSpike you can define your own reusable methods.
For example, you might want to use the QueryDsl library in your
repositories:
[source,java]
---------------------------------------------------------
import com.mysema.query.jpa.impl.JPAQuery;
public interface QueryDslSupport
{
JPAQuery jpaQuery();
}
@Repository(forEntity = Person.class)
public interface PersonRepository extends QueryDslSupport
{
...
}
---------------------------------------------------------
=== Implementing the Query Delegate
The first step is to define an interface which contains the extra
methods for your repositories (as shown above):
[source,java]
--------------------------------
public interface QueryDslSupport
{
JPAQuery jpaQuery();
}
--------------------------------
As a next step, you need to provide an implementation for this interface
once. It is also important that this implementation implements the
`DelegateQueryHandler` interface (do not worry, this is just an empty
marker interface):
[source,java]
--------------------------------------------------------------------------------------------
public class QueryDslRepositoryExtension<E> implements QueryDslSupport, DelegateQueryHandler
{
@Inject
private QueryInvocationContext context;
@Override
public JPAQuery jpaQuery()
{
return new JPAQuery(context.getEntityManager());
}
}
--------------------------------------------------------------------------------------------
As you see in the sample, you can inject a `QueryInvocationContext`
which contains utility methods like accessing the current
`EntityManager` and entity class.
Note that, if you define multiple extensions with equivalent method
signatures, there is no specific order in which the implementation is
selected.
== Mapping
While repositories are primarily intended to work with Entities, it
might be preferable in some cases to have an additional mapping layer on
top of them, for example because the Entities are quite complex but the service
layer needs only a limited view on it, or because the Entities are
exposed over a remote interface and there should not be a 1:1 view on
the domain model.
DeltaSpike Data allows to directly plugin in such a mapping mechanism
without the need to specify additional mapping methods:
[source,java]
----------------------------------------------------
@Repository(forEntity = Person.class)
@MappingConfig(PersonDtoMapper.class)
public interface PersonRepository
{
PersonDto findBySsn(String ssn);
List<PersonDto> findByLastName(String lastName);
}
----------------------------------------------------
The `PersonDtoMapper` class has to implement the `QueryInOutMapper`
interface:
[source,java]
---------------------------------------------------------------------------------
public class PersonDtoMapper implements QueryInOutMapper<Person>
{
@Override
public Object mapResult(Person result)
{
... // converts Person into a PersonDto
}
...
@Override
public Object mapResultList(List<Person> result)
{
... // result lists can also be mapped into something different
// than a collection.
}
@Override
public boolean mapsParameter(Object parameter)
{
return parameter != null && (
parameter instanceof PersonDto || parameter instanceof PersonId);
}
@Override
public Object mapParameter(Object parameter)
{
... // converts query parameters if required
}
}
---------------------------------------------------------------------------------
The mapper can also be used to transform query parameters. Parameters
are converted before executing queries and calling repository
extensions.
Note that those mapper classes are treated as CDI Beans, so it is
possible to use injection in those beans (e.g. you might inject an
`EntityManager` or other mappers). As the `@MappingConfig` refers to the
mapper class directly, the mapper must be uniquely identifiable by its
class.
It is also possible to combine mappings with the base Repository classes:
[source,java]
-------------------------------------------------------------------------------
@Repository(forEntity = Person.class)
@MappingConfig(PersonDtoMapper.class)
public interface PersonRepository extends EntityRepository<PersonDto, PersonId>
{
...
}
-------------------------------------------------------------------------------
In this case, the `forEntity` attribute in the `@Repository` annotation
is mandatory. Also it is up to the mapper to convert parameters
correctly (in this example, a conversion from a `PersonDto` parameter to
`Person` entity and from `PersonId` to `Long` is necessary).
=== Simple Mappings
In many cases it is just required to map a DTO object back and forth. For
this case, the `SimpleQueryInOutMapperBase` class can be subclassed,
which only requires to override three methods:
[source,java]
-------------------------------------------------------------------------------
public class PersonMapper extends SimpleQueryInOutMapperBase<Person, PersonDto>
{
@Override
protected Object getPrimaryKey(PersonDto dto)
{
return dto.getId();
}
@Override
protected PersonDto toDto(Person entity)
{
...
}
@Override
protected Person toEntity(Person entity, PersonDto dto) {
...
return entity;
}
}
-------------------------------------------------------------------------------
The first method, `getPrimaryKey`, identifies the primary key of an
incoming DTO (this might need mapping too). If there is a primary key in
the DTO, Data tries to retrieve the Entity and feed it to the `toEntity`
method, so the entity to be mapped is **attached to the persistence
context**. If there is no primary key, a new instance of the Entity is
created. In any case, there is no need to map the primary key to the
entity (it either does not exist or is already populated for an existing
entity).
== JPA Criteria API Support
Besides automatic query generation, the DeltaSpike Data module also
provides a DSL-like API to create JPA 2 Criteria queries. It takes
advantage of the JPA 2 meta model, which helps creating type safe
queries.
TIP: The JPA meta model can easily be generated with an annotation processor.
Hibernate or EclipseLink provide such a processor, which can be
integrated into your compile and build cycle.
Note that this criteria API is not intended to replace the standard
criteria API - it is rather a utility API that should make life easier on
the most common cases for a custom query. The JPA criteria API's
strongest point is certainly its type safety - which comes at the cost
of readability. We're trying to provide a middle way here. A less
powerful API, but still type safe and readable.
=== API Usage
The API is centered around the Criteria class and is targeted to provide
a fluent interface to write criteria queries:
[source,java]
---------------------------------------------------------------------------
@Repository(forEntity = Person.class)
public abstract class PersonRepository implements CriteriaSupport<Person>
{
public List<Person> findAdultFamilyMembers(String name, Integer minAge)
{
return criteria()
.like(Person_.name, "%" + name + "%")
.gtOrEq(Person_.age, minAge)
.eq(Person_.validated, Boolean.TRUE)
.orderDesc(Person_.age)
.getResultList();
}
}
---------------------------------------------------------------------------
Following comparators are supported by the API:
[options="header,autowidth"]
|===
| Name | Description
| .eq(..., ...) | Property value must be equal to the given value
| .in(..., ..., ..., ...) | Property value must be in one of the given values.
| .notEq(..., ...) | Negates equality
| .like(..., ...) | A SQL `like` equivalent comparator. Use % on the value.
| .notLike(..., ...) | Negates the like value
| .lt(..., ...) | Property value must be less than the given value.
| .ltOrEq(..., ...) | Property value must be less than or equal to the given value.
| .gt(..., ...) | Property value must be greater than the given value.
| .ltOrEq(..., ...) | Property value must be greater than or equal to the given value.
| .between(..., ..., ...) | Property value must be between the two given values.
| .isNull(...) | Property must be `null`
| .isNotNull(...) | Property must be non-`null`
| .isEmpty(...) | Collection property must be empty
| .isNotEmpty(...) |Collection property must be non-empty
|===
The query result can be modified with the following settings:
[options="header,autowidth"]
|===
| Name | Description
| .orderAsc(...) | Sorts the result ascending by the given property. Note that this can be applied to several properties
| .orderDesc(...) | Sorts the result descending by the given property. Note that this can be applied to several properties
| .distinct() | Sets distinct to true on the query.
|===
Once all comparators and query options are applied, the `createQuery()`
method is called. This creates a JPA TypedQuery object for the
repository entity. If required, further processing can be applied here.
=== Joins
For simple cases, restricting on the repository entity only works out
fine, but once the Data model gets more complicated, the query will have
to consider relations to other entities. The module's criteria API
therefore supports joins as shown in the sample below:
[source,java]
-------------------------------------------------------------------------------------
@Repository
public abstract class PersonRepository extends AbstractFullEntityRepository<Person, Long>
{
public List<Person> findByCompanyName(String companyName)
{
return criteria()
.join(Person_.company,
where(Company.class)
.eq(Company_.name, companyName)
)
.eq(Person_.validated, Boolean.TRUE)
.getResultList();
}
}
-------------------------------------------------------------------------------------
Beside the inner and outer joins, also fetch joins are supported. Those
are slighly simpler as seen in the next sample:
[source,java]
-------------------------------------------------------------------------------------
public abstract class PersonRepository extends AbstractFullEntityRepository<Person, Long>
{
public Person findBySSN(String ssn)
{
return criteria()
.fetch(Person_.familyMembers)
.eq(Person_.ssn, ssn)
.distinct()
.getSingleResult();
}
}
-------------------------------------------------------------------------------------
=== Boolean Operators
By default, all query operators are concatenated as an and conjunction
to the query. The DeltaSpike criteria API also allows to add groups of
disjunctions.
[source,java]
-------------------------------------------------------------------------------------
public abstract class PersonRepository extends AbstractFullEntityRepository<Person, Long>
{
public List<Person> findAdults()
{
return criteria()
.or(
criteria().
.gtOrEq(Person_.age, 18)
.eq(Person_.origin, Country.SWITZERLAND),
criteria().
.gtOrEq(Person_.age, 21)
.eq(Person_.origin, Country.USA)
)
.getResultList();
}
}
-------------------------------------------------------------------------------------
=== Selections
It might not always be appropriate to retrieve full entities - you might
also be interested in scalar values or by modified entity attributes.
The Criteria interface allows this with the selection method:
[source,java]
------------------------------------------------------------------------------------------------------
public abstract class PersonRepository extends AbstractFullEntityRepository<Person, Long>
{
public Statistics ageStatsFor(Segment segment)
{
return criteria()
.select(Statistics.class, avg(Person_.age), min(Person_.age), max(Person_.age))
.eq(Person_.segment, segment)
.getSingleResult();
}
public List<Object[]> personViewForFamily(String name)
{
return criteria()
.select(upper(Person_.name), attribute(Person_.age), substring(Person_.firstname, 1))
.like(Person_.name, name)
.getResultList();
}
}
------------------------------------------------------------------------------------------------------
There are also several functions supported which can be used in the
selection clause:
[options="header,autowidth"]
|===
|Name | Description
| abs(...) | Absolute value. Applicable to Number attributes.
| avg(...) | Average value. Applicable to Number attributes.
| count(...) | Count function. Applicable to Number attributes.
| max(...) | Max value. Applicable to Number attributes.
| min(...) | Min value. Applicable to Number attributes.
| modulo(...) | Modulo function. Applicable to Integer attributes.
| neg(...) | Negative value. Applicable to Number attributes.
| sum(...) | Sum function. Applicable to Number attributes.
| lower(...) | String to lowercase. Applicable to String attributes.
| substring(int from, ...) | Substring starting from. Applicable to String attributes.
| substring(int from, int to, ...) | Substring starting from ending to. Applicable to String attributes.
| upper(...) | String to uppercase. Applicable to String attributes.
| currDate() | The DB sysdate. Returns a Date object.
| currTime() | The DB sysdate. Returns a Time object.
| currTStamp() | The DB sysdate. Returns a Timestamp object.
|===
== Auditing
A common requirement for entities is tracking what is being done with
them. DeltaSpike provides a convenient way to support this requirement.
NOTE: DeltaSpike does not support creating revisions of entities. If this is a
requirement for your audits, have a look at Hibernate Envers.
=== Activating Auditing
DeltaSpike uses an entity listener to update auditing data before
entities get created or update. The entity listener must be activated
before it can be used. This can either be done globally for all entities
of a persistent unit or per entity.
Activation per persistence unit in `orm.xml`:
[source,xml]
-----------------------------------------------------------------------------------------------------------------------------------------
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd" version="2.0">
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="org.apache.deltaspike.data.impl.audit.AuditEntityListener" />
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
-----------------------------------------------------------------------------------------------------------------------------------------
Activation per entity:
[source,java]
-------------------------------------------
@Entity
@EntityListeners(AuditEntityListener.class)
public class AuditedEntity
{
...
}
-------------------------------------------
Note that for this variant, you need a compile dependency on the impl
module. Alternatively, also the per entity listener can be configured by
XML.
=== Using Auditing Annotations
All that has to be done now is annotating the entity properties which
are used to audit the entity.
==== Updating Timestamps
To keep track on creation and modification times, following annotations
can be used:
[source,java]
-------------------------------------
@Entity
public class AuditedEntity
{
...
@Temporal(TemporalType.TIMESTAMP)
@CreatedOn
private Date created;
@Temporal(TemporalType.TIMESTAMP)
@ModifiedOn
private Date updated;
...
}
-------------------------------------
In case the modification date should also be set during entity creation,
the annotation can be customized:
-----------------------------
@ModifiedOn(setOnCreate=true)
-----------------------------
==== Who's Changing My Entities?
Beside keeping track of when a change has happened, it is also often
critical to track who's responsible for the change. Annotate a user
tracking field with the following annotation:
[source,java]
-----------------------------
@Entity
public class AuditedEntity
{
...
@ModifiedBy
private String auditUser;
...
}
-----------------------------
Now a little help is needed. The entity listener needs to be able to
resolve the current user - there must be a bean available of the
matching type for the annotation property, exposed over a special CDI
qualifier:
[source,java]
----------------------------------
public class UserProvider
{
@Inject
private User user;
@Produces @CurrentUser
public String currentUser() {
return user.getUsername();
}
...
}
----------------------------------
TIP: The JPA Spec does not recommend to modify entity relations from within a
lifecycle callback. If you expose another entity here, make sure that
your persistence provider supports this. Also you should ensure that the
entity is attached to a persistent context. Also, be aware that the CDI
container will proxy a scoped bean, which might confuse the persistence
provider when persisting / updating the target entity.