Шаблон DAO в Java

Шаблон DAO в Java

1. обзор

Шаблон объекта доступа к данным (DAO) - это структурный шаблон, который позволяет намisolate the application/business layer from the persistence layer (usually a relational database, but it could be any other persistence mechanism) using an abstract API.

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

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

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

Введение в Spring Data JPA

Введение в Spring Data JPA с Spring 4 - конфигурация Spring, DAO, ручные и сгенерированные запросы и управление транзакциями.

Read more

Обзор JPA / Hibernate Типы Каскадов

Быстрый и практический обзор типов каскада JPA / Hibernate.

Read more

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

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

Допустим, мы хотим разработать приложение, управляющее пользователями. Чтобы модель предметной области приложения полностью не зависела от базы данных, мы создадимa simple DAO class that will take care of keeping these components neatly decoupled from each other.

2.1. Доменный класс

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

public class User {

    private String name;
    private String email;

    // constructors / standard setters / getters
}

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

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

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

2.2. API DAO

Давайте определим базовый уровень DAO, чтобы увидеть, как он можетkeep the domain model completely decoupled from the persistence layer.

Вот 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.

For simplicity’s sake, the users List acts like an in-memory database, which is populated with a couple of User objects in the constructor.

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

Хотя классы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"));
    }
}

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

The most relevant facet of this process is how UserDao hides from the application all the low-level details on how the objects are persisted, updated, and deleted.

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

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

Безусловно, в некоторых сценариях это действительно так. Даже в этом случае, sometimes we just want to expose to our application only a few domain-specific methods of the entity manager’s 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 позволяет нам вызывать только те методы диспетчера сущностей, которые требуются нашему приложению.

Проще говоря, у нас есть специализированный API для домена, а не API всего менеджера сущностей.

3.2. Рефакторинг классаUser

В этом случае мы будем использовать 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 Entity Manager

Предполагая, что у нас уже есть рабочий экземпляр MySQL, работающий локально или удаленно, и таблица базы данных“users” заполнена некоторыми пользовательскими записями, нам нужно получить диспетчер сущностей JPA, чтобы мы могли использовать классJpaUserDao для выполнения CRUD-операций в базе данных.

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

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

Для подробного объяснения того, как запустить реализацию JPA с помощью Java, проверьтеthis article.

3.4. КлассUserApplication

Наконец, давайте реорганизуем исходный класс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. Для простоты мы опустили детали этого процесса.

Здесь наиболее важно подчеркнуть, какthe JpaUserDao class helps to keep the UserApplication class completely agnostic about how the persistence layer performs CRUD operations.

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

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

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

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