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.
In this example, let's assume we're building an HR system. We have two primary entities: Employee
and Address
.
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;
}
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.
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> {
}
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.
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=?
Let's write the JUnit test cases to all the above query methods:
@Test
void findByFirstNameAndLastNameTest(){
List 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=?
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!