web-dev-qa-db-ja.com

JPA-FindByExample

エンティティタイプのリフレクションを介して汎用DAO内で機能するJPAでfindByExampleを実行する方法の良い例はありますか?私はプロバイダー(Hibernate)を介してそれを行うことができることを知っていますが、中立で壊れたくありません...

基準APIが適しているようですが、その反映部分の処理方法がわかりません。

28
HDave

実際、例によるクエリ(QBE)はJPA 2.0仕様に含めることが検討されていますが、主要ベンダーがサポートしている場合でも含まれていません。マイク・キースの引用:

JPA 2.0では実際にQBEを実行できなかったと申し訳ありません。 Criteria APIには特別な演算子がないため、エンティティの等価性は、PK値に基づくJP QLと同じです。申し訳ありませんが、うまくいけば、次のゴーラウンドでその面でより成功するでしょう。現時点では、すべてのベンダーがサポートするベンダー機能の1つですが、まだ仕様には含まれていません。

念のため、ドキュメント化の目的で、以下の主要なベンダー向けの(一般的ではない)サンプルコードを追加しました。

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

そして、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メソッドに入れると、必要に応じて、あるベンダーから別のベンダーに簡単に切り替えることができます。理想的ではありませんが、それでも同意します。

参考文献

37
Pascal Thivent

これはかなり粗雑で、そもそもそれが良いアイデアだとは思いません。とにかく、JPA-2.0基準APIでQBEを実装してみましょう。

Persistableインターフェースの定義から始めます。

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

これはかなり醜いです。これは、ゲッターメソッドがフィールド名から派生できることを前提としています(例はJava Bean)である必要があるため、これはおそらく安全です)、ループで文字列操作を行い、多数の例外をスローする可能性があります。このメソッドの不格好の原因は、ホイールを再発明しているという事実に関係しています。多分、ホイールを再発明するためのより良い方法がありますが、それは、敗北を認めて、上記のパスカルによってリストされた方法の1つに頼るべき場所です。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());      
}
_
11
wallenborn

Springfuseが提案するソリューションは、Spring Data&JPA 2を使用して確認する必要があります。

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

ここにいくつかのサンプルソースコード(リポジトリサブパッケージの下): https://github.com/jaxio/generated-projects

このプロジェクトが見つかりました: https://github.com/jaxio/jpa-query-by-example

3

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
  • 結合クエリをサポート
  • サポートグループ
  • カスタム仕様をサポートします。
  • ページネーションをサポート
    その他の機能は近日提供予定です……
1
superbiger

あなたはこれを使うことができます 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の詳細情報

0
xiaod0510

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 をたどることもできます。

0
wallenborn

多分答えは遅すぎます。しかし、これを確認してください。それは助けになるかもしれません。

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);

結果は、人物名に「John」という単語が含まれるすべてのレコードを取得します。

結果を制限したい場合は、次のようにすることができます。

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

このライブラリには、次のような他のメソッドがあります。

getResultCount:結果の数を取得する

createSqlStatement:使用されているSQLステートメントを取得する

getSqlWhereString:文字列が使用された場所のみを取得する

これらの関数のネイティブ形式があります。

findByExampleNativegetResultCountNativecreateSqlStatementNativeおよびgetSqlWhereStringNative

ライブラリにはQueryAnnotationsクラスもあり、このBeanを使用してクエリする方法をより詳細に制御するためにエンティティBeanプロパティに追加できる注釈が含まれています。

0