エンティティタイプのリフレクションを介して汎用DAO内で機能するJPAでfindByExampleを実行する方法の良い例はありますか?私はプロバイダー(Hibernate)を介してそれを行うことができることを知っていますが、中立で壊れたくありません...
基準APIが適しているようですが、その反映部分の処理方法がわかりません。
実際、例によるクエリ(QBE)はJPA 2.0仕様に含めることが検討されていますが、主要ベンダーがサポートしている場合でも含まれていません。マイク・キースの引用:
JPA 2.0では実際にQBEを実行できなかったと申し訳ありません。 Criteria APIには特別な演算子がないため、エンティティの等価性は、PK値に基づくJP QLと同じです。申し訳ありませんが、うまくいけば、次のゴーラウンドでその面でより成功するでしょう。現時点では、すべてのベンダーがサポートするベンダー機能の1つですが、まだ仕様には含まれていません。
念のため、ドキュメント化の目的で、以下の主要なベンダー向けの(一般的ではない)サンプルコードを追加しました。
以下は、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を実装してみましょう。
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());
}
_
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
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));
}
今すぐ機能:
あなたはこれを使うことができます 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の詳細情報
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 をたどることもできます。
多分答えは遅すぎます。しかし、これを確認してください。それは助けになるかもしれません。
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
:文字列が使用された場所のみを取得する
これらの関数のネイティブ形式があります。
findByExampleNative
、getResultCountNative
、createSqlStatementNative
およびgetSqlWhereStringNative
ライブラリにはQueryAnnotations
クラスもあり、このBeanを使用してクエリする方法をより詳細に制御するためにエンティティBeanプロパティに追加できる注釈が含まれています。