Wednesday, January 19, 2011

Simplifying JPA Code with Hades

JPA is not that much fun anymore. It was a great improvement compared to plain JDBC and XML mapping of many persistence frameworks, but after a while you come to realize that in many cases you need to write quite a lot of code to do very little work (like many other things in Java).

Enters Hades. In one sentence, it reduces the amount of JPA code by using convention over configuration. The Hades documentation is clear and comprehensive, so I won't explain it in detail here. I will demonstrate the process of migrating a simple JPA project to use Hades, something you will probably want to do once you get to know Hades.

Setting Up a JPA Project

In this example we use Hibernate as the persistence provider and JUnit for the unit tests. If you want to use Maven, you just need to add the following dependencies to your POM:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-entitymanager</artifactId>
   <version>3.6.0.Final</version>
</dependency> 
<dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.2</version>
   <scope>test</scope>
</dependency> 

Let's create a very simple persistent model class named User:

@Entity
public class User {
    
    @Id
    @GeneratedValue(strategy=IDENTITY)
    private Long id;
  
    private String username;
    
    private String fullName;
        
    private User() {}
    
    public User(String username, String fullName) {
        this.username = username;
        this.fullName = fullName;
    }
    
    public Long getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public String getFullName() {
        return fullName;
    }
    
}  

A simple DAO interface:

public interface UserDao {

    void saveUser(User user);
    User findById(Long id);
    List<User> findByFullName(String fullName);
    void deleteUser(User user);
}

And of course there is a DAO implementation class, but we will not show it here, soon you will see why.

No project is complete without unit tests, so here is a very simple one:

public class UserDaoTest {

    @Test
    public void generalTest() {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu");
        EntityManager em = emf.createEntityManager();
        UserDao userDao = new UserDaoImpl(em);
        
        final String username = "jsmith";
        final String fullName = "John Smith";
        final Long id;
        
        // Save user
        User user = new User(username, fullName);
        userDao.saveUser(user);
        id = user.getId();
        
        // Find by ID
        user = userDao.findById(id);
        assertEquals(username, user.getUsername());
        
        // Find by Full Name
        List<User> results = userDao.findByFullName("John Smith");
        assertEquals(1, results.size());
        assertEquals(username, results.get(0).getUsername());
        
        // Delete
        userDao.deleteUser(user);
        user = userDao.findById(id);
        assertNull(user);
    }
}

Regarding implementation details: the findByFullName() method can be implemented either with a named query or with a criteria, it doesn't matter for our example.

Adding Hades

Add Hades to your project. With Maven we add this dependency:

<dependency>
   <groupId>org.synyx.hades</groupId>
   <artifactId>org.synyx.hades</artifactId>
   <version>2.0.1.RELEASE</version>
</dependency> 

To make our DAO work with Hades, we make UserDao inherit from GenericDao<User, Long>. This is a Hades interface where the first type is the persistent class it will work with and the second type is the type of the persistent class identifier.
Now we simply delete stuff. That's right - we delete the DAO implementation class, the named query (if you have one) and some of the DAO interface methods.

The UserDao interface should now look like this:

public interface UserDao extends GenericDao<User, Long> {

    List<User> findByFullName(String fullName);
}

But what about all the methods that we removed? They are no longer needed because GenericDao has equivalent methods, and Hades provides the implementation. Take a look at the JavaDoc of GenericDao to see what you get "for free".

That's great, but doesn't findByFullName() need an implementation? That depends. For simple finder methods you can use method names that matches Hades conventions. In this case, the prefix "findBy" that precedes "FullName" means that this is a search query that uses the "fullName" property in the "where" clause, meaning Hades generates a this query from the method name: "from User where fullName = ?" .

If we do want to write more complex queries, we can do it by annotating the method in the DAO interface. Let's change findByFullName() to use the "like" operator instead of "=" :

public interface UserDao extends GenericDao<User, Long> {

    @Query("from User where fullName like ?1")
    List<User> findByFullName(String fullName); 

}
This makes much more sense (at least to me) than writing the query as part of the model code.

So, if there is no implementation class (more correctly - Hades generates the implementation class at runtime), how do we instantiate the DAO ? We use Hades GenericDaoFactory. Let's change the unit test code accordingly:
public class UserDaoTest {

    @Test
    public void generalTest() {
        
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu");
        EntityManager em = emf.createEntityManager();
        
        GenericDaoFactory gdf = GenericDaoFactory.create(em);
        UserDao userDao = gdf.getDao(UserDao.class);
        
        // rest of the code as before
    }
}
That's it. By using Hades we made our code much more clean because:
  • We get the basic CRUD operations in every DAO out of the box
  • We don't need to write DAO implementation class in many cases
  • We write JPQL queries in the DAO interface and not in the model
  • We don't even need to write JPQL queries for simple cases where we can use conventional method names
Although Hades removes the need to write DAO implementation code, sometimes we have no choice. Maybe we want to build a dynamic query or we simple want to migrate gradually to Hades while keeping existing JPA code. For this case we can write an additional custom DAO interface with an implementation, and make the Hades DAO inherit the custom DAO:

public interface UserDaoCustom {

    List<User> searchUsers(String username, String fullName);
}

public class UserDaoImpl implements UserDaoCustom {

    List<User> searchUsers(String username, String fullName) {
        // implementation code...
    }
}

public interface UserDao extends GenericDao<User, Long>, UserDaoCustom {

    @Query("from User where fullName like ?1")
    List<User> findByFullName(String fullName); 
} 

When we use a custom DAO with implementation, we need to tell the Hades factory to use this implementation, so we modify one line the unit test code:

UserDao userDao = gdf.getDao(UserDao.class, UserDaoImpl.class);

So there - you don't have to make any drastic changes to your codebase at once in order to benefit from Hades.

There are plenty of nice features in Hades we didn't cover such as pagination, sorting, named parameters and Spring integration. I encourage you to read the Hades Documentation to learn about its features. We will cover some of them in future posts.

No comments:

Post a Comment