Book Image

Spring Data

By : Petri Kainulainen
Book Image

Spring Data

By: Petri Kainulainen

Overview of this book

Spring Framework has always had a good support for different data access technologies. However, developers had to use technology-specific APIs, which often led to a situation where a lot of boilerplate code had to be written in order to implement even the simplest operations. Spring Data changed all this. Spring Data makes it easier to implement Spring-powered applications that use cloud-based storage services, NoSQL databases, map-reduce frameworks or relational databases. "Spring Data" is a practical guide that is full of step-by-step instructions and examples which ensure that you can start using the Java Persistence API and Redis in your applications without extra hassle. This book provides a brief introduction to the underlying data storage technologies, gives step-by-step instructions that will help you utilize the discussed technologies in your applications, and provides a solid foundation for expanding your knowledge beyond the concepts described in this book. You will learn an easier way to manage your entities and to create database queries with Spring Data JPA. This book also demonstrates how you can add custom functions to your repositories. You will also learn how to use the Redis key-value store as data storage and to use its other features for enhancing your applications. "Spring Data" includes all the practical instructions and examples that provide you with all the information you need to create JPA repositories with Spring Data JPA and to utilize the performance of Redis in your applications by using Spring Data Redis.
Table of Contents (13 chapters)

Java Persistence API


Before the Java Persistence API (JPA) was introduced, we had the following three alternative technologies which we could use to implement our persistence layer:

  • The persistence mechanism provided by Enterprise JavaBeans (EJB) 2.x specifications

  • The JDBC API

  • The third party object-relational mapping (ORM) frameworks such as Hibernate.

This gave us some freedom when selecting the best tool for the job but as always, none of these options were problem free.

The problem with EJB 2.x was that it was too heavyweight and complicated. Its configuration relied on complicated XML documents and its programming model required a lot of boilerplate code. Also, EJB required that the application be deployed to a Java EE application server.

Programming against the JDBC API was rather simple and we could deploy our application in any servlet container. However, we had to write a lot of boilerplate code that was needed when we were transforming the information of our domain model to queries or building domain model objects from query results.

Third party ORM frameworks were often a good choice because they freed us from writing the unnecessary code that was used to build queries or to construct domain objects from query results. This freedom came with a price tag: objects and relational data are not compatible creatures, and even though ORM frameworks can solve most of the problems caused by the object-relational mismatch , the problems that they cannot solve efficiently are the ones that cause us the most pain.

The Java Persistence API provides a standard mechanism for implementing a persistence layer that uses relational databases. Its main motivation was to replace the persistence mechanism of EJB 2.x and to provide a standardized approach for object-relational mapping. Many of its features were originally introduced by the third party ORM frameworks, which have later become implementations of the Java Persistence API. The following section introduces its key concepts and describes how we can create queries with it.

Key concepts

An entity is a persistent domain object. Each entity class generally represents a single database table, and an instance of such a class contains the data of a single table row. Each entity instance always has a unique object identifier, which is the same thing to an entity that a primary key is to a database table.

An entity manager factory creates entity manager instances. All entity manager instances created by the same entity manager factory will use the same configuration and database. If you need to access multiple databases, you must configure one entity manager factory per used database. The methods of the entity manager factory are specified by the EntityManagerFactory interface.

The entity manager manages the entities of the application. The entity manager can be used to perform CRUD (Create, Read, Updated, and Delete) operations on entities and run complex queries against a database. The methods of an entity manager are declared by the EntityManager interface.

A persistence unit specifies all entity classes, which are managed by the entity managers of the application. Each persistence unit contains all classes representing the data stored in a single database.

A persistence context contains entity instances. Inside a persistence context, there must be only one entity instance for each object identifier. Each persistence context is associated with a specific entity manager that manages the lifecycle of the entity instances contained by the persistence context.

Creating database queries

The Java Persistence API introduced two new methods for creating database queries: Java Persistence Query Language (JPQL) and the Criteria API . The queries written by using these technologies do not deal directly with database tables. Instead, queries are written over the entities of the application and their persistent state. This ensures, in theory, that the created queries are portable and not tied to a specific database schema or database provider.

It is also possible to use SQL queries, but this ties the application to a specific database schema. If database provider specific extensions are used, our application is tied to the database provider as well.

Next we will take a look at how we can use the Java Persistence API to build database queries by using SQL, JPQL, and the Criteria API. Our example query will fetch all contacts whose first name is "John" from the database. This example uses a simple entity class called Contact that represents the data stored in the contacts table. The following table maps the entity's properties to the columns of the database:

Contact

contacts

firstName

first_name

Native SQL queries

SQL is a standardized query language that is designed to manage data that is stored in relational databases. The following code example describes how we can implement the specified query by using SQL:

//Obtain an instance of the entity manager
EntityManager em = ...

//Build the SQL query string with a query parameter
String getByFirstName="SELECT * FROM contacts c WHERE c.first_name = ?1";

//Create the Query instance
Query query = em.createNativeQuery(getByFirstName, Contact.class);

//Set the value of the query parameter
query.setParameter(1, "John");

//Get the list of results
List contacts = query.getResultList();

This example teaches us three things:

  • We don't have to learn a new query language in order to build queries with JPA.

  • The created query is not type safe and we must cast the results before we can use them.

  • We have to run the application before we can verify our query for spelling or syntactical errors. This increases the length of the developer feedback loop and decreases productivity.

Because SQL queries are tied to a specific database schema (or to the used database provider), we should use them only when it is absolutely necessary. Often the reason for using SQL queries is performance, but we might also have other reasons for using it. For example, we might be migrating a legacy application to JPA and we don't have time to do it right at the beginning.

Java Persistence Query Language

JPQL is a string-based query language with a syntax resembling that of SQL. Thus, learning JPQL is fairly easy as long as you have some experience with SQL. The code example that executes the specified query is as follows:

//Obtain an instance of the entity manager
EntityManager em = ...

//Build the JPQL query string with named parameter
String getByFirstName="SELECT c FROM Contact c WHERE c.firstName = :firstName";

//Create the Query instance
TypedQuery<Contact> query = em.createQuery(getByFirstName, Contact.class);

//Set the value of the named parameter
query.setParameter("firstName", "John");

//Get the list of results
List<Contact> contacts = query.getResultList();

This example tells us three things:

  • The created query is type safe and we don't have to cast the query results.

  • The JPQL query strings are very readable and easy to interpret.

  • The created query strings cannot be verified during compilation. The only way to verify our query strings for spelling or syntactical errors is to run our application. Unfortunately, this means that the length of the developer feedback loop is increased, which decreases productivity.

JPQL is a good choice for static queries. In other words, if the number of query parameters is always the same, JPQL should be our weapon of choice. But implementing dynamic queries with JPQL is often cumbersome as we have to build the query string manually.

The Criteria API

The Criteria API was introduced to address the problems found while using JPQL and to standardize the criteria efforts of third party ORM frameworks. It is used to construct query definition objects, which are transformed to the executed SQL query. The next code example demonstrates that we can implement our query by using the Criteria API:

//Obtain an instance of entity manager
EntityManager em = ...
//Get criteria builder
CriteriaBuilder cb = em.getCriteriaBuilder();

//Create criteria query
CriteriaQuery<Contact> query = cb.greateQuery(Contact.class);

//Create query root
Root<Contact> root = query.from(Contact.class);

//Create condition for the first name by using static meta
//model. You can also use "firstName" here.
Predicate firstNameIs = cb.equal(root.get(Contact_.firstName, "John");

//Specify the where condition of query
query.where(firstNameIs);

//Create typed query and get results
TypedQuery<Contact> q = em.createQuery(query);
List<Contact> contacts = q.getResultList();

We can see three things from this example:

  • The created query is type safe and results can be obtained without casting

  • The code is not as readable as the corresponding code that uses SQL or JPQL

  • Since we are dealing with a Java API, the Java compiler ensures that it is not possible to create syntactically incorrect queries

The Criteria API is a great tool if we have to create dynamic queries. The creation of dynamic queries is easier because we can deal with objects instead of building query strings manually. Unfortunately, when the complexity of the created query grows, the creation of the query definition object can be troublesome and the code becomes harder to understand.