Опростете DAO с Spring и Java Generics

1. Общ преглед

Тази статия ще се съсредоточи върху опростяването на слоя DAO чрез използване на един, генериран обект за достъп до данни за всички обекти в системата, което ще доведе до елегантен достъп до данни, без излишна бъркотия или многословност.

Ще надграждаме абстрактния клас DAO, който видяхме в предишната ни статия за Spring и Hibernate, и ще добавим поддръжка за генерици.

2. Hibernate и JPA DAO

Повечето производствени кодови бази имат някакъв DAO слой. Обикновено внедряването варира от множество класове без абстрактен базов клас до някакъв генериран клас. Едно обаче е последователно - винаги има повече от едно . Най-вероятно има едно към едно отношение между DAO и обектите в системата.

Също така, в зависимост от нивото на участващите генерици, действителните имплементации могат да варират от силно дублиран код до почти празен, като по-голямата част от логиката е групирана в основен абстрактен клас.

Тези множество изпълнения обикновено могат да бъдат заменени от един параметризиран DAO. Можем да приложим това така, че да не се загуби функционалност, като се възползваме изцяло от безопасността на типа, предоставена от Java Generics.

По-нататък ще покажем две реализации на тази концепция, едната за ориентиран към хибернация слой, а другата се фокусира върху JPA. Тези внедрения в никакъв случай не са завършени, но можем лесно да добавим още включени допълнителни методи за достъп до данни.

2.1. Абстрактният зимен сън DAO

Нека да разгледаме набързо класа AbstractHibernateDao :

public abstract class AbstractHibernateDao { private Class clazz; @Autowired SessionFactory sessionFactory; public void setClazz(Class clazzToSet){ this.clazz = clazzToSet; } public T findOne(long id){ return (T) getCurrentSession().get(clazz, id); } public List findAll() { return getCurrentSession().createQuery("from " + clazz.getName()).list(); } public T create(T entity) { getCurrentSession().saveOrUpdate(entity); return entity; } public T update(T entity) { return (T) getCurrentSession().merge(entity); } public void delete(T entity) { getCurrentSession().delete(entity); } public void deleteById(long entityId) { T entity = findOne(entityId); delete(entity); } protected Session getCurrentSession() { return sessionFactory.getCurrentSession(); } }

Това е абстрактен клас с няколко метода за достъп до данни, който използва SessionFactory за манипулиране на обекти.

2.2. Generic Hibernate DAO

Сега, когато имаме абстрактния клас DAO, можем да го разширим само веднъж. Общото изпълнение на DAO ще се превърне в единственото изпълнение, от което се нуждаем:

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

Първо, имайте предвид, че родовата реализация сама по себе си е параметризирана , позволявайки на клиента да избере правилния параметър за всеки отделен случай. Това ще означава, че клиентите получават всички предимства на безопасността на типа, без да е необходимо да създават множество артефакти за всеки обект.

На второ място, забележете обхвата на прототипа на тази обща реализация на DAO . Използването на този обхват означава, че контейнерът Spring ще създаде нов екземпляр на DAO всеки път, когато бъде поискан (включително при автоматично свързване). Това ще позволи на услугата да използва множество DAO с различни параметри за различни обекти, ако е необходимо.

Причината този обхват да е толкова важен се дължи на начина, по който Spring инициализира зърната в контейнера. Оставянето на общия DAO без обхват би означавало да се използва единичен обхват по подразбиране, което би довело до един екземпляр на DAO, живеещ в контейнера . Това очевидно би било основно ограничаващо за всякакъв вид по-сложен сценарий.

В IGenericDao е просто интерфейс за всички начини на DAO, така че да можем да си инжектират изпълнението имаме нужда от:

public interface IGenericDao { T findOne(final long id); List 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

В AbstractJpaDao е много подобен на AbstractHibernateDao:

public abstract class AbstractJpaDao { private Class clazz; @PersistenceContext EntityManager entityManager; public void setClazz( Class clazzToSet ) { this.clazz = clazzToSet; } public T findOne( Long id ){ return entityManager.find( clazz, id ); } public List 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

Подобно на внедряването на хибернация, JPA обектът за достъп до данни също е ясен:

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

3. Инжектиране на този DAO

Сега имаме един DAO интерфейс, който можем да инжектираме. Също така трябва да посочим Класа:

@Service class FooService implements IFooService{ IGenericDao dao; @Autowired public void setDao(IGenericDao daoToSet) { dao = daoToSet; dao.setClazz(Foo.class); } // ... }

Spring автоматично свързва новия екземпляр на DAO с помощта на инжектиране на сетер, така че изпълнението може да бъде персонализирано с обекта Class . След тази точка DAO е напълно параметризиран и готов за използване от услугата.

Има, разбира се, и други начини, по които класът може да бъде определен за DAO - чрез отражение или дори в XML. Предпочитанието ми е към това по-просто решение поради подобрената четливост и прозрачност в сравнение с използването на отражение.

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

В тази статия се обсъжда опростяването на слоя за достъп до данни чрез предоставяне на единична, многократно внедрена генерална DAO. Показахме изпълнението както в режим на хибернация, така и в среда, базирана на JPA. Резултатът е рационализиран слой на устойчивост, без излишна бъркотия.

За стъпка по стъпка за въвеждане на контекста на Spring с помощта на Java базирана конфигурация и основния Maven pom за проекта, вижте тази статия.

И накрая, кодът за тази статия може да бъде намерен в проекта GitHub.