Tuesday, April 3, 2018

What is difference between getOne and findById in Spring Data JPA?

Both findById() and getOne() methods are used to retrieve an object from underlying datastore. The underlying mechanism for retrieving records is different for both these methods.

1. getOne() method

getOne() returns a reference to the entity with the given identifier. getOne internally invokes EntityManager.getReference() method. As per docs, this method will always return a proxy without hitting the database (lazily fetched). This method will throw EntityNotFoundException at the time of actual access if the requested entity does not exist in the database.
Example 1. EntityManager.getReference(Class, Object) Docs
Get an instance, whose state may be lazily fetched. If the requested instance does not exist in the database, the EntityNotFoundException is thrown when the instance state is first accessed. (The persistence provider runtime is permitted to throw the EntityNotFoundException when getReference is called.) The application should not expect that the instance state will be available upon detachment, unless it was accessed by the application while the entity manager was open.

2. findById() method

This method will actually hit the database and return the real object mapping to a row in the database. It is EAGER loaded operation that returns null if no record exists in database.

3. Which one to choose?

The only real difference between these methods is about the performance. Lazily loaded getOne() method avoids database roundtrip from the JVM as it never hits the database until the properties of returned proxy object are actually accessed.
There are scenarios when you just want to get retrieve an entity from database and assign it as a reference to another object, just to maintain the relationship (OneToOne or ManyToOne). Lets take a concrete example:
We have a Employee that belong to Department.
@Entity
@Table(name = "t_departments")
public class Department {

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

    private String name;
@Entity
@Table(name = "t_employees")
public class Employee {

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

    private String name;

    @ManyToOne  
    private Department department;
Employee object needs a reference of Department.
Now lets consider the following code where we want to create a new employee and assign it to a department.
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class HRService {

    @Autowired
    private DepartmentRepository departmentRepository;

    @Autowired
    private EmployeeRepository employeeRepository;

    public Department createDepartment() {
        Department dept = new Department();
        dept.setName("Product & Engg");
        return departmentRepository.save(dept);
    }

    public void createEmployee1(long deptId) {
        final Department pne = departmentRepository.getOne(deptId); 
        Employee employee = new Employee();
        employee.setName("Foo 1");
        employee.setDepartment(pne);
        employeeRepository.save(employee);
    }
We are retrieving a reference of Department object and assigning it to employee. Using getOne() is better than using findById() in this particular case, because we need not to fetch the details of department object.
generated SQL traces
insert into t_employees (department_id, name, id) values (?, ?, ?)
When we use findById() instead of getOne(), then an additional call to database is made to retrieve the department object, as shown in the below code:
    public void createEmployee2(long deptId) {
        Optional<Department> pne = departmentRepository.findById(deptId);
        Employee employee = new Employee();
        employee.setName("Foo 1");
        pne.ifPresent(department -> {
            employee.setDepartment(department);
        });
        employeeRepository.save(employee);
    }
generated SQL traces
select department0_.id as id1_4_0_, department0_.name as name2_4_0_ from t_departments department0_ where department0_.id=? 
insert into t_employees (department_id, name, id) values (?, ?, ?)
We can see here that an additional call to database is made to eagerly fetch the department record, and then assign it as a refernce in employee record.
Table 1. Comparison Summary
getOne()findById()
Better performance
An additional round-trip to database is required
Lazily loaded reference to target entity
Actually loads the entity for the given id
Useful only when access to properties of object is not required
Object is eagerly loaded so all attributes can be accessed
Throws EntityNotFoundException if actual object does not exist at the time of access invocation
Returns null if actual object corresponding to given Id does not exist

No comments:

Post a Comment

Your comment will be published after review from moderator