JPA - FindByExample

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

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

28
задан Manuel Drieschmanns 7 November 2017 в 21:04
поделиться

3 ответа

Фактически, 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

И с 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();

Теперь, хотя должно быть возможно реализовать что-то, приближающееся к независимому от производителя способу с API критериев JPA 2.0 и отражением, мне действительно интересно, стоит ли это усилий. Я имею в виду, что если вы сделаете любой из приведенных выше фрагментов универсальным и поместите код в метод DAO, будет довольно легко переключиться с одного поставщика на другого, если возникнет такая необходимость. Согласен, не идеально, но все же.

Ссылки

38
ответ дан 28 November 2019 в 03:09
поделиться

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

Кроме того, вы можете захотеть следовать ссылка на учебное пособие здесь .

0
ответ дан 28 November 2019 в 03:09
поделиться

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

Это довольно уродливо. Предполагается, что методы получения могут быть получены из имен полей (это, вероятно, безопасно, например, 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());      
}
11
ответ дан 28 November 2019 в 03:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: