Press "Enter" to skip to content

Spring’s Lightweight JPA/Hibernate Alternatives

Hibernate is a fantastic piece of engineering which became pretty much a default persistence solution for Java projects around the world.

However, many admit that it’s often a bit too feature-rich and that things like lazy-loading, dirty-checking, schema generation, session management, etc., make it unnecessarily heavy, unpredictable, and hard to debug.

Luckily, Spring ecosystem features two interesting alternatives that many are not aware of.

These are:

  1. spring-jdbc
  2. spring-data-jdbc

Let’s have a brief look at them.

Spring JDBC (aka JdbcTemplate)

Remember JDBC(Java Database Connectivity)?

Most of us remember it for its inconvenient/old-school API:

try (Connection conn = DriverManager.getConnection(DB_URL, "user", "pass");
     Statement stmt = conn.createStatement()) {

    try (ResultSet rs = stmt.executeQuery("SELECT id, name, surname, age FROM Users")) {
        while (rs.next()) {
            int id = rs.getInt("id");
            int age = rs.getInt("age");
            String name = rs.getString("name");
            String surname = rs.getString("surname");

            // ...
        }
    }
} catch (Exception se) {
    throw new RuntimeException(se);
}

Not even mentioning working with prepared statements, batching, and transaction support.

Luckily, Spring JDBC provides a tool that makes it straightforward to work with JDBC – the mighty JdbcTemplate:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

You can think of it as a generic database client that allows you to write and execute SQL statements… and that’s all. No magic, dirty-checking, lazy-initialization, object-relational mapping. Just you and SQL.

Imagine we want to persist a simple Movie class(technically, we don’t even need a class to persist, we can persist arbitrary data):

public class Movie {
    private long id;
    private String title;

    // boilerplate removed
}

To do that, we need to write our custom repository that exposes CRUD operations and internally uses JdbcTemplate:

public interface MovieRepository {
    Optional<Movie> findOneById(long id);
    void save(Movie movie);
}

And the implementation injecting JdbcTemplate:

@Repository
class JdbcMovieRepository implements MovieRepository {

    private final JdbcTemplate jdbcTemplate;

    public JdbcMovieRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // ...
}

And now, if we want to save a Movie instance into a database, we need to write our SQL by hand and pass to JdbcTemplate#update method along with values that need to be inserted:

jdbcTemplate.update(
  "INSERT INTO MOVIES(id, title) VALUES(?, ?)", 
  movie.getId(), movie.getTitle());

And that’s all.

Queries are equally straightforward:

jdbcTemplate.queryForObject(
  "SELECT * FROM MOVIES WHERE id = ?",
  (rs, rowId) -> new Movie(rs.getLong("id"), rs.getString("title")), id);

Notice that this is everything that you need to worry about. No more SQL debugging and surprises like:

Unfortunately, we need to write boilerplate mapping code by ourselves, but it’s a tiny inconvenience for the delayed gratification of working with predictable software.

However, if you do really miss entity mapping, make sure to check the next section.

You can find a complete code sample on GitHub.

Spring Data JDBC

Spring Data JDBC is a skimmed version of Spring Data JPA (an abstraction over Hibernate’s EntityManager). It looks and feels like Spring Data JPA, but without excessive “magic”.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

All your classic JPA-like annotations are there:

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

@Table("movies")
public class Movie {

    @Id
    private long id;
    private String title;

    // boilerplate removed
}

Now it’s possible to implement custom repositories using the standard Spring Data way:

@Repository
public interface MovieRepository extends CrudRepository<Movie, Long> {
}

And we’re ready to perform CRUD operations on our entity without writing any mapping code, and dealing with lazy-loading and dirty-checking:

private final MovieRepository movieRepository;

// ...

movieRepository.findById(42L).ifPresent(System.out::println);

However, it’s worth remembering that Spring Data JDBC doesn’t provide automatic schema generation and Spring Data’s signature derived queries.

So if we want to expose custom methods, we need to resort to @Query annotations on top of repository interface methods:

@Repository
public interface MovieRepository extends CrudRepository<Movie, Long> {

    @Query("SELECT * FROM movies WHERE title=:title")
    List<Movie> findByTitle(@Param("title") String title);
}

Besides Spring Data repository interfaces, we get access to a variation over JdbcTemplate called JdbcAggregateTemplate, which exposes JdbcTemplate-like methods but without direct SQL manipulation:

private final JdbcAggregateTemplate jdbcAggregateTemplate;

// ...

jdbcAggregateTemplate.insert(
  new Movie(42, "The Hitchhiker's Guide to the Galaxy"));

Definitely worth researching if you don’t rely on Hibernate’s full potential.

You can find a complete code sample on GitHub.

Summary

As you can see, if you don’t rely on Hibernate’s advanced features, Spring provides handy lightweight alternatives that can simplify your projects.

Personally, I’d rather choose raw JdbcTemplate over Spring Data JDBC since the latter has a steeper learning curve and some unusual edge cases which can be confusing for people that got used to Hibernate. At the same time, I don’t mind paying the price of writing my entity mapping code.

If you want to have a look outside the Spring ecosystem, you can have a look at:

  1. JDBI
  2. jOOQ

And keep an eye out for the JdbcTemplate’s reactive brother:

Both code samples are available on GitHub. Feel free to play around with them.




If you enjoyed the content, consider supporting the site: