JPA - FindByExample

前端 未结 7 2011
故里飘歌
故里飘歌 2020-12-08 04:29

Does anyone have a good example for how to do a findByExample in JPA that will work within a generic DAO via reflection for any entity type? I know I can do it via my prov

7条回答
  •  生来不讨喜
    2020-12-08 05:18

    This is quite crude and i'm not convinced it's a good idea in the first place. But anyway, let's try to implement QBE with the JPA-2.0 criteria API.

    Start with defining an interface Persistable:

    public interface Persistable {
        public  Class getPersistableClass();
    }
    

    The getPersistableClass() method is in there because the DAO will need the class, and i couldn't find a better way to say T.getClass() later on. Your model classes will implement Persistable:

    public class Foo implements Persistable {
        private String name;
        private Integer payload;
    
        @SuppressWarnings("unchecked")
        @Override
        public  Class getPersistableClass() {
            return (Class) getClass();
        }
    }
    

    Then your DAO can have a findByExample(Persistable example) method (EDITED):

    public class CustomDao {
        @PersistenceContext
        private EntityManager em;
    
        public  List findByExample(T example) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
            Class clazz = example.getPersistableClass();
            CriteriaBuilder cb = em.getCriteriaBuilder();
            CriteriaQuery cq = cb.createQuery(clazz);
            Root r = cq.from(clazz);
            Predicate p = cb.conjunction();
            Metamodel mm = em.getMetamodel();
            EntityType et = mm.entity(clazz);
            Set> attrs = et.getAttributes();
            for (Attribute 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 query = em.createQuery(cq);
            return query.getResultList();
        }
    

    This is quite ugly. It assumes getter methods can be derived from field names (this is probably safe, as example should be a Java Bean), does string manipulation in the loop, and might throw a bunch of exceptions. Most of the clunkiness in this method revolves around the fact that we're reinventing the wheel. Maybe there's a better way to reinvent the wheel, but maybe that's where we should concede defeat and resort to one of the methods listed by Pascal above. For Hibernate, this would simplify the Interface to:

    public interface Persistable {}
    

    and the DAO method loses almost all of its weight and clunkiness:

    @SuppressWarnings("unchecked")
    public  List 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: Then the following test should succeed:

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

提交回复
热议问题