How does JPA Streamer work?
JPA Streamer uses annotation processing to generate a meta-model at compile time for all classes marked with @Entity, which can be interpreted and used by the query optimizer of JPA Streamer.
What does this look like in the code?
Our sample entity book (getter/setter not included):
@Entity(name = "t_book")
public class Book {
private static final String SEQ_GENERATOR = "SEQ_BOOK_ID";
private static final String SEQUENCE = "SEQ_BOOK";
@Id
@GeneratedValue(generator = SEQ_GENERATOR)
@SequenceGenerator(name = SEQ_GENERATOR, sequenceName = SEQUENCE, allocationSize = 1)
@Column(name = "book_id")
private long id;
@Column(name = "title")
private String title;
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Page> pages;
@Column(name = "page_count", nullable = false)
private int pageCount;
@Column(name = "book_character")
@ManyToMany(cascade = { CascadeType.ALL })
@JoinTable(
name = "t_book_book_character",
joinColumns = { @JoinColumn(name = "book_id") },
inverseJoinColumns = { @JoinColumn(name = "book_character_id") }
)
private List<BookCharacter> bookCharacters = new ArrayList<>();
}
The class receives an additional Book$ from the JPA streamer:
public final class Book$ {
/**
* This Field corresponds to the {@link Book} field "pages".
*/
public static final ReferenceField<Book, List<Page>> pages = ReferenceField.create(
Book.class,
"pages",
Book::getPages,
false
);
/**
* This Field corresponds to the {@link Book} field "id".
*/
public static final LongField<Book> id = LongField.create(
Book.class,
"id",
Book::getId,
false
);
/**
* This Field corresponds to the {@link Book} field "pageCount".
*/
public static final IntField<Book> pageCount = IntField.create(
Book.class,
"pageCount",
Book::getPageCount,
false
);
/**
* This Field corresponds to the {@link Book} field "title".
*/
public static final StringField<Book> title = StringField.create(
Book.class,
"title",
Book::getTitle,
false
);
/**
* This Field corresponds to the {@link Book} field "bookCharacters".
*/
public static final ReferenceField<Book, List<BookCharacter>> bookCharacters = ReferenceField.create(
Book.class,
"bookCharacters",
Book::getBookCharacters,
false
);
}
What is the benefit of this?
This Book$ class can now be used to utilize JPA queries via streaming API. This can help to write queries faster and easier.
Let's start with a simple example: We want to find out how many book titles begin with "G".
The SQL could look like this:
SELECT COUNT(BOOK_ID) FROM T_BOOK WHERE TITLE LIKE 'G%';
Implementation with the CriteriaBuilder (Hibernate provides the implementation):
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
Root<Book> bookRoot = criteriaQuery.from(Book.class);
criteriaQuery.select(criteriaBuilder.count(bookRoot));
criteriaQuery.where(criteriaBuilder.like(bookRoot.get("title"), "G%"));
Long count = em.createQuery(criteriaQuery).getSingleResult();
The same example with the JPA streamer library:
JPAStreamer streamer = JPAStreamer.of(em.getEntityManagerFactory());
long count = streamer.stream(Book.class)
.filter(Book$.title.startsWith("G"))
.count();
Please note that the class generated by JPA Streamer is used in the filter, otherwise the whole thing will not work.
In both cases, the following query is generated during execution:
select count(b1_0.book_id) from t_book b1_0 where b1_0.title like ?
However, the JPA streamer example is much leaner and much easier to understand.
Another, somewhat more complex example of projections (projections are always used when not all fields are required): Instead of books, we only want to select the book titles that start with D. The list should be sorted.
The SQL for this:
SELECT TITLE FROM T_BOOK WHERE TITLE LIKE 'D%' ORDER BY TITLE;
Here again the implementation with the CriteriaBuilder:
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.class);
Root<Book> bookRoot = criteriaQuery.from(Book.class);
criteriaQuery.select(bookRoot.get("title"));
criteriaQuery.where(criteriaBuilder.like(bookRoot.get("title"), "D%"));
criteriaQuery.orderBy(criteriaBuilder.asc(bookRoot.get("title")));
List<String> bookTitlesSorted = em.createQuery(criteriaQuery).getResultList();
And the implementation with JPA Streamer:
StreamConfiguration<Book> bookTitleConfiguration = StreamConfiguration.of(Book.class)
.selecting(Projection.select(Book$.title));
streamer.stream(bookTitleConfiguration)
.sorted(Book$.title)
.filter(Book$.title.startsWith("D"))
.map(Book::getTitle)
.collect(Collectors.toList());
In both cases, the generated queries are identical to our SQL statement:
select
b1_0.title
from
t_book b1_0
where
b1_0.title like ?
order by
1
As before, the JPA Streamer code reads somewhat more simply than its CriteriaBuilder counterpart.
What else can the JPA Streamer library do?
JPA Streamer supports a variety of database operations. A brief overview:
| SQL | Java Stream |
| FROM | stream() |
| SELECT | map(Projection.select()) |
| WHERE | filter() (before collecting) |
| ORDER BY | sorted() |
| OFFSET | skip() |
| LIMIT | limit() |
| COUNT | count() |
| GROUP BY | collect(groupingBY()) |
| HAVING | filter() (after collecting) |
| DISTINCT | distinct() |
| SELECT | map() |
| UNION | concat(s0, s1).distinct() |
| JOIN | flatmap() |
Further information on the individual operations can be found in the official documentation. In the Github project you will find further examples.
Is the project worthwhile?
JPA Streamer is a powerful library that can simplify the development of JPA applications. The ability to write queries using standard Java stream operators can help make writing queries faster and easier. In addition, the queries are usually easier to read and understand. However, as with the CriteriaBuilder and SQL queries, readability and simplicity also have their limits with increasing complexity. In addition, functions are currently still missing or not all operations are currently supported. The use in the project must therefore always be evaluated individually.
Sources:



