Рубрики
Без рубрики

Шаблон DAO в Java

Узнайте, как реализовать шаблон объекта доступа к данным (DAO) в Java, чтобы изолировать уровни персистентности и бизнес-уровни вашего приложения.

Автор оригинала: baeldung.

1. Обзор

Шаблон объекта доступа к данным (DAO) – это структурный шаблон, который позволяет нам изолировать уровень приложения/бизнеса от уровня персистентности (обычно это реляционная база данных, но это может быть любой другой механизм персистентности) с помощью абстрактного API .

Функциональность этого API заключается в том, чтобы скрыть от приложения все сложности, связанные с выполнением операций CRUD в базовом механизме хранения. Это позволяет обоим слоям развиваться отдельно, ничего не зная друг о друге.

В этом уроке мы глубоко погрузимся в реализацию шаблона и узнаем, как использовать его для абстрагирования вызовов JPA entity manager .

Дальнейшее чтение:

Введение в весенние данные JPA

Обзор типов каскадов JPA/Hibernate

2. Простая Реализация

Чтобы понять, как работает шаблон DAO, давайте создадим базовый пример.

Допустим, мы хотим разработать приложение, которое управляет пользователями. Чтобы модель домена приложения полностью не зависела от базы данных, мы создадим простой класс DAO, который позаботится о том, чтобы эти компоненты были аккуратно отделены друг от друга .

2.1. Класс Домена

Поскольку наше приложение будет работать с пользователями, нам нужно определить только один класс для реализации его модели домена:

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

Класс User является простым контейнером для пользовательских данных, поэтому он не реализует никакого другого поведения, заслуживающего внимания.

Конечно, наиболее релевантный выбор дизайна, который нам нужно сделать здесь, заключается в том, как изолировать приложение, использующее этот класс, от любого механизма сохранения, который может быть реализован в какой-то момент.

Ну, это именно та проблема, которую пытается решить шаблон DAO.

2.2. API DAO

Давайте определим базовый слой DAO, чтобы мы могли увидеть, как он может полностью отделить модель домена от уровня персистентности.

Вот API DAO:

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

С высоты птичьего полета ясно видно, что интерфейс Dao определяет абстрактный API, который выполняет операции CRUD с объектами типа T .

Благодаря высокому уровню абстракции, который обеспечивает интерфейс, легко создать конкретную, мелкозернистую реализацию, которая работает с объектами User .

2.3. Класс UserDao

Давайте определим пользовательскую реализацию интерфейса Dao :

public class UserDao implements Dao {
    
    private List users = new ArrayList<>();
    
    public UserDao() {
        users.add(new User("John", "[email protected]"));
        users.add(new User("Susan", "[email protected]"));
    }
    
    @Override
    public Optional get(long id) {
        return Optional.ofNullable(users.get((int) id));
    }
    
    @Override
    public List 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 .

Для простоты список users действует как база данных в памяти, которая заполняется несколькими объектами User в конструкторе .

Конечно, легко рефакторировать другие методы, чтобы они могли работать, например, с реляционной базой данных.

Хотя оба класса User и UserDao сосуществуют независимо в одном приложении, нам все равно нужно посмотреть, как последний можно использовать для сохранения уровня персистентности, скрытого от логики приложения:

public class UserApplication {

    private static Dao userDao;

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

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

Пример надуманный, но он показывает, в двух словах, мотивы, лежащие в основе шаблона ДАО. В этом случае метод main просто использует экземпляр UserDao для выполнения операций CRUD с несколькими объектами User .

Наиболее важным аспектом этого процесса является то, как UserDao скрывает от приложения все низкоуровневые сведения о том, как объекты сохраняются, обновляются и удаляются .

3. Использование шаблона С JPA

Среди разработчиков существует общая тенденция полагать, что выпуск JPA снизил функциональность шаблона DAO до нуля, поскольку шаблон становится просто еще одним уровнем абстракции и сложности, реализованным поверх того, который предоставляется менеджером сущностей JPA.

Несомненно, в некоторых сценариях это верно. Тем не менее , иногда мы просто хотим предоставить нашему приложению только несколько специфичных для домена методов API менеджера сущностей. В таких случаях шаблон DAO имеет свое место.

3.1. Класс JpaUserDao

С учетом сказанного, давайте создадим новую реализацию интерфейса Dao , чтобы мы могли увидеть, как он может инкапсулировать функциональность, которую менеджер сущностей JPA предоставляет из коробки:

public class JpaUserDao implements Dao {
    
    private EntityManager entityManager;
    
    // standard constructors
    
    @Override
    public Optional get(long id) {
        return Optional.ofNullable(entityManager.find(User.class, id));
    }
    
    @Override
    public List 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 action) {
        EntityTransaction tx = entityManager.getTransaction();
        try {
            tx.begin();
            action.accept(entityManager);
            tx.commit(); 
        }
        catch (RuntimeException e) {
            tx.rollback();
            throw e;
        }
    }
}

То JpaUserDao класс способен работать с любой реляционной базой данных, поддерживаемой реализацией JPA.

Кроме того, если мы внимательно посмотрим на класс, мы поймем, как использование Composition и Dependency Injection позволяет нам вызывать только методы entity manager, требуемые нашим приложением.

Проще говоря, у нас есть специализированный API для конкретного домена, а не 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, работающий локально или удаленно, и таблица базы данных “пользователи” , заполненная некоторыми записями пользователей, нам нужно получить менеджер сущностей JPA, чтобы мы могли использовать класс JpaUserDao для выполнения операций CRUD в базе данных.

В большинстве случаев мы достигаем этого с помощью типичного “persistence.xml” файл, что является стандартным подходом.

В этом случае мы воспользуемся подходом “без xml” и получим entitymanager с простой Java через удобный класс Hibernate EntityManagerFactoryBuilderImpl .

Для получения подробного объяснения того, как загрузить реализацию JPA с Java, пожалуйста, ознакомьтесь с этой статьей .

3.4. Класс UserApplication

Наконец, давайте проведем рефакторинг исходного Пользовательского приложения класса, чтобы он мог работать с экземпляром JpaUserDao и выполнять операции CRUD над сущностями User :

public class UserApplication {

    private static Dao jpaUserDao;

    // standard constructors
    
    public static void main(String[] args) {
        User user1 = getUser(1);
        System.out.println(user1);
        updateUser(user1, new String[]{"Jake", "[email protected]"});
        saveUser(new User("Monica", "[email protected]"));
        deleteUser(getUser(2));
        getAllUsers().forEach(user -> System.out.println(user.getName()));
    }
    
    public static User getUser(long id) {
        Optional user = jpaUserDao.get(id);
        
        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
    
    public static List 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 с той, которую предоставляет менеджер сущностей.

В большинстве приложений существует платформа DI, которая отвечает за внедрение экземпляра JpaUserDao в класс UserApplication . Для простоты мы опустили детали этого процесса.

Наиболее важным моментом, который следует подчеркнуть здесь, является то, как класс JpaUserDao помогает сохранить Пользовательское приложение класс полностью независимым от того, как слой персистентности выполняет операции CRUD .

Кроме того, мы могли бы поменять MySQL на любую другую СУБД (и даже на плоскую базу данных) в дальнейшем, и все же наше приложение продолжало бы работать, как ожидалось, благодаря уровню абстракции, обеспечиваемому интерфейсом Dao и менеджером сущностей.

4. Заключение

В этой статье мы подробно рассмотрели ключевые концепции шаблона DAO, как реализовать его в Java и как использовать его поверх менеджера сущностей JPA.

Как обычно, все примеры кода, показанные в этой статье, доступны на GitHub .