Spring Data JPA findById Anti-Pattern

Srikanth Dannarapu
Javarevisited
Published in
4 min readMar 9, 2023

--

The Spring Data JPA findById method is a common anti-pattern that can lead to performance issues in large-scale applications. This method is typically used to retrieve a single entity from the database by its primary key.

Problem1:

The problem with this approach is that it can lead to the creation of many unnecessary database queries. For example, consider a scenario where you need to retrieve multiple entities by their primary keys. If you use findById for each entity, you will end up executing one query for each entity, which can quickly become a performance bottleneck.

Let’s consider an example of using findById method in a Spring Data JPA application.

Assume we have an entity called User with the following fields:

@Entity
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String firstName;

private String lastName;

private String email;

// getters and setters
}

Now, let’s say we want to retrieve a single user by their id using findById method in a Spring Data JPA repository:

public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findById(Long id);
}

And we want to retrieve the user in our service layer:

@Service
public class UserService {

@Autowired
private UserRepository userRepository;

public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found with id " + id));
}
}

In this example, every time we call getUserById method, Spring Data JPA will execute a separate SQL query to retrieve the user by its id. If we have to retrieve multiple users by their id using findById, we will execute a separate SQL query for each user, leading to unnecessary database calls and potentially causing performance issues.

Solution To Problem1:

To avoid above issue, you should consider using the findAllById method instead. This method allows you to retrieve multiple entities in a single database query by passing in a collection of primary keys.

And here is an updated version of our service layer:

@Service
public class UserService {

@Autowired
private UserRepository userRepository;

public List<User> getUsersByIds(List<Long> ids) {
return userRepository.findAllById(ids);
}
}
SELECT * FROM User user WHERE user.id IN :ids

In this updated example, we are using the findAllById method to retrieve multiple users by their id in a single SQL query. This can significantly reduce the number of database calls and improve the performance of our application.

Problem2:

Another issue with findById is that it can lead to the creation of many unnecessary objects. Each time you call findById, Spring Data JPA creates a new entity object, even if the entity is already in the persistence context. This can lead to a significant increase in memory usage and garbage collection overhead.

To illustrate this issue, let’s consider an example of a one-to-many relationship between a Department entity and its related Employee entities. Here's how the entities might look like:

domain model
@Entity
@Table(name = "departments")
public class Department {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

@OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
private List<Employee> employees = new ArrayList<>();

// getters and setters
}

@Entity
@Table(name = "employees")
public class Employee {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;

// getters and setters
}

Note that the Department entity has a OneToMany relationship with the Employee entity, while the Employee entity has a ManyToOne relationship with the Department entity.

Here’s an example of using findById to retrieve a department from the database along with its related employees:

@Service
public class DepartmentService {

@Autowired
private DepartmentRepository departmentRepository;

public Department getDepartmentById(Long id) {
return departmentRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Department not found with id " + id));
}
}

When we call this method with an id, Hibernate will generate the following SQL query to fetch the Department object and its related Employee objects from the database:

SELECT * FROM departments WHERE id = ?

SELECT * FROM employees WHERE department_id = ?

Solution to Problem 2:

To avoid this issue, you should consider using the getOne method instead. This method returns a reference to the entity without actually loading it from the database. This can be useful when you only need to access the entity's primary key or a subset of its properties.

below is an example of using getOneto retrieve a reference to a department from the database without loading its related employees:

@Service
public class DepartmentService {

@Autowired
private DepartmentRepository departmentRepository;

public Department getDepartmentReferenceById(Long id) {
return departmentRepository.getOne(id);
}
}

When we call this method with an id, Hibernate will generate the following HQL query to fetch a proxy object representing the Department entity:

SELECT department FROM Department department WHERE department.id = ?

Note that the actual SQL query to fetch the related Employee objects will only be executed when we access the employees property of the Department object, or call a method on one of its related employees that requires database access.

So, in summary, if we need to access the related entities along with the main entity, we should use findById. On the other hand, if we only need to access the primary key or a subset of the properties of the entity and want to optimize performance by avoiding the loading of related entities, we can use getOne.

Thanks, before you go:

  • 👏 Please clap for the story and follow the author 👉
  • Please share your questions or insights in the comments section below. Let’s help each other and become better Java developers.
  • Let’s connect on LinkedIn

--

--