Minborg

Minborg
Minborg
Showing posts with label ORM. Show all posts
Showing posts with label ORM. Show all posts

Thursday, October 1, 2020

How to get Type-Safe and Intuitive Hibernate/JPA Queries by Leveraging Java Streams

A large proportion of Java database applications are using Hibernate/JPA to bridge the gap between Java and SQL. Until recently, we were forced to mix Java and JPQL or to use complex imperative criteria builders to create database queries. Both of these methods are inherently neither type-safe nor very intuitive.

The newly launched open-source library JPAstreamer addresses these issues by allowing you to express Hibernate/JPA queries using Java Streams. This means we can avoid any impedance mismatches between JPQL/HQL and Java and get full type-safety. In this article, I will show you how to put Java Stream queries to work in your application using JPAstreamer.

JPAstreamer in a nutshell

As mentioned, JPAstreamer allows JPA queries to be expressed as standard Java Streams using short and concise, type-safe declarative constructs. This makes our code shorter, less complex, and easier to read and maintain. Best of all, we can stick to using only Java code without needing to mix it with SQL/JPQL or other language constructs/DSL. 

In short, we can query a database like this:

jpaStreamer.stream(Film.class)
.sorted(Film$.length.reversed())
.limit(15)
.map(Film$.title)
.forEach(System.out::println);

This prints the title of the 15 longest films in the database. 

OSS License 

JPAstreamer is using the same license as Hibernate (LGPL). This makes it easy to use in existing Hibernate projects. JPAstreamer also works with other JPA providers such as EclipseLink, OpenJPA, TopLink etc.

Installation

Installing JPAstreamer entails just adding a single dependency in your Maven/Gradle configuration file as described here. For example, Maven users add the following dependency:


    <dependency>
        <groupId>com.speedment.jpastreamer</groupId>
        <artifactId>jpastreamer-core</artifactId>
        <version>0.1.8</version>
</dependency>

Let’s have a look at how JPAstreamer fits in an existing application.

Example Database and JPA Entities

In the examples below, we are using JPAstreamer to query the “Sakila” example database that is available for download directly from Oracle or as a Docker instance.

This is how you install and run the example database using Docker:

 
$ docker pull restsql/mysql-sakila
$ docker run -d --publish 3306:3306 --name mysqld restsql/mysql-sakila

We will also be relying on JPA entities like the Film-class partly shown here:

@Entity
@Table(name = "film", schema = "sakila")
public class Film implements Serializable {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "film_id", nullable = false, updatable = false, columnDefinition = "smallint(5)")
private Integer filmId;

@Column(name = "title", nullable = false, columnDefinition = "varchar(255)")
private String title;

@Column(name = "description", nullable = false, columnDefinition = "text")
private String description;

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(
name = "film_actor",
joinColumns = @JoinColumn(name = "film_id") ,
inverseJoinColumns = @JoinColumn(name = "actor_id")
    )
private List<Artist> actors;
...
}

The complete code in this article is open-sourced and available here

JPAstreamer - Printing the Longest Films

Here is a complete example of how we can use JPAstreamer to create a query that prints out the length and title of the 15 longest films in the database:

public class LongestFilms  {
public static void main(String[] args) {
final JPAStreamer jpaStreamer = JPAStreamer.of("sakila");
jpaStreamer.stream(Film.class)
.sorted(Film$.length.reversed())
.limit(15)
.map(f -> String.format("%3d %s", f.getLength(), f.getTitle()))
.forEach(System.out::println);

jpaStreamer.close();
}
 }

This will print:


185 SOLDIERS EVOLUTION
185 GANGS PRIDE
185 SWEET BROTHERHOOD
185 CHICAGO NORTH
185 HOME PITY
185 POND SEATTLE
185 CONTROL ANTHEM
185 DARN FORRESTER
185 WORST BANGER
184 SMOOCHY CONTROL
184 SONS INTERVIEW
184 SORORITY QUEEN
184 MOONWALKER FOOL
184 THEORY MERMAID

As can be seen, queries are simple, concise, completely type-safe and follow the Java Stream standard API. No need to learn new stuff.

The code above will create the following SQL (shortened for brevity):

select
film0_.film_id as film_id1_1_,
film0_.length as length4_1_,
film0_.title as title10_1_,
/* more columns */
from
film film0_
order by
film0_.length desc limit ?

This means that most of the Java stream is actually executed on the database side. It is only the map() and forEach() operations (which cannot easily be translated to SQL) that are executed in the JVM. This is really cool!

Pre-Joining Columns

To avoid the “SELECT N + 1” problem, it is possible to configure streams to join in columns eagerly by providing a configuration object like this:

StreamConfiguration configuration = StreamConfiguration.of(Film.class)
.joining(Film$.actors)
.joining(Film$.language);

jpaStreamer.stream(configuration)
.filter(Film$.rating.in("G", "PG"))
.forEach(System.out::println);

This will create a Hibernate join under the hood and will only render a single SQL query where all the Film fields “List<Artist> artists” and “Language language” will be populated on the fly:


select
Film
from
Film as Film
left join
fetch Film.actors as generatedAlias0
left join
fetch Film.language as GeneratedAlias1
where
Film.rating in (
:param0, :param1
)

Conclusion

In this article, I have shown how you can avoid impedance mismatches between JPQL/HQL in Hibernate/JPA using the open-source library JPAstreamer. The Stream API allows you to compose type-safe and expressive database queries in standard Java without compromising the application performance. 

Feedback

The background to JPAStreamer is that we have developed the stream-based ORM-tool Speedment, and we have come across many developers that want to use Java streams but are constrained to use Hibernate in their applications. Therefore, we have now developed JPAstreamer, a JPA/Hibernate extension that handles Java Stream queries without the need to change the existing codebase. 

Take JPAStreamer for a spin and let me know what you like/dislike by dropping a message on Gitter!

Resources

Thursday, October 20, 2016

Java 8: A Closer Look at Speedment 3.0.1 “Forest” Stream ORM

Following the Road

Forest.png
I have been contributing to the open-source project Speedment (which is a Stream ORM Java Toolkit and Runtime) and a new major version called 3.0.1 “Forest” was just released. Releases are named after the avenues in Palo Alto, California where most of the contributors work. Each new major release gets a new name by following Middlefield Road southwards. The new version is now modularized which helps developers keep up the good pace. There are also a large number of new features for Speedment users and in this article we will look into some of the things to discover!

Persistence

People used to older ORMs can now use Speedment in the same way when creating, updating or removing entities from a database. For example, we can create entities in a database “JPA-style” like this:
Hare hare = new HareImpl();
hare.setName("Flopsy");
hare.setAge(1);
hare.setColor("Gray");

entityManager.persist(hare);  // Persists (=inserts) the new Hare in the database

While this is not a big change, it is still convenient.

Declarative Stream Composition

Speedment database queries are expressed as operations on Standard Java 8 Streams. In the new version, the Speedment API provides methods that returns functions rather than operating on objects directly. This simplifies something called Declarative Stream Composition which simply means that it becomes easier and more efficient to write streams.

Let us take a closer look at an example where we want to join objects from two different tables. We have two tables “hare” and “carrot” where “carrot” has a field named “owner” that is a foreign key to the column “hare”.”id”. The task is to build a Map that contains all Hare entities as keys and a List of Carrot entities that belongs to a particular Hare via its foreign key, as values. This can be expressed like this:

Map<Hare, List<Carrot>> joinMap = carrots.stream()
    .collect(
        groupingBy(hares.finderBy(Carrot.OWNER)) // Applies the finderBy(Carrot.OWNER) classifier
    );

The goupingBy() method takes a Function that maps from a Carrot to a Hare entity. So, by working by methods that returns functions, our code becomes very compact. This also opens up future ways of optimizing the stream, since these functions can be identified and analyzed in the stream pipeline prior to the stream is started. It should be noted that both the collect() and groupingBy() methods are standard Java 8 methods.

Even Better Code Generation

Speedment generates code automatically from the database schema data. One good thing with Speedment is that we can see, understand and change the generated code. This makes things less “magic” compared to other ORMs and puts the developer in the driving seat. The new code generation functionalities include:

Support for Primitive Types

Now we can use primitive types like int, long or double for columns and improve both execution speed and memory usage. Nullable fields can be mapped to specialized Optional types like OptionalInt, OptionalLong and OptionalDouble consistent with Java 8 code styling.

Modular Code Generation

We can plug in our own code generation logic and adapt the default code generator. This comes in handy for us developers that might understand our domain model in depth and want to leverage that knowledge. When new functionality is added by customizing the code generator, these new features will be applied immediately to all generated code. Code the code and get leverage!

Compatibility Mode

Some older solutions are not prepared for Optional fields and so a new “compatibility” mode was added where, for example, a nullable integer will be returned as an Integer and not as an OptionalInt.

Configurable Name Space

We can now configure the code generator to put entities, managers and configuration objects individually on any namespace. This is good for modularized projects.

Improved Code Renderer

Speedment is using a Model View Controller (MVC) paradigm for code generation. This means that the code Model (which is an Abstract Syntax Tree) is separate from the actual code rendering (View). The Views have been updated and improved so it produces better looking code.

Checksum Protection

Manually changes classes are protected by checksums so that they are retained even if we decide to change the name space.

Increased Type Safety

Speedment can now map columns that take values from small sets of strings to Enums further improving type safety. When the generated code uses an Enum, any mismatch between the database model and the values used in the business logic will be found as early as possible by the compiler, instead of later in the development cycle.

Improved Logging for Transparency

Speedment has a new logging system to allow us to see the exact SQL code being sent to the database. This is good for transparency and allows us to see precisely what is happening under the hood. We can easily enable logging of all CRUD operations like this:

HaresApplication loggingApp = new HaresApplicationBuilder()
    .withPassword("secretDbPassword")
    .withLogging(STREAM)
    .withLogging(PERSIST)
    .withLogging(UPDATE)
    .withLogging(REMOVE)
    .build();

Manager<Hare> hares = loggingApp.getOrThrow(HareManager.class);

long oldHares = hares.stream()
    .filter(Hare.AGE.greaterThan(8))
    .count();

System.out.println("There are " + oldHares + " old hares");

This will produce the following log:
2016-10-19T20:50:21.957Z DEBUG [main] (#SELECT) - 
    SELECT COUNT(*) FROM `hares`.`hare` WHERE (`hares`.`hare`.`age` > ?), values:[8]

There are 30 old hares


Improved User Interface

The graphical tool has been improved in many ways. Now, we get warnings and tips that gives us better guidance. Several code generator configuration options have been added and we also see more relevant information when we select different configuration objects.

New Maven Goals

There are two new Maven goals; “clear” and “reload”, that can be used to automate and simplify the building process. The goal “clear” removes all generated code (that is not manually changed) and “reload” reloads the domain model directly from an existing database (metadata).

Take it for a Spin

Check out open-source Speedment on GitHub where there also is a Wiki and a quick start guide. Feel free to give feedback and join the discussion via Gitter.

Drive safely!

Thursday, November 12, 2015

Easily Create Database Content with Java 8

Database Connectivity Now and Then

Spire and Duke adding stuff
to a database.
I remember back in the old (Java) days, when we were sitting up late nights and experimented a lot with Java and databases. In the beginning of the Java era, there was not much support for database connectivity and so we had to basically write your own database classes and handle ResultSets, Connections and SQLExceptions by ourself.

Nowadays, we expect the simple things just to happen! Suppose that we have an existing database and we want to add or update information in it using a Java application. How can we do that in a simple way without having to write a lot of boilerplate code?

I have contributed a lot to the Java 8 Open Source project Speedment, that can be used to very easily extract Java code from existing database schemas and start coding applications directly.

Let's take it for a spin.

Example

Let's say we have a MySQL database table that is supposed to contain data on various countries. The table could look something like this:

mysql> explain country +------------+-------------+------+-----+---------+----------------+ | Field      | Type        | Null | Key | Default | Extra          | +------------+-------------+------+-----+---------+----------------+ | id         | int(11)     | NO   | PRI | NULL    | auto_increment | | name       | varchar(45) | YES  | UNI | NULL    |                | | local_name | varchar(45) | YES  |     | NULL    |                | | code       | int(11)     | YES  |     | NULL    |                | | domain     | varchar(10) | YES  |     | NULL    |                +------------+-------------+------+-----+---------+----------------+ 5 rows in set (0.00 sec)

Let us further pretend that we have the task of populating the table with a few countries and see how that can be solved.

Setting up the Speedment Project

In order to set up a project with Speedment, we need to include a few lines in our POM.xml file, connect to the database and generate code. Read more on how to do this here!.

Also, check out this film how easy it is:

Initializing the Database Connection

Now that we have our database domain model generated automatically, we can start with the actual coding for inserting data into the database. First, we need to setup our Java 8 database project like this:

// Setup Speedment speedment = new JavapotApplication().withPassword("javapot").build(); Manager<Country> countries = speedment.managerOf(Country.class);

The JavapotApplication class was generated automatically from the database schema and contains all meta data (like columns and tables) of the database. Note that we manually need to provide the password since this is not stored in the meta data model (for security reasons). The countries variable is a "handle" for the table we are about to work with.

There is really no "magic" going on with the generation. We can see all the generated Java files in clear text and we can change them or introduce our own versions if we want.

Inserting Data in the Database

Once the setup is made, is is very easy to insert data rows in the database like this:

try { countries.newInstance() .setName("United States") .setLocalName("United States") .setCode(1) .setDomain(".us") .persist(); countries.newInstance() .setName("Germany") .setLocalName("Deutschland") .setCode(49) .setDomain(".de") .persist(); // Needless to say, you can call the setters in any order. countries.newInstance() .setDomain(".uk") .setCode(44) .setName("United Kingdom") .setLocalName("United Kingdom") .setDomain(".uk") .persist(); countries.newInstance() .setName("Sweden") .setLocalName("Sverige") .setCode(40) // Intentionally wrong, should be 46!! .setDomain(".se") .persist(); } catch (SpeedmentException se) { // Handle the exception here }

The newInstance() method creates a new empty Country object and then we just use the setters to initialize the country. After all parameters are set, we call the persist() method to store the object in the database. If there is an error during the database insert, a SpeedmentException will be thrown, allowing you to examine why (for example if you are trying to insert two countries with the same name).  I intentionally picked the wrong country call code for Sweden (förlåt Sverige) so that we can learn how to update data in our database too.

Updating Data in the Database

If you want to update an existing row in the database you can do it like this:

countries.stream() .filter(Country.NAME.equal("Sweden")) .findAny() .ifPresent(c -> c.setCode(46).update());

This will create a Stream with all the counties that have the name "Sweden" (which, evidently, is only one country) and then it will try to find that country and if it is present, it will take that country and set the code to 46 (which is the correct calling code for Sweden) and then it will update the selected country in the database. It is important to understand that even though our country table might contain a large number of countries, it will only include those countries in the stream that are satisfying an equivalent query of "select * from country where name='Sweden' " in this case.

Now our database looks like this:
mysql> select * from country; +----+----------------+----------------+------+--------+ | id | name           | local_name     | code | domain | +----+----------------+----------------+------+--------+ |  1 | United States  | United States  |    1 | .us    | |  2 | Germany        | Deutschland    |   49 | .de    | |  3 | United Kingdom | United Kingdom |   44 | .uk    | |  4 | Sweden         | Sverige       |   46 | .se    | +----+----------------+----------------+------+--------+ 4 rows in set (0.00 sec)

Success! Mission accomplished!

Contributing 

Read more about Speedment Open Source on www.speedment.org and that's the place to be if you want to learn more things like how the API looks like and how you use Speedment in your projects. Speedment is here on GitHub. You can contribute by submitting comments on gitter or download the source code and create pull requests with your own code contributions.

Conclusions

With Java 8, you can easily write database applications with almost no extra manual code work. There are tools that automatically can extract your domain model from a database schema.

These days, we Java programmers can can put more time on the actual problem (and perhaps get some more well deserved sleep) instead of fiddling around with basic database functionality.