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:

Table 1. StreamConfiguration
Modifer and type Method Description

StreamConfiguration<T>

of(Class<T> entityClass)

Creates and returns a new StreamConfiguration that can be used to configure streams.

Class<T>

entityClass()

Returns the entity class that is to appear in a future Stream.

Set<JoinConfiguration<T>

joins()

Returns the fields that shall be joined in a future stream.

StreamConfiguration<T>

joining(Field<T> field)

Creates and returns a new StreamConfiguration configured with the provided field so that it will be eagerly joined when producing elements in the future Stream using join type left.

StreamConfiguration<T>

joining(Field<T> field, JoinType joinType)

Creates and returns a new StreamConfiguration configured with the provided field so that it will be eagerly joined when producing elements in the future Stream using the provided join type.

Optional<Projection<T>>

selections()

Returns the projected columns to use when creating entities or Optional.empty() if no projection should be used.

StreamConfiguration<T>

selecting(Projection<T> projection)

Selects the projected columns to initialize when creating initial entities in a future stream.

StreamConfiguration<T>

withHint(String hintName, Object value)

Adds a query hint.

Map<String, Object>

hints()

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 underlying StreamSupplier its EntityManager

  • StreamSupplier.stream() - the execution of a terminal operation does not close the StreamSupplier and its EntityManager

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.