JPA – FindByExample

有没有人有一个很好的例子来说明如何在JPA中执行findByExample,它将通过任何实体类型的reflection在genericsDAO中工作? 我知道我可以通过我的提供商(Hibernate)来做,但我不想打破中立……

看起来像标准API可能是要走的路……但我不知道如何处理它的reflection部分。

实际上,查询示例(QBE)已被考虑包含在JPA 2.0规范中,但未包括在内,即使主要供应商支持它。 引用Mike Keith:

我很遗憾地说我们实际上没有在JPA 2.0中做QBE。 Criteria API没有任何特殊的运算符,因此实体相等就像JP QL一样,基于PK值。 对不起,但希望我们在下一轮比赛中能在这方面取得更大的成功。 目前,它是每个供应商支持的供应商function之一,但尚未在规范中。

为了以防万一,我为下面的主要供应商添加了(非通用的)示例代码以用于文档目的。

的EclipseLink

以下是在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的

OpenJPA通过其扩展的OpenJPAQueryBuilder接口支持这种查询方式:

 CriteriaQuery 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和reflection以供应商中立的方式实现一些接近的东西,但我真的很想知道它是否值得付出努力。 我的意思是,如果您将上述任何代码段设为通用代码并将代码放在DAO方法中,那么在需要时可以很容易地从一个供应商切换到另一个供应商。 我同意这不理想,但仍然。

参考

  • 那JPA书中的findByExample怎么样?
  • JPA 2.0中的动态,类型安全查询

这是相当粗糙的,我不相信这是一个好主意。 但无论如何,让我们尝试使用JPA-2.0标准API实现QBE。

首先定义一个Persistable接口:

 public interface Persistable { public  Class getPersistableClass(); } 

getPersistableClass()方法就在那里因为DAO需要这个类,我以后找不到更好的方法来说T.getClass() 。 您的模型类将实现Persistable

 public class Foo implements Persistable { private String name; private Integer payload; @SuppressWarnings("unchecked") @Override public  Class getPersistableClass() { return (Class) getClass(); } } 

那么你的DAO可以有一个findByExample(Persistable example)方法(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 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(); } 

这很难看。 它假设getter方法可以从字段名称派生(这可能是安全的,例如应该是Java Bean),在循环中进行字符串操作,并且可能抛出一堆exception。 这种方法中的大多数笨拙都围绕着我们重新发明轮子的事实。 也许有一种更好的方法来重新发明轮子,但也许这就是我们应该承认失败并采用Pascal上面列出的方法之一。 对于Hibernate,这会简化接口:

 public interface Persistable {} 

而DAO方法几乎失去了它的所有重量和笨重:

 @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(); } 

编辑:那么以下测试应该成功:

 @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()); } 

您应该使用Spring Data和JPA 2检查Springfuse提出的解决方案。

http://www.springfuse.com/2012/01/31/query-by-example-spring-data-jpa.html

这里有一些示例源代码(在repository子包下): https : //github.com/jaxio/generated-projects

找到这个项目: https : //github.com/jaxio/jpa-query-by-example

https://github.com/superbiger/sbiger-jpa-qbe

我想通过像mybatis这样的单个表的示例查询很容易使用

基于jpa,我们也可以像这样支持Join / GroupBy:

 /* SQL: select * from user where id=1 or id=2 group by id, name order by id asc, name asc limit ? */ public List findAll(){ Example example = ExampleBuilder.create(); example.or() .andEqual("id", 1) .orEqual("id", 2); example.groupBy("id","name"); example.asc("id","name"); return userReponsitory.findAll(example, new PageRequest(0, 1)); } 

现在的特点:

  • 支持和/或逻辑操作
  • 支持是(Empty / Boolean / Null)
  • 支持Equal / NotEqual / In / NotIn / Like / NotLike
  • 支持gt / ge / lt / le /之间
  • 支持连接查询
  • 支持小组
  • 支持自定义规范。
  • 支持分页
    更多function即将推出……

Criteria API是您最好的选择。 不过,你需要一个JPA-2.0提供程序。 所以如果你有这样的实体:

 @Entity public class Foo { @Size(max = 20) private String name; } 

以下unit testing应该成功(我使用EclipseLink测试它,但它应该适用于任何JPA-2.0提供程序):

 @PersistenceContext private EntityManager em; @Test @Transactional public void testFoo(){ Foo foo = new Foo(); foo.setName("one"); em.persist(foo); CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery c = cb.createQuery(Foo.class); Root f = c.from(Foo.class); c.select(f).where(cb.equal(f.get("name"), "one")); TypedQuery query = em.createQuery(c); Foo bar = query.getSingleResult(); Assert.assertEquals("one", bar.getName()); } 

此外,您可能希望按照此处引用的教程的链接进行操作。

你可以使用这个https://github.com/xiaod0510/jpa-findbyexample

如果您的实体是联系人:

 @Entity public class Contact { @Id @GeneratedValue private Long id; @Column private String name; @Column private Date birthday; //Getter and Setter } public interface ContactRepository extends JpaSpecificationExecutor { } 

只需像这样创建自己的示例:

 public class ContactExample extends BaseExample { public final Attr id = new Attr("id"); public final Attr name = new Attr("name"); public final Attr birthday = new Attr("birthday"); //default builder public static ContactExample where() { ContactExample example = new ContactExample(); example.operatorType = OperatorType.and; return example; } } 

现在您可以通过示例查询:

  ContactRepository.findOne(ContactExample .where()//default is and .id.eq(1l) ); 

该示例实现了接口“规范”,有关该github的更多信息

也许答案为时已晚。 但检查一下。 它可能会有所帮助。

https://sourceforge.net/projects/simplejpaquery/

首先,将jar包含在类路径中。 你将有一个名为com.afifi.simpleJPAQuery.entities.utility.JPAUtil的类。 此类使用reflection从bean中扣除查询。 假设您有一个实体bean,如下所示:

  @Entity public class Person { @Id private Integer personNo; private String personName; public Integer getPersonNo() { return personNo; } public void setPersonNo(Integer personNo) { this.personNo = personNo; } public String getPersonName() { return personName; } public void setPersonName(String personName) { this.personName = personName; } } 

然后,如果您想按人名查询,则需要执行以下操作:

  //initiate entity manager (em) Person p=new Person(); p.setPersonName("John"); String sortString=""; List result= JPAUtil.findByExample(em,p,sortString); 

结果将获得人名包含单词“John”的所有记录。

如果要限制结果,可以执行以下操作:

  List result= JPAUtil.findByExample(em, p, sortString, start, size); 

该库有其他方法,如:

getResultCount :获取结果的计数

createSqlStatement :获取正在使用的sql语句

getSqlWhereString :获取使用的字符串

它具有这些function的原生forms:

findByExampleNativegetResultCountNativecreateSqlStatementNativegetSqlWhereStringNative

该库还具有QueryAnnotations类,该类包含可以添加到Entity bean属性的注释,以便更好地控制使用bean查询的方式。