Build generic type-safe DAO |
using Hibernate and Spring AOP |
|
|
|
|
send this page as an e-mail message |
|
|
Expand Tomcat Application |
|
|
Download IBM Open source Java application Server was CE new version V1.1 |
|
|
Level: Intermediate
Per Mellqvist (per@mellqvist.name), System architect, freelance writer
June 05, 2006 because of the adoption of the Java™5 generics, the idea of implementing the generic type security Data Access Object (DAO) becomes practical. In this article, the system architect per mellqvist shows a generic DAO implementation class based on Hibernate. Then show how to use the Spring AOP introductions to add a type-safe interface to a class to facilitate query execution.
For most developers, writing almost the same code for each DAO in the system has become a habit so far. While everyone has labeled this repetition "code taste," Most of us have learned to endure it. There is actually a solution. You can use many ORM tools to avoid code duplication. For example, using Hibernate, you can simply use session operations directly for all persistent domain objects. The disadvantage of this approach is the loss of type safety.
Why do you provide a type-safe interface for data access code. I would argue that when used with modern IDE tools, it reduces programming errors and increases productivity. First, the type-safe interface clearly indicates which domain objects have the available persistent storage. Second, it eliminates the need for error-prone type casts (which is a more common problem in query operations than in CRUD). Finally, it leverages most of the automatic completion features that are available today for most Ides. Using AutoComplete is a quick way to remember what queries can be used for specific domain classes.
In this article, I'll show you how to avoid repeatedly repeating DAO code, while still preserving the benefits of a type-safe interface. In fact, all you need to write for each new DAO is just the Hibernate mapping file, the unformatted old Java interface, and the 10 lines in the Spring configuration file.
DAO implementation
DAO patterns should be familiar to any enterprise Java developer. But the implementation of the pattern is different, so let's clarify the assumption behind the DAO implementation provided in this article: all database accesses in the system are made through DAO to implement encapsulation. Each DAO instance is responsible for one primary domain object or entity. If a domain object has an independent life cycle, it should have its own DAO. DAO is responsible for creating, reading (by primary key), updating, and deleting domain objects (creations, reads, updates, and Deletions,crud). DAO allows queries to be based on criteria other than primary key. I call this the Finder method or the finder. The return value of the Finder is typically a collection of domain objects that DAO is responsible for. DAO is not responsible for handling transactions, sessions, or connections. These are not handled by DAO to achieve flexibility.
|
Back to the top of the page |
|
Generic DAO interface
The generic DAO is based on its CRUD operations. The following interface defines the method for the generic DAO:
Listing 1. Generic DAO interface
Public interface Genericdao <t, PK extends serializable> {/** Persist the Newinstance
object into Database */
PK Create (T newinstance);
/** Retrieve An object this is previously persisted to the database using
* The indicated ID as primary key
* /
T read (PK ID);
/** Save changes made to a persistent object. *
/void update (T transientobject);
/** Remove An object from persistent storage in the database *
/void Delete (T persistentobject);
}
|
implementing Interfaces
Using Hibernate to implement the interface in Listing 1 is simple, as shown in Listing 2. It simply calls the underlying Hibernate method and adds coercion type conversions. Spring is responsible for session and transaction management. (I assume, of course, that these functions have been set appropriately, but the topic is described in detail in the Hibernate and Springt manuals.) )
Listing 2. The first generic DAO implementation
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);
Not showing implementations of getsession () and Setsessionfactory ()
}
|
Spring Configuration
Finally, in the Spring configuration, I created an instance of Genericdaohibernateimpl. You must tell Genericdaohibernateimpl which domain class The constructor DAO instance will be responsible for. Only in this way can Hibernate know the type of object managed by DAO at run time. In Listing 3, I passed the domain class person from the sample application to the constructor and set the previously configured Hibernate session factory to the parameters of the instantiated DAO:
Listing 3. Configuring 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>
|
|
Back to the top of the page |
|
Available generic DAO
I haven't finished yet, but what I have done is really ready for use. In Listing 4, you can see an example of using the generic DAO intact:
Listing 4. Using DAO
public void Somemethodcreatingaperson () {
...
Genericdao DAO = (Genericdao)
Beanfactory.getbean ("Persondao");//This should normally is injected person
p = new Person ("per", "n");
Dao.create (P);
}
|
Now, I have a generic DAO that can perform type-safe CRUD operations. It would be reasonable to have subclasses genericdaohibernateimpl the ability to add queries to each domain object. Because the purpose of this article is to show how to implement a query without writing explicit Java code for each query, I will use the other two tools to introduce the query into DAO, the Spring AOP and Hibernate named queries.
|
Back to the top of the page |
|
Spring AOP Introductions
You can use the introductions in Spring AOP to add functionality to existing objects by wrapping them in the broker, defining the interfaces that should be implemented, and assigning all previously unsupported methods to a single handler. In my DAO implementation, I use introductions to add many finder methods to an existing generic DAO class. Because the Finder method is specific to each domain object, it is applied to the typed interface of the generic DAO.
The Spring configuration is shown in Listing 5:
Listing 5. Spring Configuration for 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>
|
In the configuration file in Listing 5, I have defined three Spring beans. The first bean is finderintroductionadvisor, which handles all the methods that are introduced to DAO, which are not available in the Genericdaohibernateimpl class. I'll introduce the Advisor bean in more detail later.
The second bean is "abstract." In Spring, this means that the bean can be reused in other bean definitions, but not instantiated. In addition to the abstract attributes, the bean definition indicates only the instance I want to genericdaohibernateimpl and the reference to sessionfactory that the instance requires. Note that the Genericdaohibernateimpl class defines only one constructor that takes the domain class as its parameters. Because the bean definition is abstract, I can reuse the definition countless times in the future and set the constructor parameters to the appropriate domain class.
Finally, the third and most interesting bean wraps the Genericdaohibernateimpl vanilla instance in the broker, giving it the ability to execute the Finder method. The bean definition is also abstract and does not specify an interface that you want to introduce to the vanilla DAO. The interface is different for each specific instance. Note that the entire configuration shown in Listing 5 is defined only once.
|
Back to the top of the page |
|
Extended Genericdao
Of course, each DAO's interface is based on the Genericdao interface. I just need to adapt the interface to a particular domain class and extend the interface to include the Finder method. In Listing 6, you can see examples of GENERICDAO interfaces that are extended for specific purposes:
Listing 6. Persondao Interface
Public interface Persondao extends Genericdao<person, long> {list<person>
findbyname (String name);
|
Obviously, the method defined in Listing 6 is designed to find person by name. The required Java implementation code is all generic code and does not require any updates when adding more DAO.
Configure Persondao
Because the Spring configuration relies on a previously defined "abstract" bean, it becomes fairly concise. I need to indicate which domain class The DAO is responsible for and tell Springs which interface the DAO should implement (some methods are used directly, and some methods are used by using introductions). Listing 7 shows the Spring configuration file for Persondao:
Listing 7. Spring Configuration for 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>
|
In Listing 8, you can see this updated DAO version is used:
listing 8. Using type-safe interfaces
public void Somemethodcreatingaperson () {
...
Persondao DAO = (Persondao)
Beanfactory.getbean ("Persondao");//This should normally is injected person
p = new P Erson ("per",);
Dao.create (p);
list<person> result = Dao.findbyname ("per"); Runtime Exception
}
|
Although the code in Listing 8 is the correct way to use type-safe Persondao interfaces, the DAO implementation is not complete. Calling Findbyname () can cause a Run-time exception. The problem is that I haven't implemented the query necessary to invoke Findbyname (). All that remains to be done is to specify the query. To correct the problem, I used the Hibernate named query.
|
Back to the top of the page |
|
Hibernate named query
With Hibernate, you can define HQL queries and name them in the Hibernate mapping file (hbm.xml). You can use the query in Java code later by simply referencing the given name. One of the advantages of this approach is the ability to optimize queries at deployment time without having to change the code. As you'll see, another advantage is that you can implement the "complete" DAO without writing any new Java implementation code. Listing 9 is an example of a mapping file with a named query:
Listing 9. mapping file with named query
Listing 9 defines the Hibernate mapping for the domain class person, which has two properties: Name and weight. A person is a simple POJO with the above attributes. The file also contains a query that finds all instances of person in the database, where "name" equals the supplied parameter. Hibernate does not provide any real namespace functionality for named queries. For discussion purposes, I prefix all query names with the short (unqualified) name of the domain class. In the real world, it might be a better idea to use a full class name that includes a package name.
|
Back to the top of the page |
|
Step-by-Step Overview
You have seen all the steps necessary to create and configure a new DAO for any domain object. The three simple steps are to define an interface that extends Genericdao and contains any finder methods that are required. Adds a named query for each finder to the Hbm.xml mapping file for the domain object. Adds a 10-line Spring configuration file for DAO.
View the code that executes the Finder method, which was written only once. ) to end my discussion.
|
Back to the top of the page |
|
Reusable DAO Classes
The Spring advisor and interceptor used are simple, and in fact their job is to refer back to Genericdaohibernateimplclass. All calls with the method name beginning with "find" are passed to DAO and a single method Executefinder ().
listing 10. The realization of Finderintroductionadvisor
public class Finderintroductionadvisor extends {
Public Finderintroductionadvisor () {Super (New Finderintroductioninterceptor ()); } public class Finderintroductioninterceptor implements Introductioninterceptor {public Object invoke (METHODINVOC ation 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 () method
The only thing missing from the implementation of listing 10 is the Executefinder () implementation. The code looks at the names of the classes and methods that are invoked, and uses the conventions on the configuration to match them with the names of the Hibernate queries. You can also use Findernamingstrategy to support methods for other named queries. The default implementation looks for a query called "Classname.methodname," where ClassName is a short name with no package. Listing 11 completes the generic type-safe DAO implementation:
implementation of the list of Executefinder ()
Public list<t> Executefinder (method, final object[] Queryargs) {
final String queryname = Querynamefromm Ethod (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 ( );
}
|