web-dev-qa-db-fra.com

JPA - FindByExample

Quelqu'un at-il un bon exemple sur la façon de faire un findByExample dans JPA qui fonctionnera dans un DAO générique via la réflexion pour tout type d'entité? Je sais que je peux le faire via mon fournisseur (Hibernate), mais je ne veux pas rompre avec la neutralité ...

Il semble que l'API de critères soit la voie à suivre ... mais je ne sais pas comment gérer la partie réflexion de celle-ci.

28
HDave

En fait, la requête par exemple (QBE) a été envisagée pour être incluse dans la spécification JPA 2.0 mais n'est pas incluse, même si les principaux fournisseurs la prennent en charge. Citant Mike Keith:

Je suis désolé de dire que nous n'avons pas réussi à faire QBE en JPA 2.0. L'API de critères n'a pas d'opérateurs spéciaux pour elle, donc l'égalité d'entité est exactement comme dans JP QL, basée sur la valeur PK. Désolé, mais j'espère que nous aurons plus de succès sur ce front lors du prochain tour de table. Pour l'instant, c'est l'une de ces fonctionnalités que tous les fournisseurs prennent en charge, mais elle n'est pas encore dans la spécification.

Au cas où, j'ai ajouté un exemple de code (non générique) pour les principaux fournisseurs ci-dessous à des fins de documentation.

EclipseLink

Voici un exemple d'utilisation de QBE dans l'implémentation de référence 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 prend en charge ce style de requête via son interface OpenJPAQueryBuilder étendue:

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

Et avec l'API Criteria d'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();

Maintenant, alors qu'il devrait être possible d'implémenter quelque chose qui approche d'une manière neutre vis-à-vis du fournisseur avec l'API JPA 2.0 Criteria et la réflexion, je me demande vraiment si cela en vaut la peine. Je veux dire, si vous générez l'un des extraits ci-dessus et mettez le code dans une méthode DAO, il serait assez facile de passer d'un fournisseur à un autre si le besoin s'en faisait sentir. Je suis d'accord que ce n'est pas idéal, mais quand même.

Références

37
Pascal Thivent

C'est assez grossier et je ne suis pas convaincu que ce soit une bonne idée en premier lieu. Mais quoi qu'il en soit, essayons d'implémenter QBE avec l'API de critères JPA-2.0.

Commencez par définir une interface Persistable:

public interface Persistable {
    public <T extends Persistable> Class<T> getPersistableClass();
}

La méthode getPersistableClass() est là parce que le DAO aura besoin de la classe, et je n'ai pas pu trouver une meilleure façon de dire T.getClass() plus tard. Vos classes de modèle implémenteront 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();
    }
}

Votre DAO peut alors avoir une méthode 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();
    }

C'est assez moche. Il suppose que les méthodes getter peuvent être dérivées des noms de champs (cela est probablement sûr, car l'exemple devrait être un Java Bean), manipule les chaînes dans la boucle et peut lever un tas d'exceptions. La plupart de la maladresse de cette méthode tourne autour du fait que nous réinventons la roue. Peut-être y a-t-il une meilleure façon de réinventer la roue, mais c'est peut-être là que nous devons concéder la défaite et recourir à l'une des méthodes énumérées par Pascal ci-dessus. Pour Hibernate , cela simplifierait l'interface pour:

public interface Persistable {}

et la méthode DAO perd presque tout son poids et sa lourdeur:

@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: Alors le test suivant devrait réussir:

@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

Vous devriez vérifier la solution proposée par Springfuse en utilisant Spring Data et JPA 2.

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

Quelques exemples de code source ici (sous le sous-package du référentiel): https://github.com/jaxio/generated-projects

Trouvé ce projet: https://github.com/jaxio/jpa-query-by-example

3
Nicolas Romanetti

https://github.com/superbiger/sbiger-jpa-qbe

Je pense que la requête par exemple avec une seule table comme mybatis est facile à utiliser

sur la base de jpa, nous pouvons également prendre en charge Join/GroupBy comme ceci:

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

Caractéristiques maintenant:

  • Support et/ou opération logique
  • Le support est (vide/booléen/nul)
  • Support égal/NotEqual/In/NotIn/Like/NotLike
  • Soutenir gt/ge/lt/le/entre
  • Prise en charge de la requête de jointure
  • Groupe de soutien par
  • Prise en charge des spécifications personnalisées.
  • Soutenir la pagination
    plus de fonctionnalités bientôt ……
1
superbiger

vous pouvez utiliser cela https://github.com/xiaod0510/jpa-findbyexample

si votre entité est 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> {
}

il suffit de créer votre propre exemple comme celui-ci:

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

et maintenant vous pouvez interroger par exemple:

 ContactRepository.findOne(ContactExample
                    .where()//default is and
                    .id.eq(1l)
);

l'exemple implémente l'interface "Specification", plus d'informations sur ce github

0
xiaod0510

L'API de critères est votre meilleur pari. Cependant, vous aurez besoin d'un fournisseur JPA-2.0. Donc, si vous avez une entité comme celle-ci:

@Entity
public class Foo {
    @Size(max = 20)
    private String name;
}

Le test unitaire suivant devrait réussir (je l'ai testé avec EclipseLink, mais il devrait fonctionner avec l'un des fournisseurs 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());
}

Aussi, vous voudrez peut-être suivre le lien vers le tutoriel référencé ici .

0
wallenborn

Peut-être que la réponse est trop tardive. Mais vérifiez ça. Cela pourrait être utile.

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

Tout d'abord, incluez le pot dans le chemin de classe. Vous aurez une classe appelée com.afifi.simpleJPAQuery.entities.utility.JPAUtil. Cette classe utilise la réflexion pour déduire la requête du bean. Supposons que vous ayez un bean entité comme suit:

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

Ensuite, si vous souhaitez interroger par nom de personne par exemple, vous devez procéder comme suit:

    //initiate entity manager (em)
    Person p=new Person();
    p.setPersonName("John");
    String sortString="";
    List<Person> result= JPAUtil.findByExample(em,p,sortString);

Le résultat obtiendra tous les enregistrements où le nom de la personne contenait le mot "John".

si vous voulez limiter les résultats, vous pouvez faire quelque chose comme:

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

Cette bibliothèque a d'autres méthodes comme:

getResultCount: pour obtenir le nombre de résultats

createSqlStatement: pour obtenir l'instruction sql utilisée

getSqlWhereString: pour obtenir uniquement la chaîne where utilisée

Il a les formes natives de ces fonctions:

findByExampleNative, getResultCountNative, createSqlStatementNative et getSqlWhereStringNative

La bibliothèque possède également la classe QueryAnnotations qui contient des annotations qui peuvent être ajoutées aux propriétés du bean Entity pour donner plus de contrôle sur la façon dont vous souhaitez interroger à l'aide du bean.

0