Это довольно грубо, и я не уверен, что это хорошая идея. Но в любом случае, давайте попробуем реализовать QBE с API критериев JPA-2.0.
Начните с определения интерфейса. Persistable:
public interface Persistable {
public <T extends Persistable> Class<T> getPersistableClass();
}
Существует метод getPersistableClass()
, потому что классу DAO понадобится класс, и я не смог найти лучший способ сказать T.getClass()
позже. Ваши классы моделей будут реализовывать Persistable
:
public class Foo implements Persistable {
private String name;
private Integer payload;
@SuppressWarnings("unchecked")
@Override
public <T extends Persistable> Class<T> getPersistableClass() {
return (Class<T>) getClass();
}
}
Тогда ваш DAO может иметь метод findByExample(Persistable example)
(РЕДАКТИРОВАНИЕ):
public class CustomDao {
@PersistenceContext
private EntityManager em;
public <T extends Persistable> List<T> findByExample(T example) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
Class<T> clazz = example.getPersistableClass();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(clazz);
Root<T> r = cq.from(clazz);
Predicate p = cb.conjunction();
Metamodel mm = em.getMetamodel();
EntityType<T> et = mm.entity(clazz);
Set<Attribute<? super T, ?>> 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<T> query = em.createQuery(cq);
return query.getResultList();
}
Это довольно некрасиво. Предполагается, что методы получения могут быть получены из имен полей (это, вероятно, безопасно, как пример должен быть Java Bean), выполняет строковые манипуляции в цикле и может выдавать кучу исключений. Большая часть грубости в этом методе вращается вокруг факта, что мы изобретаем колесо. Возможно, есть лучший способ заново изобрести колесо, но, возможно, именно здесь мы должны признать свое поражение и прибегнуть к одному из методов, перечисленных Паскалем выше. Для Hibernate это упростит интерфейс до:
public interface Persistable {}
и метод DAO теряет почти весь вес и неуклюжесть:
@SuppressWarnings("unchecked")
public <T extends Persistable> List<T> findByExample(T example) {
Session session = (Session) em.getDelegate();
Example ex = Example.create(example);
Criteria c = session.createCriteria(example.getClass()).add(ex);
return c.list();
}
РЕДАКТИРОВАТЬ: Тогда следующий тест должен быть успешным:
@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<Foo> 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());
}