JPA - FindByExample
Есть ли у кого-нибудь хороший пример того, как сделать findByExample в JPA, который будет работать в общем DAO через отражение для любого типа сущности? Я знаю, что могу сделать это через моего провайдера (Hibernate), но я не хочу нарушать нейтралитет...
Похоже, что критерий API может быть способом... но я не уверен, как обрабатывать его часть отражения.
Ответы
Ответ 1
Фактически, Query By Example (QBE) был рассмотрен для включения в спецификацию JPA 2.0, но не включен, даже если основные поставщики поддерживают его. Цитата Майка Кейта:
Мне жаль говорить, что мы фактически не получали QBE в JPA 2.0. Критерии API не имеют для этого каких-либо специальных операторов, поэтому равенство сущности равно как и в JP QL, основанное на значении PK. Извините, но, надеюсь, мы будем более успешными на этом фронте в следующем раунде. На данный момент это одна из тех функций вендора, которую поддерживает каждый поставщик, но пока не входит в спецификацию.
На всякий случай, я добавил (не общий) пример кода для основных поставщиков ниже для целей документации.
EclipseLink
Ниже приведен пример использования QBE в эталонной реализации EclipseLink JPA 2.0:
// 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
И с API-интерфейсом Hibernate:
// 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();
Теперь, хотя должно быть возможно реализовать что-то, приближающееся к поставщику нейтральным способом с API-интерфейсом API JPA 2.0 и отражением, я действительно задаюсь вопросом, стоит ли это делать. Я имею в виду, что если вы сделаете какой-либо из вышеперечисленных фрагментов и поместите код в метод DAO, было бы легко переключиться с одного поставщика на другой, если возникнет такая необходимость. Я согласен, что это не идеальный, но все же.
Ссылки
Ответ 2
Это довольно грубо, и я не уверен, что это хорошая идея. Но в любом случае попробуйте реализовать QBE с API-интерфейсом критериев JPA-2.0.
Начните с определения интерфейса. Стабильность:
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)
(EDITED):
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), выполняет строковые манипуляции в цикле и может генерировать кучу исключений. Большая часть неуклюжести в этом методе вращается вокруг того, что мы изобретаем колесо. Может быть, есть лучший способ изобретать колесо, но, возможно, это то, где мы должны уступить поражение и прибегнуть к одному из методов, перечисленных Паскалем выше. Для 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();
}
EDIT: Тогда следующий тест должен быть успешным:
@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());
}
Ответ 3
Вы должны проверить решение, предложенное 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
Ответ 4
https://github.com/superbiger/sbiger-jpa-qbe
I'think запрос на примере с одной таблицей, такой как mybatis, прост в использовании
base на 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));
}
Теперь функции:
- Поддержка и/или логическая операция
- Поддержка (Empty/Boolean/Null)
- Поддержка Equal/NotEqual/In/NotIn/Like/NotLike
- Поддержка gt/ge/lt/le/между
- Поддержка запроса на соединение
- Группа поддержки
- Поддержка пользовательских спецификаций.
- Поддержка разбивки на страницы
больше функций скоро...
Ответ 5
Критерии API - ваш лучший выбор. Тем не менее, для этого вам понадобится поставщик JPA-2.0. Поэтому, если у вас есть такая сущность:
@Entity
public class Foo {
@Size(max = 20)
private String name;
}
Следующий unit test должен преуспеть (я тестировал его с помощью 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());
}
Кроме того, вы можете захотеть перейти по ссылке к руководству здесь.
Ответ 6
вы можете использовать этот https://github.com/xiaod0510/jpa-findbyexample
если ваш объект Contact:
@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
Ответ 7
Возможно, ответ слишком поздний. Но проверьте это. Это может быть полезно.
https://sourceforge.net/projects/simplejpaquery/
Сначала включите банку в путь к классам. У вас будет класс под названием 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);
В результате будут получены все записи, в которых имя человека содержит слово "Джон".
если вы хотите ограничить результаты, вы можете сделать что-то вроде:
List<Person> result= JPAUtil.findByExample(em, p, sortString, start, size);
В этой библиотеке есть другие методы, например:
getResultCount
: чтобы получить счетчик результата
createSqlStatement
: получить инструкцию sql, которая используется
getSqlWhereString
: чтобы получить только используемую строку
Он имеет собственные формы этих функций:
findByExampleNative
, getResultCountNative
, createSqlStatementNative
и getSqlWhereStringNative
В библиотеке также есть класс QueryAnnotations
, который содержит аннотации, которые могут быть добавлены в свойства Entity bean, чтобы дать больше контроля над тем, как вы хотите запросить с помощью bean.