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 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(); }
这很难看。 它假设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:
findByExampleNative
, getResultCountNative
, createSqlStatementNative
和getSqlWhereStringNative
该库还具有QueryAnnotations
类,该类包含可以添加到Entity bean属性的注释,以便更好地控制使用bean查询的方式。