1. 개요

DAO(Data Access Object) 패턴은 추상 API를 사용하여 지속성 계층(일반적으로 관계형 데이터베이스이지만 다른 지속성 메커니즘일 수 있음)에서 애플리케이션/비즈니스 계층 을 분리할 수 있게 해주는 구조적 패턴입니다 .

API는 기본 스토리지 메커니즘에서 CRUD 작업을 수행하는 모든 복잡성을 응용 프로그램에서 숨깁니다. 이렇게 하면 두 레이어가 서로에 대해 아무것도 알지 못한 채 개별적으로 진화할 수 있습니다.

이 예제에서는 패턴의 구현에 대해 자세히 알아보고  JPA 엔티티 관리자 에 대한 호출을 추상화하는 데 패턴을 사용하는 방법을 배웁니다 .

2. 간단한 구현

DAO 패턴의 작동 방식을 이해하기 위해 기본 예제를 만들어 보겠습니다.

사용자를 관리하는 애플리케이션을 개발하고 싶다고 가정해 보겠습니다. 우리는 응용 프로그램의 도메인 모델을 데이터베이스에 대해 완전히 독립적으로 유지하려고 합니다. 따라서 이러한 구성 요소를 서로 깔끔하게 분리된 상태로 유지하는 간단한 DAO 클래스를 만들 것입니다.

2.1. 도메인 클래스

애플리케이션이 사용자와 함께 작동하므로 해당 도메인 모델을 구현하기 위해 하나의 클래스만 정의해야 합니다.

public class User {
    
    private String name;
    private String email;
    
    // constructors / standard setters / getters
}

User 클래스 는 사용자 데이터의 일반 컨테이너일 뿐이므로 강조할 가치가 있는 다른 동작을 구현하지 않습니다.

물론 여기서 중요한 디자인 선택은 구현될 수 있는 모든 지속성 메커니즘으로부터 격리된 이 클래스를 사용하는 애플리케이션을 유지하는 방법입니다.

이것이 바로 DAO 패턴이 해결하려는 문제입니다.

2.2. DAO API

도메인 모델을 지속성 계층에서 완전히 분리된 상태로 유지할 수 있는 방법을 볼 수 있도록 기본 DAO 계층을 정의해 보겠습니다 .

DAO API는 다음과 같습니다.

public interface Dao<T> {
    
    Optional<T> get(long id);
    
    List<T> getAll();
    
    void save(T t);
    
    void update(T t, String[] params);
    
    void delete(T t);
}

조감도에서 Dao 인터페이스는 T 유형의 개체에 대해 CRUD 작업을 수행하는 추상 API를 정의하는 것이 분명합니다 .

인터페이스가 제공하는 높은 수준의 추상화로 인해 User 개체 와 함께 작동하는 구체적이고 세분화된 구현을 쉽게 만들 수 있습니다.

2.3. UserDao 클래스 _

Dao 인터페이스 의 사용자별 구현을 정의해 보겠습니다 .

public class UserDao implements Dao<User> {
    
    private List<User> users = new ArrayList<>();
    
    public UserDao() {
        users.add(new User("John", "john@domain.com"));
        users.add(new User("Susan", "susan@domain.com"));
    }
    
    @Override
    public Optional<User> get(long id) {
        return Optional.ofNullable(users.get((int) id));
    }
    
    @Override
    public List<User> getAll() {
        return users;
    }
    
    @Override
    public void save(User user) {
        users.add(user);
    }
    
    @Override
    public void update(User user, String[] params) {
        user.setName(Objects.requireNonNull(
          params[0], "Name cannot be null"));
        user.setEmail(Objects.requireNonNull(
          params[1], "Email cannot be null"));
        
        users.add(user);
    }
    
    @Override
    public void delete(User user) {
        users.remove(user);
    }
}

UserDao 클래스 는 User 개체 를 가져오고 업데이트하고 제거하는 데 필요한 모든 기능을 구현 합니다.

단순화를 위해  사용자 List 은 생성자에서 두 개의 사용자 개체로 채워지는 메모리 내 데이터베이스처럼 작동합니다 .

물론 예를 들어 관계형 데이터베이스와 함께 작동할 수 있도록 다른 방법을 쉽게 리팩토링할 수 있습니다.

UserUserDao 클래스는 동일한 애플리케이션 내에서 독립적으로 공존 하지만 애플리케이션 논리에서 숨겨진 지속성 계층을 유지하기 위해 후자를 어떻게 사용할 수 있는지 확인해야 합니다.

public class UserApplication {

    private static Dao<User> userDao;

    public static void main(String[] args) {
        userDao = new UserDao();
        
        User user1 = getUser(0);
        System.out.println(user1);
        userDao.update(user1, new String[]{"Jake", "jake@domain.com"});
        
        User user2 = getUser(1);
        userDao.delete(user2);
        userDao.save(new User("Julie", "julie@domain.com"));
        
        userDao.getAll().forEach(user -> System.out.println(user.getName()));
    }

    private static User getUser(long id) {
        Optional<User> user = userDao.get(id);
        
        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
}

이 예제는 인위적이지만 DAO 패턴 뒤에 있는 동기를 간단히 보여줍니다. 이 경우 기본 메서드는 UserDao 인스턴스를 사용하여 몇 가지 User 개체 에 대한 CRUD 작업을 수행 합니다.

이 프로세스의 가장 관련성이 높은 측면은 UserDao 가 개체가 유지, 업데이트 및 삭제되는 방법에 대한 모든 하위 수준 세부 정보를 응용 프로그램에서 숨기는 방법입니다.

3. JPA에서 패턴 사용

개발자들 사이에서는 JPA 릴리스가 DAO 패턴의 기능을 0으로 낮추었다고 생각하는 경향이 있습니다. 패턴은 JPA의 엔티티 관리자가 제공하는 것 위에 추상화 및 복잡성의 또 다른 계층이 됩니다.

이것은 일부 시나리오에서 사실입니다. 그럼에도 불구하고 때때로 엔티티 관리자 API의 몇 가지 도메인별 메서드만 애플리케이션에 노출하기를 원할 수 있습니다. DAO 패턴은 이러한 경우에 적합합니다.

3.1. JpaUserDao 클래스 _

JPA의 엔티티 관리자가 즉시 제공하는 기능을 캡슐화하는 방법을 알아보기 위해 Dao 인터페이스 의 새 구현을 작성해 보겠습니다 .

public class JpaUserDao implements Dao<User> {
    
    private EntityManager entityManager;
    
    // standard constructors
    
    @Override
    public Optional<User> get(long id) {
        return Optional.ofNullable(entityManager.find(User.class, id));
    }
    
    @Override
    public List<User> getAll() {
        Query query = entityManager.createQuery("SELECT e FROM User e");
        return query.getResultList();
    }
    
    @Override
    public void save(User user) {
        executeInsideTransaction(entityManager -> entityManager.persist(user));
    }
    
    @Override
    public void update(User user, String[] params) {
        user.setName(Objects.requireNonNull(params[0], "Name cannot be null"));
        user.setEmail(Objects.requireNonNull(params[1], "Email cannot be null"));
        executeInsideTransaction(entityManager -> entityManager.merge(user));
    }
    
    @Override 
    public void delete(User user) {
        executeInsideTransaction(entityManager -> entityManager.remove(user));
    }
    
    private void executeInsideTransaction(Consumer<EntityManager> action) {
        EntityTransaction tx = entityManager.getTransaction();
        try {
            tx.begin();
            action.accept(entityManager);
            tx.commit(); 
        }
        catch (RuntimeException e) {
            tx.rollback();
            throw e;
        }
    }
}

JpaUserDao 클래스 는 JPA 구현에서 지원하는 모든 관계형 데이터베이스와 함께 작동할 수 있습니다.

또한 클래스를 자세히 살펴보면 구성  및  의존성 주입 을 사용하여 응용 프로그램에 필요한 엔터티 관리자 메서드만 호출할 수 있는 방법을 알 수 있습니다.

간단히 말해서 전체 엔티티 관리자의 API가 아닌 도메인별 Custom형 API가 있습니다.

3.2. 사용자 클래스 리팩터링

이 경우 Hibernate를 JPA 기본 구현으로 사용하므로 그에 따라 User 클래스를 리팩터링합니다.

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    private String name;
    private String email;
    
    // standard constructors / setters / getters
}

3.3. 프로그래밍 방식 으로 JPA 엔티티 관리자 부트스트랩 

이미 로컬 또는 원격으로 실행 중인 MySQL의 작업 인스턴스가 있고 일부 사용자 레코드로 채워진 데이터베이스 테이블 "users" 가 있다고 가정하면 데이터베이스 에서 CRUD 작업을 수행 하기 위해 JpaUserDao 클래스를 사용할 수 있도록 JPA 엔터티 관리자가 필요합니다. .

대부분의 경우 표준 접근 방식인 일반적인 persistence.xml  파일을 통해 이를 수행합니다.

이 경우, 우리는 XML 없는 접근 방식을 취하고 Hibernate의 편리한  EntityManagerFactoryBuilderImpl 클래스를 통해 일반 Java로 엔티티 관리자를 얻을 것입니다.

Java로 JPA 구현을 부트스트랩하는 방법에 대한 자세한 설명은 이 기사 를 확인하십시오 .

3.4. UserApplication 클래스 _

마지막으로 초기 UserApplication 클래스를 리팩터링하여 JpaUserDao 인스턴스와 함께 작동하고 User 엔터티 에서 CRUD 작업을 실행할 수 있도록 합니다.

public class UserApplication {

    private static Dao<User> jpaUserDao;

    // standard constructors
    
    public static void main(String[] args) {
        User user1 = getUser(1);
        System.out.println(user1);
        updateUser(user1, new String[]{"Jake", "jake@domain.com"});
        saveUser(new User("Monica", "monica@domain.com"));
        deleteUser(getUser(2));
        getAllUsers().forEach(user -> System.out.println(user.getName()));
    }
    
    public static User getUser(long id) {
        Optional<User> user = jpaUserDao.get(id);
        
        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
    
    public static List<User> getAllUsers() {
        return jpaUserDao.getAll();
    }
    
    public static void updateUser(User user, String[] params) {
        jpaUserDao.update(user, params);
    }
    
    public static void saveUser(User user) {
        jpaUserDao.save(user);
    }
    
    public static void deleteUser(User user) {
        jpaUserDao.delete(user);
    }
}

여기의 예는 매우 제한적입니다. 그러나 DAO 패턴의 기능을 엔티티 관리자가 제공하는 기능과 통합하는 방법을 보여주는 데 유용합니다.

대부분의 애플리케이션에는 JpaUserDao 인스턴스를 UserApplication 클래스에 주입하는 역할을 하는 DI 프레임워크가 있습니다. 단순화를 위해 이 프로세스의 세부 정보는 생략했습니다.

여기서 강조해야 할 가장 관련성이 높은 점은 JpaUserDao 클래스 가 지속성 계층이 CRUD 작업을 수행하는 방법에 대해 UserApplication 클래스를 완전히 독립적 으로 유지 하는 데 어떻게 도움이 되는지입니다.

또한 향후 MySQL을 다른 RDBMS(심지어 플랫 데이터베이스)로 교체할 수 있으며 Dao 인터페이스와 엔티티 관리자가 제공하는 추상화 수준 덕분에 애플리케이션이 예상대로 계속 작동할 것입니다 .

4. 결론

이 기사에서는 DAO 패턴의 핵심 개념에 대해 자세히 살펴보았습니다. Java로 구현하는 방법과 JPA의 엔티티 관리자 위에서 사용하는 방법을 살펴보았습니다.

늘 그렇듯이 이 기사에 나오는 모든 코드 샘플은 GitHub 에서 사용할 수 있습니다 .

Persistence footer banner