Skip to content

Tutorial: A First Stream from Speedment

Mislav Milicevic edited this page Nov 4, 2019 · 45 revisions

This tutorial covers some basic stream operations in a Speedment application. The examples are using data from the Sakila film database, which is freely available to download from Oracle.

Step 1: Setup the database

The Sakila database can be downloaded here. Installation instructions are here.

Step 2: Create a new Speedment Maven project

If this is your first time using Speedment, we explain how to set up a new Speedment Maven project in our Quick Start guides Start a New Speedment Maven Project and Connect to Your Database.

Step 3: Write the Application

Previous tutorials describe in detail how to set up a Speedment application. Basically, a simple example boils down to code of the following outline. First the app is built, then the Managers needed for the application logic is fetched from the app and finally, the app is closed when the application logic is completed.

    public static void main(String... params) {
        SakilaApplication app = new SakilaApplicationBuilder()
            .withBundle(MySqlBundle.class)
            .withPassword("MyPassword")
            .build();

        FilmManager films = app.getOrThrow(FilmManager.class);
        ActorManager actors = app.getOrThrow(ActorManager.class);
        FilmActorManager filmActors = app.getOrThrow(FilmActorManager.class);

        // your application logic here

        app.close();
    }

Step 4 Counting the number of items in a stream

As a first application, we count the number of films with the rating PG-13. This is done by filtering the stream with the desired predicate and then counting the items.

        long count = films.stream()
            .filter(Film.RATING.equal("PG-13"))
            .count();

        System.out.format("There are %d PG-13 films in the DB", count);

By adding a logging setting to the application when creating it, we can trace the internal operation of the Speedment runtime. The following application will produce the count of PG-13 films in the database as well as log the SQL operations performed.

    public static void main(String... params) {
        SakilaApplication app = new SakilaApplicationBuilder()
            .withBundle(MySqlBundle.class)
            .withPassword("sakila")
            .withLogging(ApplicationBuilder.LogType.STREAM)
            .build();

        FilmManager films = app.getOrThrow(FilmManager.class);

        long count = films.stream()
            .filter(Film.RATING.equal("PG-13"))
            .count();

        System.out.format("There are %d PG-13 films in the DB", count);

        app.close();
    }

The stream is actually a declarative statement describing a result (the films with rating PG-13) rather than an imperative statement about which operations to perform and in which order. Therefore, the Speedment runtime is free to compute the desired result in any way it sees fit and it turns out that running this piece of code will result in the following SQL query to be executed by the database engine.

SELECT COUNT(*) FROM sakia.film WHERE ('rating'  = 'PG-13' COLLATE utf8_bin)
There are 223 PG-13 films in the DB

Step 5: Basic film lookup via primary key

While the counting of items in the database can be fully delegated to the database engine by Speedment, a typical use case entails in-JVM data operations too. As a first example, consider the following code snippet that finds films using the film ID. Arguably the most basic kind of lookup, the code for accomplishing this task is very straightforward; the stream of films is filtered on ID and then the films of the stream are mapped to the kind of descriptions we are looking for.

        Scanner scn = new Scanner(System.in);
        System.out.println("Please enter Film ID ");
        final int id = Integer.decode(scn.nextLine().trim());

        final Optional<String> title = films.stream()
            .filter(Film.FILM_ID.equal(id))      // find the films we are looking for
            .map(Film.TITLE)                     // switch from a stream of films to one of titles
            .findAny();                          // we want the first and only match for this unique key

        if (title.isPresent()) {
            System.out.format("Film ID %d has title %s.", id, title.get());
        } else {
            System.out.format("Film ID not found.", id);
        }

        app.close();

Proper input validation is left out for clarity.

Step 6: Understanding Stream Optimization

The Speedment runtime framework handles the execution of the Speedment streams and since the stream in the application is a declarative construct the Speedment runtime may optimize the stream in any way it finds useful as long as the final result remains the same.

As an example of such optimization, consider the following code snippet:

long count = films.stream()
    .filter(Film.RATING.equal("PG-13"))
    .filter(Film.LENGTH.greaterOrEqual(75))
    .map(Film.TITLE)
    .sorted()
    .count();

System.out.printf("Found %d films", count);

The result of this stream is a count of items that are filtered by two different predicates, mapped to Strings and finally sorted. The reader probably notes that mapping items from Films to titles of films as well as sorting titles of films will never affect the number of items in the stream. The Speedment runtime will draw the same conclusion and optimize away the sorting and mapping operations, leaving once again all the work to the database engine with a database query along the following lines

SELECT COUNT(*) FROM 'sakila'.'film' WHERE ('rating' = 'PG-13' COLLATE utf8_bin) AND ('length' >= 75)

Clearly, when the stream is built as a single sequence of statements as above, the optimization only helps if the designer of the application creates suboptimal code. However, a non-terminated stream is a logical construct that can be passed around as a parameter so it is quite possible to allow the construction of the structure of a stream to be the result of analysis of input data where some parts are added only when certain conditionals hold. Knowing that the framework will optimize the stream before executing it allows the application designer to focus on functional correctness rather than all execution paths creating optimal streams.

Step 7: Classifying Films

As a more elaborate example, the following code snippet collects the stream of films and group them by rating. This piece of code demonstrates how Speedment gives immediate access to the full versatility and power of Java Streams to any database application.

films.stream()
    .collect(Collectors.groupingBy(Film.RATING,
        Collectors.counting())).forEach(
            (rating, count) -> System.out.printf("There are %d %s rated films.%n", count, rating));

The output looks as follows.

There are 223 PG-13 rated films.
There are 195 R rated films.
There are 210 NC-17 rated films.
There are 178 G rated films.
There are 194 PG-rated films.

Step 8: Film lookup via actor name

A slightly more advanced use case of Speedment streams is to look for films featuring actors of a given last name. To achieve this, the application logic will be split into two parts. First, we compute selectedActorIds, the actors with matching names. Then we filter the relation between films and actors in the actor IDs in question and finally map the items of the stream into the desired output format.

    System.out.println("Please enter actor last name ");
    final String actorName = scn.nextLine().trim();
    Set<Integer> selectedActorIds = actors.stream()
        .filter(Actor.LAST_NAME.equalIgnoreCase(actorName))  
        .mapToInt(Actor.ACTOR_ID)                             // turning the stream into a stream of actor IDs
        .boxed()                                              // turning IntStream into Stream<Integer>
        .collect(toSet()); 

    if (selectedActorIds.isEmpty()) {
        System.out.println("No actor with last name " + actorName + " found.");
    } else {
        System.out.println("Films with actor with last name " + actorName + ":");
        filmActors.stream()
            .filter(FilmActor.ACTOR_ID.in(selectedActorIds))   
            .map(films.finderBy(FilmActor.FILM_ID))  // the stream of films we are looking for
            .map(Film.TITLE.getter())                // the stream of film titles
            .sorted()
            .forEach(title -> System.out.println(" " + title));
    }

Example output

The following is an example output from the application, where we look for Film ID 14 and then films featuring an actor names Akroyd.

Please enter Film ID 
14

Film ID 14 has title ALICE FANTASIA
Please enter actor last name 
Akroyd

Films with an actor with last name Akroyd:
 APOLLO TEEN
 BACKLASH UNDEFEATED
 BETRAYED REAR
 ...
Clone this wiki locally