JPA - FindByExample

时间:2010-05-21 07:17:06

标签: java jpa criteria dao

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

似乎标准API可能是要走的路......但我不知道如何处理它的反射部分。

7 个答案:

答案 0 :(得分:37)

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

  

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

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

的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<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方法中,则在需要时可以很容易地从一个供应商切换到另一个供应商。我同意这不理想,但仍然。

参考

答案 1 :(得分:11)

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

从定义界面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();
    }

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

答案 2 :(得分:3)

你应该使用Spring Data&amp; amp;检查Springfuse提出的解决方案。 JPA 2。

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

答案 3 :(得分:1)

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<User> findAll(){
    Example<User> 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));
}

现在的功能:

  • 支持和/或逻辑操作
  • 支持是(空/布尔/空)
  • 支持Equal / NotEqual / In / NotIn / Like / NotLike
  • 支持gt / ge / lt / le / between
  • 支持加入查询
  • 支持小组
  • 支持自定义规范。
  • 支持分页
    更多功能即将推出......

答案 4 :(得分:0)

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

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

以下单元测试应该成功(我使用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<Foo> c = cb.createQuery(Foo.class);
    Root<Foo> f = c.from(Foo.class);
    c.select(f).where(cb.equal(f.get("name"), "one"));
    TypedQuery<Foo> query = em.createQuery(c);
    Foo bar = query.getSingleResult();
    Assert.assertEquals("one", bar.getName());
}

此外,您可能希望关注指向here的教程的链接。

答案 5 :(得分:0)

您可以使用此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<Contact> {
}

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

public class ContactExample extends BaseExample<ContactExample, Contact> {
    public final Attr<Long> id = new Attr<Long>("id");
    public final Attr<String> name = new Attr<String>("name");
    public final Attr<Date> birthday = new Attr<Date>("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的更多信息

答案 6 :(得分:0)

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

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

首先,将jar包含在类路径中。您将拥有一个名为com.afifi.simpleJPAQuery.entities.utility.JPAUtil的班级。 此类使用反射从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<Person> result= JPAUtil.findByExample(em,p,sortString);

结果将获得人名包含单词&#34; John&#34;的所有记录。

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

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

这个库有其他方法,如:

getResultCount:获取结果的计数

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

getSqlWhereString:获取使用的字符串

它具有这些功能的原生形式:

findByExampleNativegetResultCountNativecreateSqlStatementNativegetSqlWhereStringNative

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