JPA - FindByExample
有没有人有一个很好的例子来说明如何在JPA中做一个findByExample,它将通过任何实体类型的反射在通用DAO中工作?我知道我可以通过我的提供商(Hibernate)做到这一点,但我不想打破中立性......
似乎API标准可能是要走的路....但我不确定如何处理它的反射部分。
有没有人有一个很好的例子来说明如何在JPA中做一个findByExample,它将通过任何实体类型的反射在通用DAO中工作?我知道我可以通过我的提供商(Hibernate)做到这一点,但我不想打破中立性......
似乎API标准可能是要走的路....但我不确定如何处理它的反射部分。
实际上,按示例查询 (QBE) 已被考虑包含在 JPA 2.0 规范中,但未包括在内,即使主要供应商支持它。引用迈克·基思的话:
我很抱歉地说,我们实际上并没有在JPA 2.0中做QBE。Criteria API 没有任何特殊运算符,因此实体相等性就像在 JP QL 中一样,基于 PK 值。很抱歉,但希望我们在下一轮中在这方面取得更大的成功。目前,它是每个供应商都支持的供应商功能之一,但尚未在规范中。
为了以防万一,我为下面的主要供应商添加了(非通用)示例代码,以便进行文档编写。
以下是在 EclipseLink JPA 2.0 参考实现中使用 QBE 的示例:
// Create a native EclipseLink query using QBE policy
QueryByExamplePolicy policy = new QueryByExamplePolicy();
policy.excludeDefaultPrimitiveValues();
ReadObjectQuery q = new ReadObjectQuery(sampleEmployee, policy);
// Wrap the native query in a standard JPA Query and execute it
Query query = JpaHelper.createQuery(q, em);
return query.getSingleResult();
OpenJPA通过其扩展接口支持这种查询方式:OpenJPAQueryBuilder
CriteriaQuery<Employee> q = cb.createQuery(Employee.class);
Employee example = new Employee();
example.setSalary(10000);
example.setRating(1);
q.where(cb.qbe(q.from(Employee.class), example);
使用Hibernate的Criteria API:
// get the native hibernate session
Session session = (Session) getEntityManager().getDelegate();
// create an example from our customer, exclude all zero valued numeric properties
Example customerExample = Example.create(customer).excludeZeroes();
// create criteria based on the customer example
Criteria criteria = session.createCriteria(Customer.class).add(customerExample);
// perform the query
criteria.list();
现在,虽然应该有可能使用JPA 2.0 Criteria API和反射以供应商中立的方式实现一些东西,但我真的想知道这是否值得付出努力。我的意思是,如果您使上述任何片段通用并将代码放在DAO方法中,那么如果需要,从一个供应商切换到另一个供应商将非常容易。我同意这并不理想,但仍然如此。
这是非常粗糙的,我不相信这首先是一个好主意。但无论如何,让我们尝试使用JPA-2.0标准API实现QBE。
从定义一个持久化接口开始:
public interface Persistable {
public <T extends Persistable> Class<T> getPersistableClass();
}
方法在那里,因为DAO将需要这个类,我找不到更好的方式说以后。您的模型类将实现:getPersistableClass()
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();
}
这是相当丑陋的。它假设 getter 方法可以从字段名称派生(这可能是安全的,例如应该是 Java Bean),在循环中执行字符串操作,并可能引发一堆异常。这种方法中的大多数笨拙都围绕着我们正在重新发明轮子的事实。也许有更好的方法来重新发明轮子,但也许这就是我们应该承认失败并诉诸Pascal上面列出的方法之一的地方。对于休眠,这将简化接口::
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());
}