Does anyone have a good example for how to do a findByExample in JPA that will work within a generic DAO via reflection for any entity type? I know I can do it via my prov
This is quite crude and i'm not convinced it's a good idea in the first place. But anyway, let's try to implement QBE with the JPA-2.0 criteria API.
Start with defining an interface Persistable:
public interface Persistable {
public Class getPersistableClass();
}
The getPersistableClass() method is in there because the DAO will need the class, and i couldn't find a better way to say T.getClass() later on. Your model classes will implement Persistable:
public class Foo implements Persistable {
private String name;
private Integer payload;
@SuppressWarnings("unchecked")
@Override
public Class getPersistableClass() {
return (Class) getClass();
}
}
Then your DAO can have a findByExample(Persistable example) method (EDITED):
public class CustomDao {
@PersistenceContext
private EntityManager em;
public List findByExample(T example) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
Class clazz = example.getPersistableClass();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(clazz);
Root r = cq.from(clazz);
Predicate p = cb.conjunction();
Metamodel mm = em.getMetamodel();
EntityType et = mm.entity(clazz);
Set> attrs = et.getAttributes();
for (Attribute super T, ?> a: attrs) {
String name = a.getName();
String javaName = a.getJavaMember().getName();
String getter = "get" + javaName.substring(0,1).toUpperCase() + javaName.substring(1);
Method m = cl.getMethod(getter, (Class>[]) null);
if (m.invoke(example, (Object[]) null) != null)
p = cb.and(p, cb.equal(r.get(name), m.invoke(example, (Object[]) null)));
}
cq.select(r).where(p);
TypedQuery query = em.createQuery(cq);
return query.getResultList();
}
This is quite ugly. It assumes getter methods can be derived from field names (this is probably safe, as example should be a Java Bean), does string manipulation in the loop, and might throw a bunch of exceptions. Most of the clunkiness in this method revolves around the fact that we're reinventing the wheel. Maybe there's a better way to reinvent the wheel, but maybe that's where we should concede defeat and resort to one of the methods listed by Pascal above. For Hibernate, this would simplify the Interface to:
public interface Persistable {}
and the DAO method loses almost all of its weight and clunkiness:
@SuppressWarnings("unchecked")
public List findByExample(T example) {
Session session = (Session) em.getDelegate();
Example ex = Example.create(example);
Criteria c = session.createCriteria(example.getClass()).add(ex);
return c.list();
}
EDIT: Then the following test should succeed:
@Test
@Transactional
public void testFindFoo() {
em.persist(new Foo("one",1));
em.persist(new Foo("two",2));
Foo foo = new Foo();
foo.setName("one");
List l = dao.findByExample(foo);
Assert.assertNotNull(l);
Assert.assertEquals(1, l.size());
Foo bar = l.get(0);
Assert.assertNotNull(bar);
Assert.assertEquals(Integer.valueOf(1), bar.getPayload());
}