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 .