Problems

Service 에서 사용되는 DAO 의 개수는 이전처럼 한 개로만 구성될 수 있지만, 실제 Real-Time Service 에서는 Customer, Student, Product, Book 등 여러 개의 DAO 를 사용한다.

그렇다면 늘어나는 DAO 의 개수에 따라서 매번 DAO Interface, DAO Implemetation 를 해주는 방식은 효율적인 것일까?

기본적인 CRUD methods 들은 유지한 채로, Entity 와 Primary Key 만 따로 지정해주는 방식으로 여러 개의 DAO 를 define 할 수 있으면 매우 효율적일 것 같다.

Spring Data JPA

Spring Data JPA 가 이에 대한 solution 이 될 수 있다. Spring Data JPA 에서는 entity type 과 primary key 를 plug in 만 해주면 CRUD methods 를 자동으로 제공해주는 spring project 이다. 이는 boiler-plate DAO code, 즉 반복적으로 작성되는 code 작성을 보통 70% 넘게 줄여주는 효과가 있다고 한다.

Spring Data JPA 는 JpaRepository 라는 Interface 를 제공하는데, 기본적인 CRUD method 들을 제공한다. 사용하는 방법도 매우 간단하다. 기존에는 DAO Interface 를 만들고, 이에 대한 DAO Implementation code 를 작성해야 했다면, 아래와 같이 단순히 JpaRepository interface 를 extends 하는 것만으로도 모든 작업은 완료된다.

public interface EmployeeRepository extends JpaRespository<Employee, Integer> {}

위와 같이 entity type 과 primary key type 만 지정해주면 끝난다. 이후에 Service Impementation code 에서 해당 repository 를 injection 하여 사용하면 된다.

Advanced Features

Spring Data JPA 의 advanced features 는 다음과 같다.

  1. Extending and adding custom queries with JPQL
  2. Query Domain Specific Language (Query DSL)
  3. Defining custom methods(low-level coding)
Using Spring Data JPA

이전에 만들었던 DAO 대신 Spring Data JPA 를 사용해보자. 우선 기존의 DAO Interface 와 Implementation 은 삭제하고, dao package 에 EmployeeRepository 라는 Interface 를 새로 만들자. 그리고 단순히 JpaRepository 를 extends 해주면 Repository 에 대한 사용 준비는 벌써 끝났다.

이후 EmployeeServiceImpl.java 에서 다음과 같이 DAO 대신 Repository 를 injection 해주면 된다.

private EmployeeRepository employeeRepository;  
  
@Autowired  
public EmployeeServiceImpl(EmployeeRepository employeeRepository) {  
    this.employeeRepository = employeeRepository;  
}

그리고 DAO 를 사용했던 code 부분들을 employeeRepository 로 수정한다. 이때, findById() method 는 기존 code 에서 수정이 필요하다.

우선, JpaRepository 에서 제공하는 findById method 의 return type 이 Optional 이기 때문에 기존 code 에 대한 수정이 필요하다.

Optionalnull 일 수도 있는 값을 감싸는 Wrapper Class 인데, 이를 이용하여 안전하게 null 값을 다룰 수 있다. 또한 Optional 은 해당 값이 null 이 아닐 때는 Optional.of(value) 를 통하여 값을 return 하고, null 이라면 Optional.empty() 를 통하여 해당 값이 null 임을 간접적으로 알려주는 것이다.

실제 Optional.empty() 의 내부 구현을 보면, return 하려는 값이 private static final Optional<?> EMPTY = new Optional<>(null);, 즉 Optional 로 감싸진 형태의 null 값이기 때문에 직접적으로 Optional 값을 null 과 비교하는 것은 바람직하지 않다.

따라서, Service Class 에서의 findById method 를 다음과 같이 수정해야 한다.

@Override  
public Employee findById(int theId) {  
    Optional<Employee> result = employeeRepository.findById(theId);  
    Employee theEmployee = null;  
  
    if (result.isPresent()) {  
        theEmployee = result.get();  
    }  
    else {  
        throw new RuntimeException("Did not find employee id - " + theId);  
    }  
  
    return theEmployee;  
}

이렇게 Option.isPresent() 를 통하여 값이 null 인지 아닌지를 판단하고, 아니라면 RuntimeException 을 발생시킨다. 결과적으로 해당 method 는 반드시 값이 존재할 때만 해당 theEmployee object 를 return 하게 된다.

그러나 code 자체가 약간은 깔끔하지 않기 때문에 refactoring 을 수행할 수 있을 것 같다.

@Override
public Employee findById(int theId) { 
	return employeeRepository.findById(theId) 
		.orElseThrow(() -> new RuntimeException("Did not find employee id - " + theId));
}

위와 같이 Lambda 를 사용하여 functional programming 을 사용하면 훨씬 깔끔하고 가독성이 더 나아진 느낌을 준다.

Spring Data JPA - Test

위와 같은 상태의 DB table 에서, Service 에서 mapping 했던 endpoints 들을 모두 test 해보면 모두 성공적으로 결과가 response 되고, 아래와 같이 실제 DB 에도 잘 적용된 모습을 볼 수 있다.