Stream Examples
The section presents several examples that combines the operations described in the previous chapter. All examples are part of the JPAstreamer Demo that can be located here and tested locally on your computer.
The examples use the MySQL example database Sakila. It models a classic movie rental store and contains tables such as Film, Actor, Language etc.
Paging
The following example demonstrates how to serve a request for pages from a GUI or a similar application. The page number (starting with page = 0) and ordering will be given as parameters:
private static final int PAGE_SIZE = 20;
private static List<Film> filmPage(JPAStreamer jpaStreamer, int page, Comparator<Film> comparator) {
return jpaStreamer.stream(Film.class)
.sorted(comparator)
.skip(page * PAGE_SIZE)
.limit(PAGE_SIZE)
.collect(Collectors.toList());
}
This method can e.g. be used to fetch the second page of films sorted by title in descending order:
filmPage(1, Film$.title.reversed());
Partition By
Partitioning is a special case of grouping in which there are only two different classes: false
or true
. Java has its own partitioner that can be used to classify database entities. The example below classifies the films in two different categories: short and long films, where a long film is of length greater than 120 minutes.
Map<Boolean, List<Film>> map = jpaStreamer.stream(Film.class)
.collect(
Collectors.partitioningBy(Film$.length.greaterThan(120))
);
map.forEach((k, v) -> {
System.out.format("long is %5s has %d films%n", k, v.size());
});
This will print:
long is false has 543 films
long is true has 457 films
Joins
Relationships between tables are handled via the JPA provider. Hence, JPAstreamer assumes that the correct relationships are described by the present @Entity
beans. The following examples makes use of the three tables Film
, Actor
and Language
from the Sakila database. The corresponding entities are described below:
@Entity
@Table(name = "film", schema = "sakila")
public class Film {
@Id
@Column(name = "film_id", columnDefinition = "smallint(5)")
private Integer filmId;
@ManyToOne (1)
@JoinColumn(name="language_id", nullable = false)
private Language language;
@ManyToMany(cascade = { CascadeType.ALL }) (2)
@JoinTable(
name = "film_actor",
joinColumns = { @JoinColumn(name = "film_id") },
inverseJoinColumns = { @JoinColumn(name = "actor_id") }
)
List<Actor> actors = new ArrayList<>();
//...
}
1 | A film can only be spoken in one language (simplified reality) |
2 | A film can starr many actors |
@Entity
@Table(name = "language", schema = "sakila")
public class Language {
@Id
@Column(name = "language_id", columnDefinition = "tinyint(5)")
private Integer languageId;
@OneToMany(mappedBy = "language") (1)
private List<Film> films;
//...
}
1 | One language can occur in many films |
@Entity
@Table(name = "actor", schema = "sakila")
public class Actor {
@Id
@Column(name = "actor_id", columnDefinition = "smallint(5)")
private Integer actorId;
@ManyToMany(mappedBy = "actors") (1)
List<Film> films = new ArrayList<>();
//...
}
1 | An actor can starr in many films |
Specifying Stream Joining
So far, we have talked about the relations for joining in entities in other entities (e.g. how an Actor relates to a Film).
It is also important when and how that relation is applied. This can be specified using a StreamConfiguration
as shown hereunder:
import static com.speedment.jpastreamer.streamconfiguration.StreamConfiguration.*;
...
jpaStreamer.stream(of(Film.class).joining(Film$.actors).joining(Film$.language))
.filter(Film$.length.between(100, 120))
.forEach(System.out::println);
This will create a stream where the film actors and the film language is (JoinType.LEFT) joined into the query, thereby avoiding potential "select N + 1" performance problems.
More generally, it is possible to specify the join type (JoinType.INNER, JoinType.LEFT or JoinType.RIGHT
) using the method:
StreamConfiguration<T> joining(Field<T> field, JoinType joinType);
One-to-Many relations
A One-to-Many relationship is defined as a relationship between two tables where a row from a first table can have multiple matching rows in a second table. For example, many films can be in the same language.
The following example maps the languages to a list of all films that are spoken in that language:
Map<Language, Set<Film>> languageFilmMap = jpaStreamer.stream(of(Language.class).joining(Language$.films))
.collect(toMap(
Function.identity(),
Language::getFilms
)
);
In this example, we are joining the Language.films thereby preventing an extra query each time Language::getFilms is invoked.
|
Many-to-One relations
A Many-to-One relationship is defined as a relationship between two tables where many multiple rows from a first table can match the same single row in a second table. For example, a single language may be used in many films.
The following example maps every film with rating PG-13 to its spoken language:
Map<Film, Language> languageMap = jpaStreamer.stream(of(Film.class).joining(Film$.language)) (1)
.filter(Film$.rating.equal("PG-13"))
.collect(
Collectors.toMap(Function.identity(), (2)
Film::getLanguage (3)
)
);
1 | Stream over the Film-table (joining in the field language) |
2 | The identity function assigns the current Film as the key |
3 | Enters the spoken language as the value |
If printed with some formatting this may yield the following output:
ALTER VICTORY: English
APOLLO TEEN: English
AIRPLANE SIERRA: English
...
Many-to-Many relations
A Many-to-Many relationship is defined as a relationship between two tables where many multiple rows from a first table can match multiple rows in a second table. Often a third table is used to form these relations. For example, an actor may participate in several films and a film usually have several actors.
Given the entities above, we can create a filmography that maps every actor to a list of films that they have starred in:
Map<Actor, List<Film>> filmography = jpaStreamer.stream(of(Actor.class).joining(Actor$.films)) (1)
.collect(
Collectors.toMap(Function.identity(), (2)
Actor::getFilms (3)
)
);
1 | Stream over the Actor-table (joining the field films) |
2 | The identity function assigns the current Actor as the key |
3 | Enters the List of films as the value |
If printed with some formatting this may yield the following output:
JENNIFER DAVIS: [GREEDY ROOTS, HANOVER GALAXY, ... ]
ED CHASE: [ARTIST COLDBLOODED, IMAGE PRINCESS, ... ]
NICK WAHLBERG: [JEKYLL FROGMEN, CHISUM BEHAVIOR, ... ]
...
Pivot Data
The following example shows a pivot table of all the actors and the number of films they have participated in for each film rating category (e.g. “PG-13”):
Map<Actor, Map<String, Long>> pivot = jpaStreamer.stream(of(Actor.class).joining(Actor$.films)) (1)
.collect(
groupingBy(Function.identity(), (2)
Collectors.flatMapping(a -> a.getFilms().stream(), (3)
groupingBy(Film::getRating, counting()) (4)
)
)
);
As this is a more advanced example it requires some thinking to understand.
1 | Streams the Actor table (joining the field films) |
2 | Assigns Actor as the key |
3 | Applies a downstream collector which flatmaps all the films which the actor starr in |
4 | The films are grouped according to rating, and the number of films of each rating is counted |
Collectors.flatMapping() is only present in Java 9 and forward.
|
This will produce the following (slightly simplified) output:
MICHAEL BOLGER {PG-13=9, R=3, NC-17=6, PG=4, G=8}
LAURA BRODY {PG-13=8, R=3, NC-17=6, PG=6, G=3}
CAMERON ZELLWEGER {PG-13=8, R=2, NC-17=3, PG=15, G=5}
...