Не повторюйте DAO!, Комерція, Різне, статті

Для більшості розробників написання одного і того ж коду для кожного DAO в системі стало звичкою. Хоча можна було б назвати повторення “забрудненням коду”, більшість з нас навчилися жити з цим. Крім того, існують обхідні шляхи. Ви можете використовувати численні ORM-засоби для видалення повторень коду. Наприклад, за допомогою Hibernate ви може просто використовувати операції сесії безпосередньо для всіх ваших персистентних доменних об’єктів. Зворотною стороною такого підходу є втрата типізації.


Навіщо вам потрібен типізований інтерфейс для вашого коду доступу до даних? Я б відповів так: він зменшує помилки програмування і збільшує продуктивність при використанні з сучасними IDE-засобами. Перш за все, типізований інтерфейс чітко вказує, які персистентних доменні об’єкти доступні. По-друге, він усуває необхідність використання схильних до помилок привидів типу (проблема більш типова для операцій запиту, ніж для CRUD). Нарешті, він підтримує функцію автозавершення, наявну в більшості сучасних IDE. Використання автозавершення є швидким способом згадати, які запити доступні для певного класу домену.


У даній статті я покажу вам, як уникнути повторення DAO-коду, одночасно користуючись перевагами типізованого інтерфейсу. Фактично, все, що вам потрібно написати для кожного нового DAO – це Hibernate-файл відображення, традиційний Java-інтерфейс і 10 рядків у вашому конфігураційному файлі Spring.


Реалізація DAO


Шаблон DAO повинен бути добре відомий кожному розробнику корпоративних Java-додатків. Реалізації шаблону значно відрізняються між собою, проте давайте прояснимо положення, що лежать в основі реалізації DAO, представленої в цій статті:




Узагальнений інтерфейс DAO


Основою узагальненого DAO є його операції CRUD. Методи для узагальненого DAO визначає наступний інтерфейс:


Лістинг 1. Узагальнений інтерфейс DAO





public interface GenericDao <T, PK extends Serializable> {
/ ** Зберегти об’єкт newInstance в базі даних * /
PK create(T newInstance);
/ ** Витягти об’єкт, попередньо збережений в базі даних, використовуючи * Зазначений id в якості первинного ключа
*/
T read(PK id);
/ ** Зберегти зміни, зроблені в об’єкті. * /
void update(T transientObject);
/ ** Видалити об’єкт з бази даних * /
void delete(T persistentObject);
}

Реалізація інтерфейсу


Реалізація наведеного в лістингу 1 інтерфейсу з Hibernate тривіальна (лістинг 2). Просто викликаються відповідні Hibernate-методи і додається приведення типів. Spring займається управлінням сесіями і транзакціями. Звичайно, я припускаю, що ці функції коректно встановлені. Ця тема добре розглянута в довідкових посібниках з систем Hibernate і Spring.


Лістинг 2. Перша реалізація узагальненого DAO





public class GenericDaoHibernateImpl <T, PK extends Serializable>
implements GenericDao<T, PK>, FinderExecutor {
private Class<T> type;
public GenericDaoHibernateImpl(Class<T> type) {
this.type = type;
}
public PK create(T o) {
return (PK) getSession().save(o);
}
public T read(PK id) {
return (T) getSession().get(type, id);
}
public void update(T o) {
getSession().update(o);
}
public void delete(T o) {
getSession().delete(o);
}
/ / Не показані реалізації getSession () і setSessionFactory ()
}

Конфігурація Spring


Нарешті, в конфігурації Spring я створюю примірник GenericDaoHibernateImpl. Конструктору GenericDaoHibernateImpl потрібно вказати, за якою доменний клас буде відповідати примірник DAO. Це необхідно для того, щоб Hibernate знав під час виконання, яким типом об’єкту управляє DAO. У лістингу 3 я передаю доменний клас Person з прикладу програми в конструктор і встановлюю попередньо налаштовану фабрику Hibernate-сесій в якості параметра для створеного екземпляра DAO:


Лістинг 3. Конфігурування DAO





<bean id=”personDao” class=”genericdao.impl.GenericDaoHibernateImpl”>
<constructor-arg>
<value>genericdaotest.domain.Person</value>
</constructor-arg>
<property name=”sessionFactory”>
<ref bean=”sessionFactory”/>
</property>
</bean>

 


Готовий узагальнений DAO


Я ще не закінчив, але те, що є, вже точно можна використовувати. У лістингу 4 ви можете побачити приклад використання узагальненого DAO в тому вигляді, який він має на даний момент часу:


Лістинг 4. Використання DAO





public void someMethodCreatingAPerson() {

GenericDao dao = (GenericDao) beanFactory.getBean (“personDao”); / / Це зазвичай потрібно впроваджувати
Person p = new Person(“Per”, 90);
dao.create(p);
}

На даний момент часу у мене є узагальнений DAO, здатний виконувати типізований CRUD-операції. Абсолютно обгрунтовано було б створити підклас GenericDaoHibernateImpl для додавання можливості виконувати запити для кожного доменного об’єкта. Однак, оскільки метою даної статті є демонстрація того, як можна це зробити без явного Java-коду для кожного запиту, я буду використовувати два додаткових інструменту для введення запитів до DAO, а саме, іменовані запити Spring AOP і Hibernate.


 


Впровадження Spring AOP


Ви можете використовувати впровадження (introductions) в Spring AOP для додавання функціональності в існуючий об’єкт, уклавши об’єкт в проксі-об’єкт, визначивши нові інтерфейси, які він повинен реалізувати, і передавши всі раніше непідтримувані методи одному обробникові. У моїй реалізації DAO я використовую впровадження для додавання кількох методів finder в існуючий узагальнений DAO-клас. Оскільки методи finder специфічні для кожного доменного об’єкта, вони застосовуються до типізований інтерфейсам узагальненого DAO.


У лістингу 5 приведена використовувана конфігурація Spring:


Лістинг 5. Конфігурація Spring для FinderIntroductionAdvisor





<bean id=”finderIntroductionAdvisor” class=”genericdao.impl.FinderIntroductionAdvisor”/>
<bean id=”abstractDaoTarget”
class=”genericdao.impl.GenericDaoHibernateImpl” abstract=”true”>
<property name=”sessionFactory”>
<ref bean=”sessionFactory”/>
</property>
</bean>
<bean id=”abstractDao”
class=”org.springframework.aop.framework.ProxyFactoryBean” abstract=”true”>
<property name=”interceptorNames”>
<list>
<value>finderIntroductionAdvisor</value>
</list>
</property>
</bean>

У конфігураційному файлі, наведеному в лістингу 5, я визначив три Spring-компонента. Перший компонент, FinderIntroductionAdvisor, обробляє всі методи, включені в DAO і недоступні в класі GenericDaoHibernateImpl. Через деякий час я розгляну компонент Advisor докладно.


Другий компонент є “абстрактним”. В Spring це означає, що компонент можна використовувати повторно у визначеннях інших компонентів, але не можна створити його екземпляр. На відміну від абстрактного властивості визначення компонента просто вказує, що я хочу отримати примірник GenericDaoHibernateImpl, І йому необхідна посилання на SessionFactory. Зверніть увагу на те, що клас GenericDaoHibernateImpl визначає тільки один конструктор, який приймає як аргумент доменний клас. Оскільки це визначення компонента є абстрактним, я можу використовувати його повторно багато разів і встановлювати аргумент конструктора в підходящий доменний клас.


Нарешті, третій і самий цікавий компонент укладає уніфікований примірник GenericDaoHibernateImpl в проксі, надаючи йому можливість виконувати методи finder. Це визначення компонента теж абстрактно і не вказує інтерфейс, який мені хотілося б ввести в мій уніфікований DAO. Інтерфейс буде різним для кожного конкретного екземпляра. Зверніть увагу на те, що повна конфігурація, показана в лістингу 5, виконується тільки один раз.


 


Розширення GenericDAO


Інтерфейс для кожного DAO, звичайно ж, заснований на інтерфейсі GenericDao. Мені просто потрібно адаптувати інтерфейс до конкретного доменному класу і розширити його, включивши мої методи finder. У лістингу 6 ви можете побачити приклад інтерфейсу GenericDao, Розширеного для конкретного завдання:


Лістинг 6. Інтерфейс PersonDao





public interface PersonDao extends GenericDao<Person, Long> {
List<Person> findByName(String name);
}

Очевидно, що призначенням методу, визначеного в лістингу 6, є пошук Person по імені. Необхідний Java-код реалізації є повністю узагальненим кодом, що не вимагає яких-небудь змін при додаванні додаткових DAO.


Конфігурування PersonDao


Оскільки конфігурація Spring заснована на визначених раніше “абстрактних” компонентах, вона є досить компактною. Я повинен зазначити, за якою доменний клас відповідає мій DAO, а також вказати Spring, який інтерфейс DAO повинен реалізувати (деякі методи безпосередньо, а деякі – за допомогою впроваджень). У лістингу 7 показаний конфігураційний файл Spring для PersonDAO:


Лістинг 7. Конфігурація Spring для PersonDao





<bean id=”personDao” parent=”abstractDao”>
<property name=”proxyInterfaces”>
<value>genericdaotest.dao.PersonDao</value>
</property>
<property name=”target”>
<bean parent=”abstractDaoTarget”>
<constructor-arg>
<value>genericdaotest.domain.Person</value>
</constructor-arg>
</bean>
</property>
</bean>

У лістингу 8 приведена оновлена ​​версія використання DAO:


Лістинг 8. Використання типізованого інтерфейсу





public void someMethodCreatingAPerson() {

PersonDao dao = (PersonDao) beanFactory.getBean (“personDao”); / / Це зазвичай потрібно впроваджувати
Person p = new Person(“Per”, 90);
dao.create(p);
List result = dao.findByName (“Per”); / / Виняткова ситуація, виконавчі
}

Хоча код, наведений у лістингу 8, є коректним способом використання типізованого інтерфейсу PersonDao, Реалізація DAO не закінчена. Виклик findByName() генерує виняткову ситуацію часу виконання. Проблема полягає в тому, що я ще не реалізував запит, необхідний для виклику findByName(). Все, що залишилося зробити, – вказати запит. Для цього я використовую іменований запит Hibernate.


 


Іменовані запити Hibernate


Використовуючи Hibernate, ви можете визначити HQL-запит у файлі відображення Hibernate (hbm.xml) і дати йому ім’я. Ви можете використовувати цей запит пізніше у вашому Java-коді, просто посилаючись на дане ім’я. Одним з переваг цього підходу є можливість редагувати запити під час розгортання без зміни початкового коду. Як ви побачите пізніше, ще однією перевагою є можливість реалізувати “Повний” DAO без запису будь-якого нового Java-коду реалізації. У лістингу 9 наведено приклад файлу відображення з іменованим запитом:


Лістинг 9. Файл відображення Hibernate з іменованим запитом





<hibernate-mapping package=”genericdaotest.domain”>
<class name=”Person”>
<id name=”id”>
<generator class=”native”/>
</id>
<property name=”name” />
<property name=”weight” />
</class>
<query name=”Person.findByName”>
<![CDATA[select p from Person p where p.name = ? ]]>
</query>
</hibernate-mapping>

Лістинг 9 визначає Hibernate-відображення доменного класу Person з двома властивостями: name і weight. Person – Це простий POJO із згаданими властивостями. Файл також містить запит, який знаходить все екземпляри Person в базі даних, чиє властивість name одно вказаному параметру. Hibernate не надає цієї функціональності простору імен для іменованих запитів. Для цілей цього обговорення я використовую префікс у всіх назвах запитів у вигляді короткого (не повного) назви доменного класу. В реальній ситуації, можливо, більш гарною ідеєю було б використання повної назви класу, включаючи назву пакета.


 


Покроковий огляд


Ви побачили всі кроки, які необхідно виконати в процесі створення і конфігурації нового DAO для будь-якого доменного об’єкта. Цими трьома простими кроками є:



  1. Визначити інтерфейс, що розширює GenericDao і містить всі необхідні вам методи finder.

  2. Додати іменований запит для кожного методу finder в файл відображення hbm.xml для кожного доменного об’єкта.

  3. Додати 10-рядковий конфігураційний файл Spring для DAO.

Я завершую обговорення розглядом коду (записуваного тільки один раз!), Який виконує мої методи finder.


 


Повторно використовуваний DAO-клас


Використовувані Spring-методи advisor і interceptor є тривіальними, а їхня робота полягає, фактично, у зверненні назад до GenericDaoHibernateImplClass. Всі дзвінки, назва методу яких починається з find, передаються в DAO і один метод executeFinder().


Лістинг 10. Реалізація FinderIntroductionAdvisor





public class FinderIntroductionAdvisor extends DefaultIntroductionAdvisor {
public FinderIntroductionAdvisor() {
super(new FinderIntroductionInterceptor());
}
}
public class FinderIntroductionInterceptor implements IntroductionInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
FinderExecutor genericDao = (FinderExecutor) methodInvocation.getThis();
String methodName = methodInvocation.getMethod().getName();
if (methodName.startsWith(“find”)) {
Object[] arguments = methodInvocation.getArguments();
return genericDao.executeFinder(methodInvocation.getMethod(), arguments);
} else {
return methodInvocation.proceed();
}
}
public boolean implementsInterface(Class intf) {
return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf);
}
}

Метод executeFinder ()


Єдиним методом, відсутнім у наведеній в лістингу 10 реалізації, є метод executeFinder(). Цей код шукає назву викликаного класу і методу і порівнює їх з ім’ям Hibernate-запиту, використовуючи угоди з конфігурації. Ви можете також використовувати FinderNamingStrategy для вирішення інших способів іменування запитів. Реалізація за замовчуванням шукає запит з ім’ям ClassName.methodName, Де ClassName – Це коротке ім’я без пакетів. У лістингу 11 приведено завершення моєї реалізації узагальненого типізованого DAO:


Лістинг 11. Реалізація executeFinder ()





public List<T> executeFinder(Method method, final Object[] queryArgs) {
final String queryName = queryNameFromMethod(method);
final Query namedQuery = getSession().getNamedQuery(queryName);
String[] namedParameters = namedQuery.getNamedParameters();
for(int i = 0; i < queryArgs.length; i++) {
Object arg = queryArgs[i];
Type argType = namedQuery.setParameter(i, arg);
}
return (List<T>) namedQuery.list();
}
public String queryNameFromMethod(Method finderMethod) {
return type.getSimpleName() + “.” + finderMethod.getName();
}

 


На закінчення


До Java 5 язик не підтримував написання коду, який був би і узагальненим і універсальна, і ви повинні були вибирати одне з двох. У даній статті ви побачили тільки один приклад використання шаблонів класів Java 5 (generics) в комбінації з такими інструментальними засобами як Spring і Hibernate (і AOP) для поліпшення продуктивності. Узагальнений типізований DAO-клас написати відносно легко. Все, що вам треба – це один інтерфейс, кілька іменованих запитів і 10-рядкове додавання в файл конфігурації Spring. В результаті ви значно зменшите ймовірність помилок, а також заощадите час.


Майже весь вихідний код, наведений у цій статті, є повторно використовуваним. Хоча ваші DAO-класи можуть містити типи запитів або операцій, не реалізовані тут (наприклад, групові операції), ви повинні зуміти реалізувати, принаймні, деякі з них за допомогою продемонстрованої мною методики.


Подяки


Концепція одного узагальненого типізованого DAO виникла після появи шаблонів класів (generics) в мові програмування Java. Я коротко обговорював можливість реалізації узагальненого DAO з Доном Смітом (Don Smith) на JavaOne 2004. Реалізація DAO-класу, яка використовується в даній статті, є прикладом; існують і інші реалізації. Наприклад, Крістіан Бауер (Christian Bauer) опублікував реалізацію з CRUD-операціями і пошуку за критеріями. Ерік Бурке (Eric Burke) теж працював у цій галузі. Я повинен висловити подяку Крістіану за перегляд моєї першої спроби написання узагальненого типізованого DAO і за запропоновані поліпшення. Нарешті, я дякую Рамніваса Ладдада (Ramnivas Laddad) за безцінну допомогу в рецензуванні цієї статті.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*