Абстрагирование именованных запросов в абстрактном JPA DAO

У меня есть абстрактный класс DAO, который использует параметризованные типы E (сущность) и K (первичный ключ). В каждой сущности у меня есть @NamedQuery. Я хочу динамически вызывать этот именованный запрос, не зная его точного имени и имени параметра.

В качестве примера представьте себе следующую сущность City

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

и это CityDao

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

Как мне реализовать метод findByName() в AbstractDao, чтобы мне не нужно было знать точное имя и имя параметра?

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

    // ...
}

person yoav.str    schedule 03.02.2011    source источник


Ответы (4)


Соглашение об именах для именованных запросов обычно <Entity Name>.findBy<PropertyAndAnotherProperty>, «City.findByName» в вашем примере, поэтому я бы попытался изменить именованные запросы, чтобы они следовали этому шаблону. Параметр этого запроса также должен иметь то же имя, или вы можете использовать позиционные параметры. Тогда ваш метод find превратится в

@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;
    }
}
person Jörn Horstmann    schedule 09.02.2011

Самый простой способ — передать имя запроса конструктору абстрактного DAO:

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

Затем определите общедоступную статическую конечную строку в City для хранения имени:

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

В качестве альтернативы вы можете объявить DaoAbstreact как абстрактный, а затем иметь в нем такой метод:

public abstract String getFindByNameQueryName();

И реализовать это в ConcreteCityDao.

Наконец, вы также можете ввести перечисление:

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

Затем ваш DAO может определить тип из переданного класса. Чтобы убедиться, что вы не забыли добавить сущность в перечисление, вы можете заставить каждую сущность реализовать интерфейс с помощью метода getNamedEntityType(). Затем вы можете указать, что ваш абстрактный универсальный DAO будет принимать только объекты, реализующие этот интерфейс.

person Russ Hayward    schedule 03.02.2011

Очевидным способом было бы передать значения из конкретных классов в абстрактный суперкласс, используя метод abstract

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

или как аргумент конструктора:

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

Хотя это требует согласованного именования параметров запроса для разных сущностей.

Также обратите внимание на незначительные улучшения в этих фрагментах.

person axtavt    schedule 03.02.2011

В основном вы хотите аннотировать аннотации, которые определяют именованные запросы, таким образом, чтобы вы могли программно обнаружить, что такое запрос «findByName» (и возможные другие запросы).

Поскольку это невозможно в Java, вы можете использовать тот факт, что @NamedQuery поддерживает подсказки запросов, которые определены как зависящие от поставщика. Неизвестные подсказки игнорируются. Вы можете добавить сюда свои собственные данные, которые общий DAO может считывать из entityClass:

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