Абстрагирование именованных запросов в абстрактном 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)


arrow_upward
13
arrow_downward

Соглашение об именах для именованных запросов обычно <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

arrow_upward
1
arrow_downward

Самый простой способ — передать имя запроса конструктору абстрактного 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

arrow_upward
0
arrow_downward

Очевидным способом было бы передать значения из конкретных классов в абстрактный суперкласс, используя метод 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

arrow_upward
0
arrow_downward

В основном вы хотите аннотировать аннотации, которые определяют именованные запросы, таким образом, чтобы вы могли программно обнаружить, что такое запрос «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