25

So, After a 10+ year break I'm coming back to Java and trying out stuff with JPA and Java generics. I've created a generics based findAll(other) JPA query that basically does

SELECT * FROM source WHERE other_id = other.id;

This is where I'm up to. It works, but I'm wondering if there's a better, cleaner way to do it. Using ManagedType was hard, and there's not much complete documentation or simple examples around.

I've decided to keep my code as generic as possible (no pun intended) so I use JPA2.

This is the root of all Entity Classes. I probably don't need it, but it stops me from having basic mistakes.

import java.io.Serializable;

public abstract class DomainObject implements Serializable {

    private static final long serialVersionUID = 1L;

    public abstract void setId(Long id);
    public abstract Long getId();

}

This is the abstract DAO class. I extend this for the implementation classes as I need to be more specific doing other activities - mostly making sure lazy sets are loaded.

public abstract class GenericDAOImpl<T extends DomainObject, T2 extends DomainObject> implements GenericDAO<T, T2> {

private Class<T> type;

@PersistenceContext
protected EntityManager entityManager;

public GenericDAOImpl(Class<T> type) {
    super();
    this.type = type;
}

... save and delete classes go here

@Override
public List<T> findAll(T2 where) {

    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(type);
    Root<T> rootQuery = criteriaQuery.from(type);
    if (where != null) {

        EntityType<T> entity = entityManager.getMetamodel().entity(type);

        SingularAttribute<? super T, ?> attribute = null;
        for (SingularAttribute<? super T, ?> singleAttribute: entity.getSingularAttributes()) {
            // loop through all attributes that match this class
            if (singleAttribute.getJavaType().equals(where.getClass())) {
                // winner!
                attribute = singleAttribute;
                break;
            }
        }
        // where t.object = object.getID()
        criteriaQuery.where(criteriaBuilder.equal(rootQuery.get(attribute), where));
    }
    criteriaQuery.select(rootQuery);
    TypedQuery<T> query = entityManager.createQuery(criteriaQuery);

    // need this to make sure we have a clean list?
    // entityManager.clear();
    return query.getResultList();
}

Any suggestions? If anything, I want this out there so other people can make use of it.

Java Enthusiast
  • 654
  • 7
  • 19
Jim Richards
  • 708
  • 1
  • 7
  • 19
  • 2
    Why reinvent the wheel? Spring Data JPA already does all that and more. And for the Spring haters Spring Data JPA works fine in a plain JEE environment also no Spring (apart from the internal usage of Spring Data JPA) needed. – M. Deinum Jul 04 '14 at 10:40
  • 1
    I'm using Spring for my presentation layer, but I'm not ready to read 10,000+ pages of documentation just yet to work out Spring DATA JPA (and I've read a lot the last few months already). I'm sure Spring DATA JPA can do it - and I'd be happy for you to post examples/links. But this works, it's non-Java standard free and considering 2 months ago I had no idea about this ... anyway, if I want to know how a wheel works, I'll build one not buy. – Jim Richards Jul 04 '14 at 11:02
  • 1
    This should be posted on http://codereview.stackexchange.com. That said. What if a Child has a father and a mother, both of type Human? – JB Nizet Jul 04 '14 at 11:21
  • Yeah, it doesn't work when there is more than one Attribute of the same type. I found this out with my `Subject Entity` having `String username'` and `String password;`. – Jim Richards Jul 05 '14 at 09:39

4 Answers4

33

Hat tip to Adam Bien if you don't want to use createQuery with a String and want type safety:

 @PersistenceContext
 EntityManager em;

 public List<ConfigurationEntry> allEntries() {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<ConfigurationEntry> cq = cb.createQuery(ConfigurationEntry.class);
        Root<ConfigurationEntry> rootEntry = cq.from(ConfigurationEntry.class);
        CriteriaQuery<ConfigurationEntry> all = cq.select(rootEntry);
        TypedQuery<ConfigurationEntry> allQuery = em.createQuery(all);
        return allQuery.getResultList();
 }

http://www.adam-bien.com/roller/abien/entry/selecting_all_jpa_entities_as

dur
  • 15,689
  • 25
  • 79
  • 125
Adam
  • 3,675
  • 8
  • 45
  • 77
14

I found this page very useful

https://code.google.com/p/spring-finance-manager/source/browse/trunk/src/main/java/net/stsmedia/financemanager/dao/GenericDAOWithJPA.java?r=2

public abstract class GenericDAOWithJPA<T, ID extends Serializable> {

    private Class<T> persistentClass;

    //This you might want to get injected by the container
    protected EntityManager entityManager;

    @SuppressWarnings("unchecked")
    public GenericDAOWithJPA() {
            this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
            return entityManager.createQuery("Select t from " + persistentClass.getSimpleName() + " t").getResultList();
    }
}
R. Gulbrandsen
  • 3,648
  • 1
  • 22
  • 35
  • 1
    I'm trying to avoid using SQL at all in the query. It's just my preference, otherwise I'd just use pure SQL in the transactions, and then you end up writing your own version of JPA. – Jim Richards Feb 02 '15 at 07:43
  • 3
    It's in fact JPQL (Java Persistance Query Language), it's the core of JPA. As long as Java don't have Linq queries like .NET you need to have some JPQL where core JPA don't do what you want. If you take a look at what actually happens when you do a find(MyClass.class, id) is that it translates it into SQL and the dialect you are using in your peristance.xml. When you use JPQL it does the same thing with your query. You wont make your own version of JPA, you'll be adding to the core functionality, like MyClass extends Object – R. Gulbrandsen Feb 02 '15 at 07:46
4

you can also use a namedQuery named findAll for all your entities and call it in your generic FindAll with

entityManager.createNamedQuery(persistentClass.getSimpleName()+"findAll").getResultList();
Hosseinmp76
  • 317
  • 2
  • 12
Vivien SA'A
  • 727
  • 8
  • 16
2

This will work, and if you need where statement you can add it as parameter.

class GenericDAOWithJPA<T, ID extends Serializable> {

.......

public List<T> findAll() {
            return entityManager.createQuery("Select t from " + persistentClass.getSimpleName() + " t").getResultList();
    }
}
Eduard
  • 3,176
  • 3
  • 21
  • 31