The difference between Spring Data JPA’s findById, getOne, getById, and findOne methods


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


Spring Data’s JpaRepository provides a huge set of methods that simplify the implementation of your database operations. You can use them to persist, remove, and read an entity object. Choosing the right method for your use case is one of the few problems these interfaces create. And that’s sometimes not as easy as you might expect. One example is the methods findByIdgetOne, getById, and findOne. Based on their name, all seem to do the same. So, when should you use which one?

That’s the question I will answer in this article. There are a few important differences between those methods. And as soon as you know them, picking the right one for your specific use case becomes easy.

Check the implementations of Spring Data JPA’s repository methods

Before I get into the implementation details of those 3 methods, I want to quickly show you how you can check the implementation of any of Spring Data JPA’s repository methods. You can use that whenever you’re not entirely sure how a method works or what the differences between methods with similar names are.

One of the key benefits of Spring Data JPA is that it provides default implementations for all of its repositories. You can easily find them using the tools provided by your IDE. You only need to open up one of your own repository interfaces. It extends at least one of Spring Data JPA’s standard interfaces.

public interface ChessPlayerRepository extends JpaRepository<ChessPlayer, Long>{}

In this example, that’s the JpaRepository interface. You can use your IDE to show you all implementations of that interface. When you do that for the JpaRepository interface, you will find a class called SimpleJpaRepository. It’s Spring Data JPA’s standard implementation of the JpaRepository interface with all its methods. I will reference it multiple times during this article.

4 methods that seem to do the same

When your repository extends Spring Data JPA’s JpaRepository, it inherits the methods findById, getOne, getById, and findOne. Based on their name, it seems like they are doing the same.

But Spring Data obviously doesn’t provide 4 identical methods under different names. So, let’s take a closer look at these methods and find their differences.

The findById method

Spring Data JPA’s CrudRepository is a super interface of the JpaRepository, and it defines the method Optional findById(ID id). The CrudRepository interface is not JPA-specific. The Spring Data parent project defines it. Due to that, you can find different implementations of it in all Spring Data modules.

The SimpleJpaRepository class provides the JPA-specific implementation of the findById method. As you can see in the following code snippet, that implementation is based on the find method defined by JPA’s EntityManager and wraps the retrieved entity object in an Optional.

public Optional<T> findById(ID id) {

	Assert.notNull(id, ID_MUST_NOT_BE_NULL);

	Class<T> domainType = getDomainClass();

	if (metadata == null) {
		return Optional.ofNullable(em.find(domainType, id));
	}

	LockModeType type = metadata.getLockModeType();

	Map<String, Object> hints = new HashMap<>();

	getQueryHints().withFetchGraphs(em).forEach(hints::put);

	if (metadata.getComment() != null && provider.getCommentHintKey() != null) {
		hints.put(provider.getCommentHintKey(), provider.getCommentHintValue(metadata.getComment()));
	}

	return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}

Source: https://github.com/spring-projects/spring-data-jpa/blob/main/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java#L296

Calling the EntityManager‘s find method instead of generating and executing a query enables Hibernate to utilize its 1st and 2nd level caches. That can provide performance benefits if the entity object was already fetched from the database within the current session or if the entity is part of the 2nd level cache. That makes the findById method your best option if you want to get an entity object with all its attributes by its primary key attribute.

The main difference between the findById method and a simple call of the EntityManager.find method is that it considers the LockModeType you configured for your repository and any EntityGraph associated with the repository method.

The LockModeType allows you to configure optimistic or pessimistic locking to handle concurrent modifications on the retrieved entities. I explain both concepts in more detail in the Advanced Hibernate course included in the Persistence Hub.

And an EntityGraph enables you to define the associations you want to initialize when fetching an entity object. This prevents LazyInitializationExpections and helps you avoid n+1 select issues.

The getOne and getById methods

In Spring Data JPA 3, the getOne and getById methods are deprecated and call the getReferenceById method internally. As its name indicates, that method returns a reference to an entity object instead of the entity object itself.

public T getReferenceById(ID id) {

	Assert.notNull(id, ID_MUST_NOT_BE_NULL);
	return em.getReference(getDomainClass(), id);
}

Source: https://github.com/spring-projects/spring-data-jpa/blob/main/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java#L351

As I explained in my guide to JPA’s getReference method, Hibernate doesn’t execute an SQL query when you call the getReference method. If the entity is not managed, Hibernate instantiates a proxy object and initializes the primary key attribute.

This is similar to an uninitialized, lazily-fetched association that gives you a proxy object. In both cases, only the primary key attributes are set. When you access the first non-primary key attribute, Hibernate executes a database query to fetch all attributes. That’s also the first time Hibernate checks if the referenced entity object exists. If the executed query returns no result, Hibernate throws an exception.

The delayed execution of the SQL query can cause problems and doesn’t provide any benefits if you need to access any non-primary key attributes. The getOne and getById methods are, therefore, not a good fit for use cases working with the information represented by an entity object. But they provide a very efficient way to get an entity reference if you want to initialize an association. In those cases, a reference provides all the information Hibernate needs.

The findOne methods

Spring Data JPA provides 2 versions of the findOne method.

One version is defined by the QueryByExampleExecutor interface. You can call it to find an entity object that matches an example. Spring Data JPA then generates a WHERE clause based on the provided entity object and example configuration.

// Sample player
ChessPlayer examplePlayer = new ChessPlayer();
examplePlayer.setFirstName("Magnus");
examplePlayer.setLastName("Carlsen");

Example<ChessPlayer> example = Example.of(examplePlayer);
Optional<ChessPlayer> player = playerRepo.findOne(example);

As I explain in the Spring Data JPA course in the Persistence Hub, you can customize how and for which entity attributes Spring generates the predicates for your WHERE clause. By default, it generates a query that selects an entity object and includes an equal predicate for each provided attribute.

2023-02-01 15:48:11.370 DEBUG 27840 --- [           main] org.hibernate.SQL                        : 
    select
        chessplaye0_.id as id1_1_,
        chessplaye0_.birth_date as birth_da2_1_,
        chessplaye0_.first_name as first_na3_1_,
        chessplaye0_.last_name as last_nam4_1_,
        chessplaye0_.version as version5_1_ 
    from
        chess_player chessplaye0_ 
    where
        chessplaye0_.first_name=? 
        and chessplaye0_.last_name=? 
        and chessplaye0_.version=0

The other version expects a Specification object that defines the where clause of a query. The specification is a concept defined in Domain Driven Design. They are too complex to explain in this article, and you don’t need to know the details to understand the differences between the discussed methods of the JpaRepository interface. If you want to learn more about this feature, I recommend joining the Persistence Hub and taking the Spring Data JPA course.

The main idea of the specification concept is that each specification defines a basic business rule, which you can combine into a complex ruleset. When using this concept with Spring Data JPA, each specification defines a small part of the WHERE clause.

public class ChessPlayerSpecs  {

    public static Specification<ChessPlayer> playedInTournament(String tournamentName) {
        return (root, query, builder) -> {
            SetJoin<ChessPlayer, ChessTournament> tournament = root.join(ChessPlayer_.tournaments);
            return builder.equal(tournament.get(ChessTournament_.name), tournamentName);
        };
    }

    public static Specification<ChessPlayer> hasFirstName(String firstName) {
        return (root, query, builder) -> {
            return builder.equal(root.get(ChessPlayer_.firstName), firstName);
        };
    }
}

Depending on the needs of your business code, you can use these specifications and combine them to create complex queries.

Specification<ChessPlayer> spec = ChessPlayerSpecs.playedInTournament("Tata Steel Chess Tournament 2023")
												  .and(ChessPlayerSpecs.hasFirstName("Magnus"));
Optional<ChessPlayer> player = playerRepo.findOne(spec);

The short descriptions of both findOne methods and the 2 code samples already show the difference between these methods and the previously discussed findById, getOne, and getById methods. The previously discussed methods only get an entity object or a reference by its primary key. Both versions of the findOne method enable you to find entity objects by non-primary key attributes and define queries of any complexity.

Conclusion

Spring Data JPA’s standard repository interfaces define various methods that you can use to implement your persistence layer. Some of them might have similar names, but their functionality differs. When using any of those methods in your code, you should know how they work internally and under which circumstances you should use them.

In this article, we had a closer look at multiple methods that return 1 entity object. You should use:

  • the findById method to select an entity object by its primary key and initialize its attributes.
  • the getReferenceById method to get a reference to an entity object that you can use to initialize associations. The getOne and getById methods are deprecated and call the getReferenceById method.
  • the findOne method if you want to use Spring Data JPA’s query by example or specification feature to define a query that returns 1 entity object.

These are just some of the methods provided by the JpaRepository that have similar names but provide different functionality. Other examples are the save(), saveAndFlush(), and saveAll() methods that I explained in a previous article.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.