Fetching Data
This section details how to create a basic Stream from your datasource. To initiate the data fetching process, you will need to initialize an instance of JPAStreamer
. Once you have obtained access to JPAStreamer
, you can leverage two main methods to obtain a Stream from your datasource: stream()
and createStreamSupplier()
. These methods provide efficient and customizable approaches to interact with your data. In this chapter, we will discuss what differentiates the two approaches.
Obtaining a JPAStreamer instance
From persistence unit name
The simplest way to initialize JPAstreamer is by providing the name of the persistence unit like so:
JPAStreamer jpaStreamer = JPAStreamer.of("sakila"); (1)
1 | "sakila" is to be replaced with the name of your persistence unit that can be found in a configuration-file |
In the example, the String "sakila" should refer to the name of your persistence unit. Assuming you are already using a JPA provider, your project should contain an XML-file like the one below, describing the persistence unit:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="sakila" transaction-type="RESOURCE_LOCAL"> (1)
<description>MySQL Sakila Example Database</description>
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<!-- Configuring The Database Connection Details -->
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/sakila" />
<!-- ... -->
</properties>
</persistence-unit>
</persistence>
1 | The name of the persistence unit, in this case "sakila", is used to initialize JPAStreamer. |
This configuration is just an example configuration for the MySQL Sakila example database. You should use the configuration you already have in place. |
If you have multiple persistence units, you can initiate several instances of JPAStreamer to establish connections with different sources.
|
JPAStreamer does not need any additional configuration and depends solely on this file to establish a database connection. If your starting a project from scratch, make sure to set up your JPA project before trying to use JPAStreamer.
Having obtained a JPAStreamer
instance, you are ready to go. Here is an example that includes both the instantiation and the querying:
public static void main(String[] args) {
JPAStreamer jpaStreamer = JPAStreamer.createJPAStreamerBuilder("sakila") (1)
.build();
long count = jpaStreamer.stream(Film.class)
.filter(Film$.title.startsWith("A"))
.count();
System.out.format("There are %d films with a title that starts with A", count);
}
From EntityManagagerFactory
When configuring JPAStreamer with the persistence unit name as described above, a new EntityManagerFactory
is created and managed by JPAStreamer. In this case, JPAStreamer is responsible for the life cycle of the factory, and calling JPAStreamer::close
will close the EntityManagerFactory
.
If you rather wish to reuse an existing EntityManagerFactory
you can initialize JPAStreamer as follows:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("sakila");
JPAStreamer jpaStreamer = JPAStreamer.of(emf);
In this case, JPAStreamer is not responsible for clearing up the factory resources and calling JPAStreamer::close will not close the EntityManagerFactory . However, EntityManager instances obtained via the factory are still managed by JPAStreamer.
|
From Supplier<EntityManager>
As a third option, JPAStreamer can be handed a Supplier
of Entity Managers. In this case, JPAStreamer is not responsible for the lifecycle of any supplied Entity Managers. For example:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("sakila");
JPAStreamer jpaStreamer = JPAStreamer.of(emf::createEntityManager);
This is especially useful in contexts where JPAStreamer may not be permitted to create and manage its own EntityManagerFactory
, and/or no reference to an EntityManagerFactory
is present. An example of such an environment is inside a PanacheRepository
when running Hibernate and Panache. PanacheRepository
inherits getEntityManager()
from PanacheEntityBase
, which can be used to supply JPAStreamer with Entity Managers as follows:
@ApplicationScoped
public class FilmRepository implements PanacheRepository<Film> {
private final JPAStreamer jpaStreamer = JPAStreamer.of(this::getEntityManager);
}
When using a Supplier, JPAStreamer is not responsible for the lifecycle of the Entity Managers, thus JPAStreamer::close will not close any supplied Entity Managers.
|
Creating a Stream
To create a Stream, you first need to describe the stream source as a StreamConfiguration
. The StreamConfiguration
declares what entity to use as the base for the query, and e.g. if any joins or projections should be performed. The StreamConfiguration
is then passed to JPAStreamer.stream(StreamConfiguration<T> streamConfiguration)
or JPAStreamer.createStreamSupplier(StreamConfiguration<T> streamConfiguration)
.
When deciding which of these methods to use, consider whether you will reuse the same stream source frequently or not, and if you are expecting that the source will be updated by an external application in between streams. A more detailed explanation follows in the subsequent sections on each method below.
JPAStreamer also offers convenience methods for simple StreamConfigurations
:
-
JPAStreamer.stream(Class<T> entityClass)
-
JPAStreamer.stream(Projection<T> projection)
-
JPAStreamer.createStreamSupplier(Class<T> entityClass)
-
JPAStreamer.createStreamSupplier(Projection<T> projection)
The simplest way of creating a Stream
is to provide a single entity class, creating a Stream
of the single table associated with that JPA entity:
Stream<Film> stream = jpaStreamer.stream(Film.class); (1)
1 | Creates a Stream over the Film-table. Passing the entity class Film.class is equivalent of passing StreamConfiguration.of(Film.class) . |
All options available for StreamConfiguration
is laid out in the table below:
Modifer and type | Method | Description |
---|---|---|
|
|
Creates and returns a new StreamConfiguration that can be used to configure streams. |
|
|
Returns the entity class that is to appear in a future Stream. |
|
|
Returns the fields that shall be joined in a future stream. |
|
|
Creates and returns a new |
|
|
Creates and returns a new |
|
|
Returns the projected columns to use when creating entities or |
|
|
Selects the projected columns to initialize when creating initial entities in a future stream. |
|
|
Adds a query hint. |
|
|
Returns the map with the query hints that will be configured in a future Stream. |
There are many examples of how to use a StreamConfiguration in Stream Examples.
|
stream()
Calls to JPAStreamer.stream(StreamConfiguration<T> streamConfiguration)
will lead to the creation of a StreamSupplier
. The StreamSupplier
obtains a JPA EntityManager
that JPAStreamer uses internally to issue JPA Criteria Queries.
Whenever the Stream
is terminated with a terminal operation, e.g. collect()
, the underlying StreamSupplier
and the EntityManager
is closed and can no longer be used.
final JPAStreamer jpaStreamer = JPAStreamer.of("sakila");
final List<Film> films = jpaStreamer.stream(Film.class) (1)
.filter(Film$.name.equal("Casablanca"))
.collect(toList()); (2)
1 | Creates a StreamSupplier that returns a Stream over the Film-table |
2 | The terminal operation closes the underlying StreamSupplier and its Entity Manager |
createStreamSupplier()
As calls to JPAStreamer.stream()
creates a new StreamSupplier
each time, you can potentially save resources by reusing a single StreamSupplier
for the creation of many streams. A reusable StreamSupplier
can be obtained by calling JPAStreamer.createStreamSupplier()
.
Like JPAStreamer
, the StreamSupplier
provides a method stream()
that returns a Stream
as described by the provided StreamConfiguration
. There is one important distinction between calls to these methods:
-
JPAStreamer.stream()
- the execution of a terminal operation closes the underlyingStreamSupplier
itsEntityManager
-
StreamSupplier.stream()
- the execution of a terminal operation does not close theStreamSupplier
and itsEntityManager
This means repeated calls to StreamSupplier.stream()
will reuse the same EntityManager
. We recommend using a try-with-resources block to automatically close the StreamSupplier
when done with the operations:
final JPAStreamer jpaStreamer = JPAStreamer.of("sakila");
try (final StreamSupplier<Film> streamSupplier = jpaStreamer.createStreamSupplier()) {
final List<Film> shortFilms = streamSupplier.stream(Film.class)
.filter(Film$.length.lessThan(60))
.collect(toList()); (1)
final List<Film> longFilms = streamSupplier.stream(Film.class)
.filter(Film$.length.greatherThanOrEqual(61))
.collect(toList()); (1)
} (2)
1 | The StreamSupplier and the underlying EntityManager stays open when executing the terminal operation |
2 | The StreamSupplier and the underlying EntityManager is closed |
The javax.persistence.EntityManager associated with the StreamSupplier has a first-layer cache. Thus by default, database changes performed by another application, or made directly on the database, may not be detected between calls to StreamSupplier.stream() . To ensure that the cache is cleared between each fetch, use JPAStreamer.stream() instead.
|
If you instantiate JPAStreamer with a Supplier<EntityManager> as described here, JPAStreamer will not close the underlying Entity Manager. In that case the lifecycle of the obtained Entity Managers is managed by the supplier.
|
Using Query Hints
In complex scenarios or when dealing with specific database systems, it may be necessary to provide additional guidance to the underlying JPA provider for optimal query execution. This is where query hints come into play, allowing developers to control and influence various aspects of the query execution process. The query hints influence e.g. the execution plan chosen by the JPA provider, potentially leading to improved query performance or tailored behavior based on specific requirements.
To pass a query hint to the underlying JPA provider with JPAStreamer
, you need to use a StreamConfiguration
. It exposes a method withHint()
that accepts the name and value of the query hint. This method call can be chained to set multiple hints.
StreamConfiguration<T> withHint(final String hintName, final Object value);
The available set of query hints is defined in the JPA specification and the documentation of your underlying JPA provider. Thorben Jansen wrote an excellent blog post on useful query hints available to Hibernate users here. JPAStreamer does not provide any custom query hints. |
Let’s bring query hints into the context of a JPAStreamer query. Here is an example that issues a read-only query with a timeout of 50 ms:
StreamConfiguration sc = StreamConfiguration.of(Film.class)
.withHint("javax.persistence.query.timeout", 50)
.withHint("org.hibernate.readOnly", true);
List<Film> films = jpaStreamer.stream(sc)
.filter(Film$.title.startsWith("A"))
.sorted(Film$.length)
.limit(10)
.collect(Collectors::toList);
While query hints can be powerful tools for query optimization, it’s important to use them carefully and with a clear understanding of their impact. Misusing or overusing query hints can lead to unintended consequences. |
What’s Next
The next section demonstrates how to use the available Stream operators and how they map to SQL constructs.