Reduce Boilerplate Code for DAO's -- Hades Introduction
Join the DZone community and get the full member experience.
Join For FreeMost web applications will have DAO's for accessing the database layer. A DAO provides an interface for some type of database or persistence mechanism, providing CRUD and finders operations without exposing any database details. So, in your application you will have different DAO's for different entities. Most of the time, code that you have written in one DAO will get duplicated in other DAO's because much of the functionality in DAO's is same (like CRUD and finder methods).
One of way of avoiding this problem is to have generic DAO and have your domain classes inherit this generic DAO implementation. You can also add finders using Spring AOP; this approach is explained Per Mellqvist in this article. There is a problem with the approach: this boiler plate code becomes part of your application source code and you will have to maintain it. The more code you write, there are more chances of new bugs getting introduced in your application. So, to avoid writing this code in an application, we can use an open source framework called Hades.
Hades is a utility library to work with Data Access Objects implemented with Spring and JPA. The main goal is to ease the development and operation of a data access layer in applications. In this article, I will show you how easy it is write DAO's using Hades without writing any boiler plate code.
In order to introduce you to Hades, I will show you how we can manage an entity like Book. Before we write any code we need to add the following dependencies to 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>
So, lets start by creating a Book Entity
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
}
This is a very simple JPA entity without any relationships. Now that we have modeled our entity we need to add a DAO interface for handling persistence operations. You need to create a BookDao interface which will extend GenericDao interface provided by Hades. GenericDao is an interface for generic CRUD operations on a DAO for a specific type. So, we passed the type parameters Book for entity and Long for id.
import org.synyx.hades.dao.GenericDao;
public interface BookDao extends GenericDao<Book, Long> {
}
GenericDao has an default implementation called GenericJpaDao which provides implementation of all its operations. Now that we have created a BookDao interface, we will configure it in the Spring application context xml. Hades provides a factory bean which will provide the DAO instance for the given interface (in our case 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>
In the xml shown above I have used a new feature introduced in Spring 3 Embedded Databases to give me the instance of HSQL database datasource. You can refer to my earlier post on Embedded databases in case you are not aware of it. I have used Hibernate as my JPA provider so you need to configure it in persistence.xml as shown below
<?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>
Next we will write a JUnit test for testing this code.
@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()));
}
}
Auto Configuration Using Spring Namespaces
The way that we have configured the DAO can become quite cumbersome if the number of DAO's increases. To overcome this we can make use of namespaces to configure 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>
This configuration will trigger the auto detection mechanism of DAOs that extend GenericDAO or Extended-GenericDAO. It will create DAO instances for all the DAO interfaces found in this package. You can use <include-filter> or <exclude-filter> for including or excluding interfaces from getting their beans created. Adding Finders and Query Methods So far we have used the inbuilt operations provided by GenericDao but most of the time we need to add our own finders methods like findByAuthorAndTitle, findWithPriceLessThan . Hades makes it very easy for you to add such methods in your domain dao interface like BookDao. Hades provides 3 strategies for creating JPA query at runtime. These are :-
CREATE : This will create a JPA query from method name. This strategy ties you with the method name so you have to think twice before changing the method name.
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);
}USE DECLARED QUERY : This lets you define query using JPA @NamedQuery or Hades @Query annotation. If no query is found exception will be thrown.
@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);
}CREATE IF NOT FOUND : It is the combination of both the strategies mentioned above. It will first lookup for the declared query and if it is not found will lookup for method name. This is by default option and you can change it by changing query-lookup-strategy attribute in hades:dao-config element.
Hades queries also has the support for pagination and sorting. You can pass the instance of Pageable and Sort to the finder methods create above.
public Page<Book> findByAuthor(String author, Pageable pageable);
public List<Book> findByAuthor(String author, Sort sort);
Hades is not limited to just limited to CRUD and adding custom finder methods. There are some other features like auditing ,Specifications,etc that I will discuss in second part of this article.
Opinions expressed by DZone contributors are their own.
Comments