JPA - FindByExample - PullRequest
       25

JPA - FindByExample

27 голосов
/ 21 мая 2010

Есть ли у кого-нибудь хороший пример того, как сделать findByExample в JPA, который будет работать в общем DAO через отражение для любого типа сущности?Я знаю, что могу сделать это через своего провайдера (Hibernate), но я не хочу порвать с нейтралитетом ...

Похоже, критерии API могут быть подходящим способом .... но яне уверен, как обращаться с отражающей частью.

Ответы [ 7 ]

37 голосов
/ 21 мая 2010

На самом деле, Query By Example (QBE) был рассмотрен для включения в спецификацию JPA 2.0, но не включен, даже если крупные поставщики поддерживают его. Цитируя Майка Кейта:

Мне жаль говорить, что мы на самом деле не смогли сделать QBE в JPA 2.0. В Criteria 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 критериев JPA 2.0 и рефлексии, мне действительно интересно, стоит ли это усилий. Я имею в виду, что если вы сделаете любой из вышеперечисленных фрагментов общим и поместите код в метод DAO, было бы довольно легко переключаться с одного поставщика на другого, если возникнет такая необходимость. Я согласен, что это не идеально, но все же.

Ссылки

11 голосов
/ 25 мая 2010

Это довольно грубо, и я не уверен, что это хорошая идея. Но в любом случае, давайте попробуем реализовать QBE с API критериев JPA-2.0.

Начните с определения интерфейса. 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();
    }

Это довольно некрасиво. Предполагается, что методы получения могут быть получены из имен полей (это, вероятно, безопасно, как пример должен быть 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();
}

РЕДАКТИРОВАТЬ: Тогда следующий тест должен быть успешным:

@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 голосов
/ 25 февраля 2012

Вы должны проверить решение, предложенное 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

1 голос
/ 27 декабря 2016

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

Особенности сейчас:

  • Поддержка и / или логическая операция
  • Поддержка (пустая / логическая / нулевая)
  • Поддержка равно / NotEqual / In / NotIn / Like / NotLike
  • Поддержка GT / Ge / LT / ле / между
  • Запрос на объединение поддержки
  • Группа поддержки по
  • Поддержка пользовательских спецификаций.
  • Поддержка нумерации страниц
    скоро появятся новые функции ……
0 голосов
/ 02 апреля 2017

Может быть, ответ слишком поздно.Но проверь это.Это может помочь.

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

Сначала включите банку в путь к классам.У вас будет класс с именем com.afifi.simpleJPAQuery.entities.utility.JPAUtil.Этот класс использует отражение, чтобы вычесть запрос из компонента.Предположим, у вас есть объектный компонент следующим образом:

    @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: для получения только используемой строки where

Он имеет собственные формыиз этих функций:

findByExampleNative, getResultCountNative, createSqlStatementNative и getSqlWhereStringNative

В библиотеке также есть класс QueryAnnotations, который содержит аннотации, которые могут быть добавлены в компонент Entity.свойства, чтобы дать больше контроля над тем, как вы хотите сделать запрос с помощью компонента.

0 голосов
/ 15 декабря 2016

Вы можете использовать это 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 голосов
/ 21 мая 2010

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

Также вы можете перейти по ссылке на учебник, на который ссылается здесь .

...