Don't repeat that DAO Guice style - for hibernate

613 views
Skip to first unread message

Meidos

unread,
Mar 29, 2007, 3:47:34 PM3/29/07
to google-guice
Anyone that has used Spring and Hibernate together, and with DAO
objects, might have come across this tidbit:
http://www-128.ibm.com/developerworks/java/library/j-genericdao.html

I liked this, and decided I would like to implement the same thing for
Guice.

Please not that I just hacked this together just now, and it could be
cleaned up a bit. It does also rely on SessionFactory beeing injected,
so that must be made available through Guice configuration in some
way.

GuiceDAO.java:

public interface GuiceDAO<I extends Serializable, E> {

E getById(I id);

List<E> getAll();

void delete(E entity);

void saveOrUpdate(E entity);

}

I is the Id type for the DAO, while E is the actual entity type. Here
we have the common CRUD methods we all know and love. The interface
cannot be used directly, instead you simply extend it to specify Id
and Entity class:

public interface CustomerDAO extends GuiceDAO<Integer, Customer> { }

Final use is like this:
bind(CustomerDAO.class).toProvider(GuiceDAOProviderFactory.createProvider(CustomerDAO.class));

And after this, you can depend on CustomerDAO where ever you need, and
typesafely get data.

Of course, the last piece of magic here is missing, which is the
GuiceDAOProviderFactory:
public class GuiceDAOProviderFactory {


public static <T extends GuiceDAO> Provider<T> createProvider(final
Class<T> daoInterface) {
ParameterizedType guiceDaoType = null;
for (Type interfaceType : daoInterface.getGenericInterfaces()) {
if (interfaceType instanceof ParameterizedType &&
((ParameterizedType) interfaceType).getRawType() == GuiceDAO.class) {
guiceDaoType = (ParameterizedType) interfaceType;
break;
}
}
if (guiceDaoType == null) {
throw new IllegalArgumentException("Class ["+daoInterface.getName()
+"] does not properly extend/implement ["+GuiceDAO.class+"] with
generic paramters");
}

final Class<? extends Serializable> idClass = (Class<? extends
Serializable>) guiceDaoType.getActualTypeArguments()[0];
final Class<?> entityClass = (Class)
guiceDaoType.getActualTypeArguments()[1];

return new Provider<T>() {
@Inject
private SessionFactory sessionFactory;

public T get() {
return createDAOProxy(daoInterface, newGuiceDAOImpl(idClass,
entityClass));
}

private <I extends Serializable, E> GuiceDAO<I, E> newGuiceDAOImpl(
Class<I> idClass, Class<E> entityClass) {
return new GuiceDAOImpl<I, E>(entityClass, sessionFactory);
}

};
}

private static <T> T createDAOProxy(final Class<T> daoInterface,
final GuiceDAO<?, ?> defaultDao) {
InvocationHandler daoInvocationHandler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getDeclaringClass() == GuiceDAO.class) {
return method.invoke(defaultDao, args);
}
throw new UnsupportedOperationException("No support for method
["+method+"]");
}
};
return
daoInterface.cast(Proxy.newProxyInstance(daoInterface.getClassLoader(),
new Class[] { daoInterface },
daoInvocationHandler));
}


private static class GuiceDAOImpl<I extends Serializable, E>
implements GuiceDAO<I, E> {

private final Class<E> entityClass;
private final SessionFactory sessionFactory;

public GuiceDAOImpl(final Class<E> entityClass, final SessionFactory
sessionFactory) {
super();
this.entityClass = entityClass;
this.sessionFactory = sessionFactory;
}

public void delete(E entity) {
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
session.delete(entity);
transaction.commit();
} catch (HibernateException e) {
if (transaction != null) {
transaction.rollback();
}
}
finally {
session.close();
}
}

@SuppressWarnings("unchecked")
public List<E> getAll() {
Session session = sessionFactory.openSession();
try {
return (List<E>) session.createQuery("from
"+entityClass.getName()).list();
}
finally {
session.close();
}
}

public E getById(I id) {
Session session = sessionFactory.openSession();
try {
return entityClass.cast(session.get(entityClass, id));
}
finally {
session.close();
}
}

public void saveOrUpdate(E entity) {
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
session.saveOrUpdate(entity);
transaction.commit();
} catch (HibernateException e) {
if (transaction != null) {
transaction.rollback();
}
}
finally {
session.close();
}
}

}

}

This might come of as a bit messy, so I will go through the different
parts.

1. createProvider
This method needs to look up the actual Id and Entity type. It does
this by going through the genericInterfaces from the interface
argument (which should extend GuiceDAO). We verify at construction
that things are correctly specified. If they are, we can create a
Provider. The Provider will in turn need a SessionFactory injected.

2. The Provider
Fairly simple. The createDAOProxy does the magic of implementing the
interface argument and I will explain that next. We need an Injected
SessionFactory, and we also have the method newGuiceDAOImpl. This
method is responsible for creating a backing DAO implementation that
we can use. you see the class in full as GuiceDAOImpl. The code in
that should be familiar to anyone that has worked with Hibernate. I
could have made Guice version of the SpringTemplate to clean this up
further, but I left it as is.

3. createDAOProxy
This one does the actual magic of implementing the GuiceDAO. It
creates a dynamic proxy that will handle all method calls to the
interface we are implementing. The implementation of the
InvocationHandler is fairly simple. If the method thats being called
was declared on GuiceDAO, we simple invoke it on our GuiceDAOImpl
backing object. Otherwise we throw an exception.

Thats it for a fairly simple generic DAO.

I do have intentions of extending this further though. Imagine that
you could just decleare methods like this on your own interface:

public interface CustomerDAO extends GuiceDAO<Integer, Customer> {
List<Customer> queryByName(@Field("name") String name);
}

and then have the invocation handler automatically implement it. My
idea here is that you annotate each argument with the propertyname its
targeted at. Then, since I know the result class, the argument types,
names and values, I can simply generate the query.

Another option would be to have a query annotation:
@QueryCondition(objName="obj", value=":date > obj.startDate and :date
< obj.endDate)
List<Event> queryByDate(@ParamName("date") Date eventDate);

Here I would just create a query based on the value, prefix it with
"from <classname> "+objName+"where ". objName would refer to what we
expect to prefix the obj with in the query, and would default to
"obj". The parameters needs to be annotated with ParamName so we know
which name to assign them to in the query.

Hope this is usefull for someone, and that I can finish this up later.

rakstar

unread,
Mar 29, 2007, 5:13:51 PM3/29/07
to google-guice
I actually have a similar approach, except you don't have to create
any interfaces (of course you can if you want/need to).

Final usage is like this:

GuiceHibernateModule guiceHibernateModule =
new GuiceHibernateModule( new Class[] { User.class,
Company.class } );

Injector injector = Guice.createInjector(new Module[]
{ guiceHibernateModule });

HibernateDao<User, Long> userDao =
injector.getInstance(GuiceHibernateModule.getKey(User.class,
Long.class));

User user = new User();
user.setUsername("test");

Long id = userDao.save();


Looks like we're both working towards the same end. Here's a link to
one of my earlier posts:
http://groups.google.com/group/google-guice/browse_thread/thread/88a7c8005068f2c7

If you're interested, I could share my code, it would be nice to get
someone else's input. I think this would definitely be useful to a
lot of other developers especially because hibernate/DI (mostly with
Spring) has more or less become the de facto ORM setup.

Meidos

unread,
Mar 29, 2007, 5:48:39 PM3/29/07
to google-guice
> HibernateDao<User, Long> userDao =
> injector.getInstance(GuiceHibernateModule.getKey(User.class,
> Long.class));
>

It does seem like we are trying to do the similar things, however the
above part is what I don't like about your solution. The reason is
that you will normally inject your DAO into other objects, rather than
get it directly from the injector. For example like this:

public class BusinessBean implements BusinessInterface {

@Inject HibernateDao<User, Long> userDao;

}

afaik. this is not possible atm. with Guice, though there is enough
runtime information that such a thing could be supported. What is
needed for the above statement to work is a variant on the Provider
interface:

interface GenericProvider<T> {

T get(Type[] genericTypeArguments);

}

Guice could then determine the runtime generic arguments through
introspection. The implementation internally in guice would be along
the lines of:
Field field; //field that we are going to inject into
if (field.getGenericType() instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) field.getGenericType();
Object value =
getGenericProvider(pt.getRawType()).get(pt.getActualTypeArguments());
}

There is no such feature currently though. By extending HibernateDAO
though you can get the type arguments runtime. You also get an
explicit binding that can be injected into your classes. Another goal
I have is to have custom query methods that you can just define on
your DAO interface and have the framework implement for you.


On Mar 29, 11:13 pm, "rakstar" <raks...@gmail.com> wrote:
> I actually have a similar approach, except you don't have to create
> any interfaces (of course you can if you want/need to).
>
> Final usage is like this:
>
> GuiceHibernateModule guiceHibernateModule =
> new GuiceHibernateModule( new Class[] { User.class,
> Company.class } );
>
> Injector injector = Guice.createInjector(new Module[]
> { guiceHibernateModule });
>

> User user = new User();
> user.setUsername("test");
>
> Long id = userDao.save();
>
> Looks like we're both working towards the same end. Here's a link to

> one of my earlier posts:http://groups.google.com/group/google-guice/browse_thread/thread/88a7...

> ...
>
> read more »

Meidos

unread,
Apr 8, 2007, 11:10:30 AM4/8/07
to google-guice
Ok, I have refined my stuff some more, and it's now pretty easy to
make custom queries for the DAOs.

My GuiceDAO looks like this:


public interface GuiceDAO<I extends Serializable, E> {

E getById(I id);

List<E> getAll();

void delete(E ... entity);

void delete(Iterable<E> entities);

void saveOrUpdate(E ... entity);

void saveOrUpdate(Iterable<E> entities);

}

You still need to extend this to make an actually usefull interface,
like such:


public interface CustomerDAO extends GuiceDAO<Integer, Customer> {

@QueryAll({"firstname", "lastname"})
Customer getByName(String firstname, String lastname);

@QueryAll({"firstname"})
List<Customer> getByFirstnames(String ... firstnames);

@QueryCustom("obj.firstname >= :arg0 and obj.firstname <= :arg1")
Customer[] getByFirstnameRange(String from, String to);
}

The first one will do a query where firstname = arg0, and lastname =
arg1, and return the first match.
The second one will do a firstname in (list) query
The last will do a custom query. Default table alias is "obj" (can be
overriden in the annotation), and default name for each field is arg0,
arg1 and so on, which can also be overriden in the annotation.

The bindings are still pretty simple. In my module:
bind(CustomerDAO.class).toProvider(GuiceDAOProviderFactory.createProvider(CustomerDAO.class));

And then just depend on CustomerDAO where needed.

This is just a small pet project for me, but if others have need for
it, I could always create a project on code.google.com and upload.

Hanson Char

unread,
Apr 8, 2007, 11:22:08 AM4/8/07
to google...@googlegroups.com
A trivial comment:

Wouldn't it be nicer if those getAll, getByFirstNames methods be renamed to findAll, findByFirstNames, so a getXXX always return a single instance, whereas a findXXX always return a collection ? 

My 2 cents.

Hanson

Dhanji R. Prasanna

unread,
Apr 8, 2007, 11:00:33 PM4/8/07
to google...@googlegroups.com
Also curious what is the point of the @QueryAll annotation? Can you not use named queries (@NamedQuery) instead?

Brian Pontarelli

unread,
Apr 9, 2007, 9:21:39 AM4/9/07
to google...@googlegroups.com
We went down this same route and came to the conclusion that it was too
much overhead so we dropped all of the implementing interfaces to
something like this:

@ImplementedBy(EntityBeanServiceImpl.class)
public interface EntityBeanService
List<? extends EntityBean> findAll(Class<? extends EntityBean>);

List<? extends EntityBean> query(String query);

...
}

@MappedSuperclass
public abstract class EntityBean {
...
}

This allowed us to cover almost every insert, update, delete and queries
including pagination and without the overhead of many DAO interfaces and
classes. We can then just inject this sucker where ever it is needed and
we're good to go.

You could go a step further and use named queries via this interface or
make an EJB QL query repository in XML or properties file and then load
that sucker up and find queries by key.

I find that EJB QL is pretty simple in the 90% cases and having it
sprinkled around the code doesn't bother me at all, since it is so
simple. The only thing I could think of as a downside would be query
caching and cursor optimization, but again we could always do a query
repository in those cases. Plus, I'm not building huge distributed
applications any longer so that might be unnecessary optimization.

-bp

> --~--~---------~--~----~------------~-------~--~----~
> You received this message because you are subscribed to the Google Groups "google-guice" group.
> To post to this group, send email to google...@googlegroups.com
> To unsubscribe from this group, send email to google-guice...@googlegroups.com
> For more options, visit this group at http://groups.google.com/group/google-guice?hl=en
> -~----------~----~----~----~------~----~------~--~---
>
>

Reply all
Reply to author
Forward
0 new messages