The Streams API Goes Both Ways
Persisting data in Speedment follows the same intuitive stream approach as other Speedment data oriented operations. Just as querying the database is expressed as a stream of operations on data items, the POJO entities received from the database may be persisted to the database by simply terminating the stream in a database persister.Simple retrieval of data can be expressed as
Optional<Film> longFilm = films.stream()
.filter(Film.LENGTH.greaterThan(120))
.findAny();
which will find a POJO representing a row in the underlying database for which the supplied predicate holds true. In this case, the user will get a film longer than two hours, if any such film exists. For reading data, Speedment thus supplies a stream source that represents the database, in this case the films instance which runs code generated by Speedment. Analogously, Speedment defines a stream termination that handles data writing which can be used as follows.
Stream.of("Italiano", "EspaƱol")
.map(ln -> new LanguageImpl().setName(ln))
.forEach(languages.persister());
Here we create a stream of POJOs representing rows in the database that may not yet exist in the database. The languages
instance on line 3 is a Manager just like films
above and is implemented by code generated by Speedment. The persister()
method returns a Consumer of POJOs that will persist the items to the database.
Notice the symmetry where Speedment has generated code handling reading and writing of database data as intuitive Stream operations.
Updating Data in a Single Stream
Since Speedment provides a consistent API of treating database operations as stream operations on POJOs, the reading and writing of data can be composed and combined freely. Thus, a POJO retrieved from a Speedment source is the very same kind of POJO that is needed for persistence. Therefore, it makes a lot of sense to use streams that have both source and termination defined by Speedment. If one for example would like to update some rows of a table of the database, this can be done in the following concise way.
languages.stream()
.filter(Language.NAME.equal("Deutsch"))
.map(Language.NAME.setTo("German"))
.forEach(languages.updater());
Almost self-explanatory, at least compared to the corresponding JDBC operations, the code above will find any Language named “Deutsch” in the database and rename it to “German”. The terminating operation here is the updater which in contrast to the persister modifies existing rows of the database.
Selecting the Fields to Update
The basic updater will update all relevant columns of the row in question, which makes sense in many cases. However, for the case above when updating a single column of the database this behaviour may be wasteful or even prone to errors.Even if the code above intends to update only the name of the language, since the updater updates all columns of the row it will actually update the name to a new value and also reset all other columns to the values they had when the POJO was created from the database. If this code is the sole actor modifying the database this may be a minor problem, but in a concurrent environment it may create undesired race conditions where this innocent update of a single field may happen to undo changes to other columns.
In Speedment 3.1.6 and later, the user may select which fields to update and persist by supplying a description of the desired fields of the POJO. To improve on the last example, the following code will update only the name of the Language.
Updater<Language> updater = languages.updater(FieldSet.of(Language.NAME));
languages.stream()
.filter(Language.NAME.equal("Deutsch"))
.map(Language.NAME.setTo("German"))
.forEach(updater);
There are elaborate ways to express the set of fields to update and the interested reader is encouraged to learn more from the Speedment User Guide and to get a free license and example project scaffolding at the initializer.