Статьи

Упрощение уровня доступа к данным с помощью Spring и Java Generics

1. Обзор

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

2. Hibernate и JPA DAOs

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

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

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public abstract class AbstractHibernateDao< T extends Serializable > {
 
   private Class< T > clazz;
 
   @Autowired
   SessionFactory sessionFactory;
 
   public final void setClazz( Class< T > clazzToSet ){
      this.clazz = clazzToSet;
   }
 
   public T findOne( long id ){
      return (T) getCurrentSession().get( clazz, id );
   }
   public List< T > findAll(){
      return getCurrentSession().createQuery( "from " + clazz.getName() ).list();
   }
 
   public void create( T entity ){
      getCurrentSession().persist( entity );
   }
 
   public void update( T entity ){
      getCurrentSession().merge( entity );
   }
 
   public void delete( T entity ){
      getCurrentSession().delete( entity );
   }
   public void deleteById( long entityId ){
      T entity = findOne( entityId );
      delete( entity );
   }
 
   protected final Session getCurrentSession(){
      return sessionFactory.getCurrentSession();
   }
}

DAO использует Hibernate API напрямую, без использования каких-либо шаблонов Spring (таких как HibernateTemplate ). Использование шаблонов, а также управление SessionFactory, которая автоматически подключается в DAO, были рассмотрены в руководстве Hibernate DAO .

2.2. Универсальный Hibernate DAO

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

1
2
3
4
5
6
@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, так что мы можем внедрить нашу реализацию со Spring в (или во что угодно):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public interface IGenericDao<T extends Serializable> {
 
   T findOne(final long id);
 
   List<T> findAll();
 
   void create(final T entity);
 
   T update(final T entity);
 
   void delete(final T entity);
 
   void deleteById(final long entityId);
}

2,3. Абстрактная JPA DAO

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public abstract class AbstractJpaDao< T extends Serializable > {
 
   private Class< T > clazz;
 
   @PersistenceContext
   EntityManager entityManager;
 
   public void setClazz( Class< T > clazzToSet ){
      this.clazz = clazzToSet;
   }
 
   public T findOne( Long id ){
      return entityManager.find( clazz, id );
   }
   public List< T > findAll(){
      return entityManager.createQuery( "from " + clazz.getName() )
       .getResultList();
   }
 
   public void save( T entity ){
      entityManager.persist( entity );
   }
 
   public void update( T entity ){
      entityManager.merge( entity );
   }
 
   public void delete( T entity ){
      entityManager.remove( entity );
   }
   public void deleteById( Long entityId ){
      T entity = getById( entityId );
      delete( entity );
   }
}

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

2,4. Общий JPA DAO

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

1
2
3
4
5
6
@Repository
@Scope( BeanDefinition.SCOPE_PROTOTYPE )
public class GenericJpaDao< T extends Serializable >
 extends AbstractJpaDao< T > implements IGenericDao< T >{
   //
}

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

В настоящее время Spring вводит один DAO ; также класс должен быть указан:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Service
class FooService implements IFooService{
 
   IGenericDao< Foo > dao;
 
   @Autowired
   public void setDao( IGenericDao< Foo > daoToSet ){
      dao = daoToSet;
      dao.setClazz( Foo.class );
   }
 
   // ...
 
}

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

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

4. Вывод

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

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

Ссылка: Упростите DAO с помощью Spring и Java Generics от нашего партнера JCG Юджина Параскива в блоге baeldung .