Spring Data JPA findBy Nested Object

Entities in real-world applications often have associations with other entities. These associations can be simple or complex, sometimes leading to deeply nested objects. Spring Data JPA makes querying these relationships a breeze using method conventions. In this blog post, we'll explore how to use the findBy method to query nested objects. 

JPA Entities - Employee and Address

In this example, let's assume we're building an HR system. We have two primary entities: Employee and Address.

Employee

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;
}

Address

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
@Table(name = "addresses")
public class Address1 {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String city;
    private String country;
}

Notice the @OneToOne annotation, indicating a one-to-one relationship between Employee and Address.

We are using below Lombok annotations to reduce the boilerplate code such as getter/setter methods:

@Getter: Generates getter methods for the fields of the class. 

@Setter: Generates setter methods for the fields of the class. 

@ToString: Generates an implementation of the toString method based on the fields of the class.

Spring Data JPA Repository - EmployeeRepository

Let's create an EmployeeRepository interface that extends the JpaRepository interface from Spring Data JPA:

import com.springdatajpa.springboot.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

Querying Nested Objects 

Let's say we want to fetch all employees based on their city. How do we achieve that? With Spring Data JPA, it's astonishingly simple:

import com.springdatajpa.springboot.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
   List<Employee> findByAddress_City(String city);
}

Notice the findByAddress_City method. The underscore (_) is a special character in Spring Data JPA and allows us to traverse the entity graph and bind the property to the nested property.

Under the Hood 

When you call findByAddress_City, Spring Data JPA will generate a SQL query similar to:

    select
        e1_0.id,
        e1_0.address_id,
        e1_0.name 
    from
        employees e1_0 
    left join
        addresses a1_0 
            on a1_0.id=e1_0.address_id 
    where
        a1_0.city=?

Testing - EmployeeRepository Query Methods 

Let's write the JUnit test cases to all the above query methods:

    @Test
    void findByFirstNameAndLastNameTest(){
        List<Employee> employees = employeeNestedRepository.findByAddress_City("Pune");

        employees.forEach((employee) -> {
            System.out.println(employee.toString());
        });
    }

Output:

Hibernate: 
    select
        e1_0.id,
        e1_0.address_id,
        e1_0.name 
    from
        employees e1_0 
    left join
        addresses a1_0 
            on a1_0.id=e1_0.address_id 
    where
        a1_0.city=?

Conclusion 

Spring Data JPA continues to shine when it comes to simplifying database interactions in Java applications. Its conventions around method naming, especially with nested objects, means that developers can quickly write queries without having to dive deep into SQL or JPQL. However, it's essential to be aware of the trade-offs and to know when to use these conventions vs. when to write custom queries. Happy coding!

Comments