Abstracting named queries in an abstract JPA DAO

烈酒焚心 提交于 2020-01-09 18:34:10

问题


I have an abstract DAO class which uses parameterized types E (Entity) and K (Primary Key). In every entity I have a @NamedQuery. I want to dynamically invoke this named query without knowing its exact name and parameter name.

As an example, imagine the following entity City

@Entity(name="CITY")
@NamedQuery(
    name="findCityByname",
    query="FROM CITY c WHERE name = :CityName"
)
public class City { 
    // ...
}

and this CityDao

public class CityDao extends AbstractDao<City, Long> {
    public CityDao() {
        super(City.class);
    }   
}

How should I implement the findByName() method in AbstractDao so that I don't need to know the exact name and parameter name?

public abstract class AbstractDao<E, K> implements Dao<E, K> {

    @PersistenceContext
    protected EntityManager entityManager;
    protected Class<E> entityClass;

    protected AbstractDao(Class<E> entityClass) {
        this.entityClass = entityClass; 
    }

    @Override
    public E findByName(String name) {
        try {
            return (E) entityManager
                .createNamedQuery("findCityByName")
                .setParameter("CityName", name)
                .getSingleResult();
        } catch(Exception e) {
            return null;
        }
    }

    // ...
}

回答1:


The naming convention for named queries is usually <Entity Name>.findBy<PropertyAndAnotherProperty>, "City.findByName" in your example, so I would try to change the named queries to follow this pattern. The parameter to this query should then also have the same name, or you could use positional parameters. Your find method would then turn into

@Override
public E findByName(String name) {
    E entity = null;
    try {
        return (E)entityManager.createNamedQuery(myClass.getSimpleName() + ".findByName")
                               .setParameter("name", name)
                               .getSingleResult();
    } catch (Exception ex) {
        return null;
    }
}



回答2:


The simplest method is to pass the name of the query to the constructor of the abstract DAO:

public DaoAbstreact(Class myClass, String findByNameQueryName) {
    this.myClass = myClass; 
    this.findByNameQueryName = findByNameQueryName;
}

Then define a public static final String in City to hold the name:

public class ConcreteCityDao<City,Long> extends DaoAbstreact {    
    ConcreteCityDao(){
        super(City.class, City.FIND_BY_NAME_QUERY_NAME));
    }   
}

Alternatively you could declare DaoAbstreact as abstract and then have a method like this in it:

public abstract String getFindByNameQueryName();

And implement that in ConcreteCityDao.

Finally you could also introduce an enumeration:

public enum NamedEntityType {
    CITY(City.class, "findCityByname"), 
    PERSON(Person.class, "findPersonByname");

    private final Class<?> entityClass;

    private final String findByNameQueryName;

    private NamedEntityType(Class<?> entityClass, String findByNameQueryName) {
         this.entityClass = entityClass;
         this.findByNameQueryName = findByNameQueryName;
    }

    public Class<?> getEntityClass() {
        return entityClass;
    }

    public String getFindByNameQueryName() {
        return findByNameQueryName;
    }
}

Then your DAO can determine the type from the class passed in. To ensure you don't forget to add an entity to the enumeration you can make each entity implement an interface with a getNamedEntityType() method. Then you can specify that your abstract generic DAO will only accept entities that implement that interface.




回答3:


The obvious way would be to pass values from concrete classes to the abstract superclass using abstract method

public abstract class AbstractDao<E, K extends Serializable> implements Dao <E, K> {
    ...
    protected abstract String getFindByNameQueryName();

    @Override
    public E findByName(String EntityStr) { 
        ... entityManager.createNamedQuery(getFindByNameQueryName()) ...
    }
}

@Override
public class ConcreteCityDao<City,Long> extends DaoAbstreact{
    ...
    protected String getFindByNameQueryName() { 
        return "findCityByName";
    }
}

or as a constructor argument:

public abstract class AbstractDao<E, K extends Serializable> implements Dao<E, K> {
    public AbstractDao(Class<E> myClass, String findByNameQueryName) { ... }
    ...
}

@Override
public class ConcreteCityDao<City, Long> extends DaoAbstreact{
    public ConcreteCityDao() {
        super(City.class, "findCityByName");
    }
}

Though this requires consistent naming of query parameters for different entities.

Also note the minor improvements in these snippets.




回答4:


What you basically seem to want is to annotate the annotations that define the named queries, in such a way that you can programmatically discover what the "findByName" query is (and possible other queries).

Since this is not possible in Java, you could use the fact that @NamedQuery supports query hints, that are defined as being vendor specific. Unknown hints are ignored. You could add your own data here, that the generic DAO can read back from entityClass:

@NamedQuery(
    name="findCityByname",
    query="FROM CITY c WHERE name = :CityName",
    hints=@QueryHint(name="genericDAO.type", value="findByName")
)


来源:https://stackoverflow.com/questions/4886587/abstracting-named-queries-in-an-abstract-jpa-dao

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!