Spring Data JPA. 1. Introduction

logo spring

Welcome to my course! In sixteen chapters, you’ll learn to harness the power of Spring Data JPA, always with a practical approach. Apart from being a progressive learning tool suitable for all levels, this course also serves as a quick reference guide.

If you’ve never used Spring Data, you’ll have many questions. What problems does it solve? How does it work? In this introductory chapter I give you the answers. Spoiler: Spring Data JPA is the best way to work with relational databases.

Prerequisites. This course assumes that you understand the essential aspects of Spring Boot and Spring dependency injection.


>> Read this post in Spanish here <<

>> List of Chapters <<

Contents

  1. Databases
  2. A Solid Case Against JDBC API
  3. Easy SQL with Spring Jdbc Template
  4. JPA
  5. Spring Data JPA
  6. Spring Data Commons
  7. Sample Project

Databases

Spring Data, as its name suggests, is all about data.

If you think about it, you create a lot of data. For instance, when you buy something from e-commerce, use an online service, visit your doctor, waste time reading my blog, or (gulp) pay your taxes. Organizations collect this data to provide their services, optimize their procedures, or torment you with ads for products you’ll never buy, among many other uses, some of them not very lawful.

Where’s it stored? Unless we are talking about files, in a database. The most widespread databases comply with the relational model, characterized by organizing the data in a strongly structured fashion. 

Let me give you a simple example. Imagine a software that needs to save users’ information, like their names, dates of birth, and emails. In a relational database, we model that information with a table representing the concept user, where each row (record) represents a unique user. The table’s columns hold the user’s most basic characteristics. In addition, records from different tables can be linked through solid relationships. For instance, users may be linked to their invoices, which are stored in a different table.

Relational databases are also known as SQL databases because they support the famous SQL language for storing and retrieving records and managing the data structure. 

Does all the above sound familiar? Relational databases are the ones you probably know: Oracle, PostgreSQL, MySQL, SQL Server, Access… They’ve dominated the market since the 1980s and will continue to do so in the long term.

Usually, a relational database is at the very core of management applications. Sometimes these applications are even just graphical interfaces for the underlying database.

A Solid Case Against JDBC API

As Java\Kotlin developers, a big question arises: how do we access relational databases from the code? The answer lies in the JDBC API, included in Java in the java.sql and javax.sql packages. It mediates between the code and the relational database by executing SQL statements.

Most of the API is composed of unimplemented interfaces. We need therefore an API implementation, named JDBC driver, developed for the database we use. With this approach, it doesn’t matter if we use PostgreSQL or MySQL, for instance. The Java code is always the same because it relies on the JDBC API,

If you’ve ever programmed with the JDBC API, you’ve experienced (and suffered) how cumbersome it can be. Why? Executing SQL involves a lot of code. Look at this example, which retrieves from a table named countries those countries with a population less than a certain amount:

@Autowired
private DataSource dataSource;

public List<Country> findByPopulationLessThan(int maxPopulation) throws SQLException {
    String sql = "SELECT * FROM countries WHERE population < ? ORDER BY name";
    try (Connection connection = dataSource.getConnection();
         PreparedStatement ps = connection.prepareStatement(sql);) {
         ps.setInt(1, maxPopulation);
         ResultSet resultSet = ps.executeQuery();
         return mapResults(resultSet);
    }
}

private List<Country> mapResults(ResultSet resultSet) throws SQLException {
    List<Country> results = new ArrayList<>();
    while (resultSet.next()) {
        Country country = mapToCountry(resultSet);
        results.add(country);
    }
   return results;
 }

private Country mapToCountry(ResultSet resultSet) throws SQLException {
    return new Country(
            resultSet.getLong("id"),
            resultSet.getString("name"),
            resultSet.getInt("population"),
            resultSet.getString("capital"),
            resultSet.getBoolean("oecd"),
            mapToLocalDate(resultSet.getDate("united_nations_admission")),
            null);
}

private LocalDate mapToLocalDate(Date date) {
    if (date == null) {
        return null;
    }
    return date.toLocalDate();
}

Thirty lines! And I didn’t deal with transactions because it’s a read-only operation. I only wanted the result of a query. Such a frequent procedure should be more straightforward.

It’s always the same “ritual”:

  • Obtain the connection.
  • Create an object of the Statement or PreparedStatement class that holds the SQL statement and its possible variables.
  • Perform the SQL to get the results in an object of type ResultSet.
  • Iterate the ResultSet and convert the results to objects. This task often involves dealing with nulls, indexes, and type conversions.
  • Handle the checked exceptions (SQLException).

Oh, and remember to close the resources you open.

This code verbosity isn’t due to a flawed design of the JDBC API—it’s a consequence of the API’s nature. It represents the lowest level of abstraction for communicating Java with databases.

Be that as it may, if we were paid by the number of lines written, JDBC is a money spinner. As I guess that no developer is in this situation, we should encapsulate and reuse the boilerplate code that barely changes between queries. Good news: Spring Framework did it for us.

Easy SQL with Spring Jdbc Template

Spring Jdbc Template simplifies the tedious tasks related to JDBC usage. It lets us focus on writing SQL statements and frees us from manually managing connections, transactions, and exceptions.

Let’s rewrite the example:

@Autowired
private JdbcTemplate jdbcTemplate;

public List<Country> findByPopulationLessThanWithTemplate(int maxPopulation) {
    return jdbcTemplate.query("SELECT * FROM countries WHERE population < ? ORDER BY name",
            (rs, rowNum) -> mapToCountry(rs), maxPopulation);
}

private Country mapToCountry(ResultSet resultSet) throws SQLException {
    return new Country(
            resultSet.getLong("id"),
            resultSet.getString("name"),
            resultSet.getInt("population"),
            resultSet.getString("capital"),
            resultSet.getBoolean("oecd"),
            mapToLocalDate(resultSet.getDate("united_nations_admission")),
            null);
}

private LocalDate mapToLocalDate(Date date) {
    if (date == null) {
        return null;
    }
    return date.toLocalDate();
}

As you can see, a method of the JdbcTemplate class takes the SQL query, its variables, and a lambda expression that processes the ResultSet with the results. JdbcTemplate also provides convenient methods for easily inserting, updating, and deleting records.

While the simplification is noticeable, mapToCountry() is still there, cluttering the code. In addition, we’ve to write the SQL for all the queries, and we can only use JdbcTemplate in Spring projects.

JPA

Let’s step further. Object-relational mapping tools (ORMs) skyrocket developers’ productivity. Put simply, an ORM moves data between tables and classes and vice versa. It acts as a hinge that joins the relational and object-oriented worlds. You should learn, however, to lubricate it to avoid annoying squeaks.

The following picture outlines the relationships between the actors in a typical software that relies on an ORM.

On the left, there’s a database and its tables; on the right, there’s a program written in Java, so it’s class-based. In the middle, an ORM acts as a translator between the objects of the classes and the records of the tables, as indicated by certain settings (dashed line). The program interacts with the database via ORM, using Java and the program’s class model instead of SQL and records (ResultSet objects). As a result, the program gets rid of the code responsible for translation (mapToCountry()) and increases its abstraction.

Here, the Jakarta Persistence (JPA) specification comes into play. It standardizes the basic capabilities of ORM products developed for Java. JPA-compliant ORMs are known as JPA providers and must offer the full functionality of the standard as described by its documentation. Thus the code that utilizes JPA classes and interfaces works the same with any JPA provider.

The figure below shows the abstraction layer that JPA adds on top of the JDBC API, with the indispensable collaboration of a JPA provider.

The previous explanations sound abstract, so let’s code. This version of findByPopulationLessThan() finds countries with the EntityManager interface, the heart of the JPA API:

@PersistenceContext
private EntityManager entityManager;

public List<Country> findByPopulationLessThanWithJpa(int maxPopulation) {
  return entityManager.createQuery("SELECT c FROM Country c WHERE c.population < :maxPopulation ORDER BY c.name", Country.class)
                .setParameter("maxPopulation", maxPopulation)
                .getResultList();
}

The method executes a query written with JPQL, the JPA query language. It looks like SQL, but look carefully. The FROM clause doesn’t query the countries table but the Country class. This is possible because Country represents the countries table and its columns.

Anyway, the relevant part of the example is the “invisible” code. Where is the method mapToCountry()? We don’t need it anymore. JPA maps the database records to Country objects because, again, Country represents the countries table. Let somebody else do the dirty work.

Spring Data JPA

The sample code is “slimmer” with JPA. That means less code to write and read—but it’s not enough. I wish less code and more productivity. Is it possible? It is, with Spring Data, one of Spring Framework’s projects. According to the official website:

Spring Data’s mission is to provide a familiar and consistent, Spring-based programming model for data access while still retaining the special traits of the underlying data store.


It makes it easy to use data access technologies, relational and non-relational databases, map-reduce frameworks, and cloud-based data services. This is an umbrella project which contains many subprojects that are specific to a given database. The projects are developed by working together with many of the companies and developers that are behind these exciting technologies.

Of the Spring Data subprojects or modules, the course covers Spring Data JPA. It enables us to interact with relational databases using Spring Data and Hibernate, the most prevalent JPA provider. Here’s the big picture:

Why Spring Data is so cool? I’ll show you the fourth and latest version of findByPopulationLessThan(). Behold the simplicity of Spring Data:

public interface CountryRepository extends Repository<Country, Long> {

    List<Country> findByPopulationLessThanOrderByName(int population);

}

Compare the above method with earlier versions, in particular the one that uses the JDBC API. What a difference! It’s a quantum jump.

The CountryRepository interface is a repository, a concept that is the cornerstone on which Spring Data is built. The method’s name defines the data to find because it follows certain conventions, which will be the subject of study in Chapter 5.

You inject CountryRepository wherever you want to call its method:

@Autowired
private CountryRepository countryRepository;

Let’s get pessimistic. What if you encounter a scenario in which Spring Data JPA doesn’t work? No problemo. You may use the JPA entity manager, Spring Jdbc Template, and JDBC API. Nothing prevents you from using any of the boxes of the diagram in the same project. 

And you haven’t seen anything yet. Spring Data JPA is a massive toolbox at your service. Learn how to use it effectively with my course.

Spring Data Commons

I’ll conclude this chapter by highlighting that Spring Data JPA is just one of the modules available for Spring Data. With all modules, we code similarly thanks to a common API (Spring Data Commons) and architecture (repositories). It’s the “coherent programming model” mentioned in the description I pasted.

The above implies that all modules provide the functionalities covered by Spring Data Commons, each adding specific features and capabilities of its data storage. For instance, with all modules we create repositories containing query methods, but only Spring Data JPA repositories can execute JPQL queries.

Thus you’ll be able to reuse part of the knowledge you learned in the course if you need to work, for instance, with Spring Data MongoDB or Spring Data Cassandra. Note that these NoSQL databases are “different beasts” from relational databases. In the course I’ll only consider Spring Data JPA, but I’ll carefully use the terms “Spring Data” and “Spring Data JPA” to distinguish the general features of Spring Data from those exclusive to the JPA module.

Sample Project

The project is on GitHub. In the next chapter we´ll create it from scratch. 


Chapter 2: Sample Project >>>


Other Posts in English

JSP and Spring Boot

Spring Framework: asynchronous methods with @Async, Future and TaskExecutor

Spring Framework: events handling

Spring Boot testing: Docker with Testcontainers and JUnit 5. MySQL and other images.

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.