Fetching Data

To start fetching data with JPAsStreamer you need to initialize an instance of JPAStreamer. This section describes how that is accomplished.

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.

Resetting the Streamer

Calling jpaStreamer.stream(Class<T> entityClass) creates a dedicated Streamer for the provided Entity class. The Streamer instance is reused for subsequent calls on jpaStreamer.stream() on the same Entity, see example below:

JPAStreamer jpaStreamer = JPAStreamer.createJPAStreamerBuilder("sakila")
        .build();

long count = jpaStreamer.stream(Film.class) (1)
        .filter(Film$.title.startsWith("A"))
        .count();

long count2 = jpaStreamer.stream(Film.class) (2)
        .filter(Film$.title.startsWith("A"))
        .count();
1 The first call to jpaStreamer.stream(Film.class) will create a Streamer of Film entities
2 The second call will reuse the previously configured Streamer

A Streamer instance hold a javax.persistence.EntityManager which has its own first-layer cache. Thus by default, database changes performed by another application, or made directly on the database, will not be detected. In the example above, the addition of the film "Avatar" to the database between the first and second count query therefore goes unnoticed and count will equal count2.

To ensure that database updates performed by another application are detected, you must reset the Streamer between queries. This will effectively remove the existing Streamer for the specified Entity and close its associated EntityManager. The next query will create a new Streamer with a new EntityManager, resetting the first-level cache associated with the Entity.

You can reset the Streamer for one or more Entity classes with the following command:

jpaStreamer.resetStreamer(Class<?>... entityClasses);
JPAStreamer instances configured with a Supplier<EntityManager do not manage the lifecycle of the supplied Entity Managers, and thus cannot close them. Calling resetStreamer on such instance result in an UnsupportedOperationException. See, Initializing JPAStreamer with a Supplier.

We can thus update the prior example to ensure that database changes are detected as follows:

JPAStreamer jpaStreamer = JPAStreamer.createJPAStreamerBuilder("sakila")
        .build();

long count = jpaStreamer.stream(Film.class) (1)
        .filter(Film$.title.startsWith("A"))
        .count();

jpaStreamer.resetStreamer(Film.class); (2)

long count2 = jpaStreamer.stream(Film.class) (3)
        .filter(Film$.title.startsWith("A"))
        .count();
1 Creates a Streamer of Film entities
2 Resets (removes) the Streamer of Film entities. This resets the first-level cache.
3 Creates a new Streamer of Film entities

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.