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
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