Статьи

Сократить Boilerplate код для DAO — Hades Введение

Большинство веб-приложений имеют DAO для доступа к уровню базы данных. DAO предоставляет интерфейс для некоторого типа базы данных или механизма персистентности, обеспечивая CRUD и операции поиска, не раскрывая никаких деталей базы данных. Таким образом, в вашем приложении вы будете иметь разные DAO для разных организаций. В большинстве случаев код, написанный вами в одном DAO, будет дублироваться в других DAO, потому что большая часть функций в DAO одинакова (например, CRUD и методы поиска).

Одним из способов избежать этой проблемы является использование универсального DAO, и ваши доменные классы наследуют эту универсальную реализацию DAO. Вы также можете добавить искатели, используя Spring AOP; этот подход объясняется Per Mellqvist в этой статье . Есть проблема с подходом: этот код платформы становится частью исходного кода вашего приложения, и вам придется его поддерживать. Чем больше кода вы пишете, тем больше вероятность появления новых ошибок в вашем приложении. Поэтому, чтобы избежать написания этого кода в приложении, мы можем использовать инфраструктуру с открытым исходным кодом под названием Hades .  

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

Чтобы познакомить вас с Аидом, я покажу вам, как мы можем управлять такими объектами, как Бук. Прежде чем писать какой-либо код, нам нужно добавить следующие зависимости в pom.xml.

<dependency>
<groupId>org.synyx.hades</groupId>
<artifactId>org.synyx.hades</artifactId>
<version>2.0.0.RC3</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.5.5-Final</version>
</dependency>

 Итак, давайте начнем с создания сущности книги

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Book {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Column(unique = true)
private String title;

private String author;
private String isbn;
private double price;

// setters and getters

}

Это очень простая сущность JPA без каких-либо отношений. Теперь, когда мы смоделировали нашу сущность, нам нужно добавить интерфейс DAO для обработки персистентных операций. Вам необходимо создать интерфейс BookDao, который расширит интерфейс GenericDao, предоставляемый Hades. GenericDao — это интерфейс для общих операций CRUD в DAO для определенного типа. Итак, мы передали параметры типа Book для сущности и Long для id.

import org.synyx.hades.dao.GenericDao;

public interface BookDao extends GenericDao<Book, Long> {

}

GenericDao имеет реализацию по умолчанию GenericJpaDao, которая обеспечивает реализацию всех его операций. Теперь, когда мы создали интерфейс BookDao, мы настроим его в контексте приложения Spring xml. Hades предоставляет фабричный компонент, который предоставит экземпляр DAO для данного интерфейса (в нашем случае BookDao).

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">

<bean id="bookDao" class="org.synyx.hades.dao.orm.GenericDaoFactoryBean">
<property name="daoInterface" value="com.shekhar.hades.BookDao"></property>
</bean>

<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<jdbc:embedded-database id="dataSource" type="HSQL" />
</beans>

В приведенном выше xml-файле я использовал новую функцию, представленную во встроенных базах данных Spring 3, чтобы дать мне экземпляр источника данных базы данных HSQL. Вы можете сослаться на мой предыдущий пост о встроенных базах данных, если вы не знаете об этом. Я использовал Hibernate в качестве своего JPA-провайдера, поэтому вам нужно настроить его в persistence.xml, как показано ниже  

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
<!--value='create' to build a new database on each run; value='update' to modify an existing database; value='create-drop' means the same as 'create' but also drops tables when Hibernate closes; value='validate' makes no changes to the database-->
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/>
</properties>
</persistence-unit>

</persistence>

 Далее мы напишем тест JUnit для тестирования этого кода.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional
public class BookDaoTest {

@Autowired
private BookDao bookDao;
private Book book;

@Before
public void setUp() throws Exception {
book = new Book();
book.setAuthor("shekhar");
book.setTitle("Effective Java");
book.setPrice(500);
book.setIsbn("1234567890123");
}

@Test
public void shouldCreateABook() throws Exception {
Book persistedBook = bookDao.save(book);
assertBook(persistedBook);
}

@Test
public void shouldReadAPersistedBook() throws Exception {
Book persistedBook = bookDao.save(book);
Book bookReadByPrimaryKey = bookDao.readByPrimaryKey(persistedBook.getId());
assertBook(bookReadByPrimaryKey);
}

@Test
public void shouldDeleteBook() throws Exception {
Book persistedBook = bookDao.save(book);
bookDao.delete(persistedBook);
Book bookReadByPrimaryKey = bookDao.readByPrimaryKey(persistedBook.getId());
assertNull(bookReadByPrimaryKey);
}

private void assertBook(Book persistedBook) {
assertThat(persistedBook, is(notNullValue()));
assertThat(persistedBook.getId(), is(not(equalTo(null))));
assertThat(persistedBook.getAuthor(), equalTo(book.getAuthor()));
}
}

Автоматическая настройка с использованием пространств имен Spring

Способ, которым мы настроили DAO, может стать довольно громоздким, если количество DAO увеличивается. Чтобы преодолеть это, мы можем использовать пространства имен для настройки daos.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:hades="http://schemas.synyx.org/hades"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://schemas.synyx.org/hades http://schemas.synyx.org/hades/hades.xsd">

<hades:dao-config base-package="com.shekhar.hades"
entity-manager-factory-ref="entityManagerFactory"></hades:dao-config>

<!-- All other things remain same -->
</beans>

Эта конфигурация вызовет механизм автоматического обнаружения DAO, которые расширяют GenericDAO или Extended-GenericDAO. Это создаст экземпляры DAO для всех интерфейсов DAO, найденных в этом пакете. Вы можете использовать <include-filter> или <exclude-filter> для включения или исключения интерфейсов при создании их bean-компонентов.

Добавление Finders и методов запросов

До сих пор мы использовали встроенные операции, предоставляемые GenericDao, но большую часть времени нам нужно добавить наши собственные методы поиска, такие как findByAuthorAndTitle, findWithPriceLessThan. Hades упрощает добавление таких методов в интерфейс вашего домена, как BookDao. Аид предоставляет 3 стратегии для создания запроса JPA во время выполнения. Эти :-

  1. CREATE: это создаст запрос JPA из имени метода. Эта стратегия связывает вас с именем метода, поэтому вы должны дважды подумать, прежде чем менять имя метода.

    public interface BookDao extends GenericDao<Book, Long> {

    public Book findByAuthorAndTitle(String author, String title);
    }

    // test code
    @Test
    public void shouldFindByAuthorAndTitle() throws Exception {
    Book persistedBook = bookDao.save(book);
    Book bookByAuthorAndTitle = bookDao.findByAuthorAndTitle("shekhar", "Effective Java");
    assertBook(bookByAuthorAndTitle);
    }
  2. ИСПОЛЬЗОВАТЬ ЗАЯВЛЕННЫЙ ЗАПРОС: Это позволяет определять запрос с помощью аннотации JPA @NamedQuery или Hades @Query. Если запрос не найден, будет сгенерировано исключение.

     @Query("FROM Book b WHERE b.author = ?1")
    public Book findBookByAuthorName(String author);

    // test code
    public void shouldFindBookByAuthorName() {
    Book persistedBook = bookDao.save(book);
    Book bookByAuthor = bookDao.findBookByAuthorName("shekhar");
    assertBook(bookByAuthor);
    }
  3. СОЗДАЙТЕ, ЕСЛИ НЕ НАЙДЕНО: Это комбинация обеих стратегий, упомянутых выше. Сначала он будет искать объявленный запрос, а если он не найден, будет искать имя метода. Это опция по умолчанию, и вы можете изменить ее, изменив атрибут query-lookup-стратегии в элементе hades: dao-config. 

У запросов Hades также есть поддержка разбивки на страницы и сортировки. Вы можете передать экземпляр Pageable и Sort в методы поиска, созданные выше.

public Page<Book> findByAuthor(String author, Pageable pageable);
public List<Book> findByAuthor(String author, Sort sort);

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