Перейти к основному содержимому

Упростите DAO с помощью Spring и Java Generics

· 5 мин. чтения

1. Обзор

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

Мы будем основываться на классе Abstract DAO, который мы видели в нашей предыдущей статье о Spring и Hibernate, и добавим поддержку дженериков.


2. Hibernate и JPA DAO

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

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

Эти множественные реализации обычно можно заменить одним параметризованным DAO. Мы можем реализовать это таким образом, чтобы никакая функциональность не была потеряна, в полной мере используя безопасность типов, обеспечиваемую Java Generics.

Далее мы покажем две реализации этой концепции: одну для уровня сохраняемости, ориентированного на Hibernate, а другую — для JPA. Эти реализации ни в коем случае не завершены, но мы можем легко добавить больше дополнительных методов доступа к данным.

2.1. Абстрактная Hibernate DAO

Давайте быстро взглянем на класс AbstractHibernateDao :

public abstract class AbstractHibernateDao<T extends Serializable> {
private Class<T> clazz;

@Autowired
protected SessionFactory sessionFactory;

public void setClazz(final Class<T> clazzToSet) {
clazz = Preconditions.checkNotNull(clazzToSet);
}

public T findOne(final long id) {
return (T) getCurrentSession().get(clazz, id);
}

public List<T> findAll() {
return getCurrentSession().createQuery("from " + clazz.getName()).list();
}

public T create(final T entity) {
Preconditions.checkNotNull(entity);
getCurrentSession().saveOrUpdate(entity);
return entity;
}

public T update(final T entity) {
Preconditions.checkNotNull(entity);
return (T) getCurrentSession().merge(entity);
}

public void delete(final T entity) {
Preconditions.checkNotNull(entity);
getCurrentSession().delete(entity);
}

public void deleteById(final long entityId) {
final T entity = findOne(entityId);
Preconditions.checkState(entity != null);
delete(entity);
}

protected Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}

Это абстрактный класс с несколькими методами доступа к данным, который использует SessionFactory для управления сущностями.

Здесь мы используем предварительные условия Google Guava, чтобы убедиться, что метод или конструктор вызывается с допустимым значением параметра. Если предварительные условия не выполняются, создается специальное исключение.

2.2. Общий Hibernate DAO

Теперь, когда у нас есть абстрактный класс DAO, мы можем расширить его только один раз. Общая реализация DAO станет единственной реализацией , которая нам нужна:

@Repository
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class GenericHibernateDao<T extends Serializable>
extends AbstractHibernateDao<T> implements IGenericDao<T>{
//
}

Во- первых, обратите внимание, что универсальная реализация сама по себе является параметризованной , что позволяет клиенту выбирать правильный параметр в каждом конкретном случае. Это будет означать, что клиенты получат все преимущества безопасности типов без необходимости создавать несколько артефактов для каждой сущности.

Во- вторых, обратите внимание на объем прототипа этой универсальной реализации DAO . Использование этой области означает, что контейнер Spring будет создавать новый экземпляр DAO каждый раз, когда он запрашивается (в том числе при автоподключении). Это позволит службе использовать несколько DAO с разными параметрами для разных объектов по мере необходимости.

Причина, по которой эта область так важна, связана со способом, которым Spring инициализирует bean-компоненты в контейнере. Если оставить общий DAO без области действия, это будет означать использование одноэлементной области действия по умолчанию, что приведет к тому, что один экземпляр DAO будет жить в контейнере . Очевидно, что это было бы серьезным ограничением для любого более сложного сценария.

IGenericDao — это просто интерфейс для всех методов DAO, чтобы мы могли внедрить нужную нам реализацию :

public interface IGenericDao<T extends Serializable> {
void setClazz(Class< T > clazzToSet);

T findOne(final long id);

List<T> findAll();

T create(final T entity);

T update(final T entity);

void delete(final T entity);

void deleteById(final long entityId);
}

2.3. Абстрактный JPA DAO

AbstractJpaDao очень похож на AbstractHibernateDao :

public abstract class AbstractJpaDAO<T extends Serializable> {
private Class<T> clazz;

@PersistenceContext(unitName = "entityManagerFactory")
private EntityManager entityManager;

public final void setClazz(final Class<T> clazzToSet) {
this.clazz = clazzToSet;
}

public T findOne(final long id) {
return entityManager.find(clazz, id);
}

@SuppressWarnings("unchecked")
public List<T> findAll() {
return entityManager.createQuery("from " + clazz.getName()).getResultList();
}

public T create(final T entity) {
entityManager.persist(entity);
return entity;
}

public T update(final T entity) {
return entityManager.merge(entity);
}

public void delete(final T entity) {
entityManager.remove(entity);
}

public void deleteById(final long entityId) {
final T entity = findOne(entityId);
delete(entity);
}
}

Подобно реализации Hibernate DAO, мы используем Java Persistence API напрямую, не полагаясь на ныне устаревший Spring JpaTemplate .

2.4. Общий JPA DAO

Подобно реализации Hibernate, объект доступа к данным JPA также прост:

@Repository
@Scope( BeanDefinition.SCOPE_PROTOTYPE )
public class GenericJpaDao< T extends Serializable >
extends AbstractJpaDao< T > implements IGenericDao< T >{
//
}

3. Внедрение этого DAO

Теперь у нас есть один интерфейс DAO, который мы можем внедрить. Нам также нужно указать класс:

@Service
class FooService implements IFooService{

IGenericDao<Foo> dao;

@Autowired
public void setDao(IGenericDao<Foo> daoToSet) {
dao = daoToSet;
dao.setClazz(Foo.class);
}

// ...
}

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

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

4. Вывод

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

Пошаговое введение в настройку контекста Spring с использованием конфигурации на основе Java и базовой помпы Maven для проекта см . в этой статье .

Наконец, код для этой статьи можно найти в проекте GitHub .