Фильтрация объектов в поле версии с помощью API-критериев JPA

У меня есть объект со встроенным идентификатором, который содержит идентификатор и поле версии.

@Entity
@Table(name = "MyEntity")
public class MyEntity {
    @EmbeddedId
    private MyEntityEmbeddedId compositeId;

    @Column
    private DateTime begin;

    @Column
    private DateTime end;

}

@Embeddable
public class MyEntityEmbeddedId {
    @Column(name = "ID", nullable = false, updatable = false)
    private Long id;

    @Column(name = "VERSION", nullable = false, updatable = false)
    private Long version;

    public MyEntityEmbeddedId () {}

}

Мне нужно запросить базу данных с некоторыми критериями в качестве фильтра для сущности. Например, я буду запрашивать дату и список идентификаторов.

List<MyEntity> listEntities = getMyEntities(List<Long> ids, DateTime date);

Мне удается создать запрос, который получает все объекты, для которых их идентификатор находится в списке идентификаторов, и что указанная дата находится между свойствами начала и конца даты в MyEntity.

Но так как у этого объекта есть составной идентификатор, существует много MyEntity с таким же "идентификатором", который может соответствовать параметрам запроса. Я хочу добавить еще один фильтр для извлечения MyEntity с наибольшим номером "версии", если есть много с одинаковым "идентификатором".

Вот пример того, что может быть в базе данных:

+------+-----------+--------------------+
| id   | version   | began    |   end   |
+------+-----------+--------------------+
| 1    | 1         | ...      |   ...   |
| 1    | 2         | ...      |   ...   |
| 1    | 3         | ...      |   ...   |
| 2    | 1         | ...      |   ...   |
| 2    | 2         | ...      |   ...   |
+------+-----------+--------------------+

Если все предыдущие записи базы данных соответствуют параметрам фильтрации, мне нужно получить только эти, потому что они имеют наибольший номер версии для соответствующего идентификатора:

+------+-----------+--------------------+
| id   | version   | began    |   end   |
+------+-----------+--------------------+ 
| 1    | 3         | ...      |   ...   |
| 2    | 2         | ...      |   ...   |
+------+-----------+--------------------+

В настоящее время я делаю запрос, но фильтрую версию в методе внутри моего сервиса, но я бы предпочел, чтобы версия уже была отфильтрована в запросе базы данных. Мне не нужно извлекать записи, которые будут удалены после фильтрации версии.

Я ищу способ самостоятельного соединения с JPA Criteria-API.

По следующей ссылке кажется, что принятый ответ решит мою проблему, потому что мне интересно, как бы я перевел предложенный SQL в JPA Criteria-API.

SQL Выберите только строки с максимальным значением в столбце

select yt1.*
from yourtable yt1
left outer join yourtable yt2
on (yt1.id = yt2.id and yt1.rev < yt2.rev)
where yt2.id is null;

Моя проблема, когда я пытаюсь создать соединение. Вам необходимо предоставить свойство от первого объекта, который ссылается на второй объект в объединении. Поскольку я хочу сделать самостоятельное соединение, в моей сущности нет свойства, которое указывало бы на себя.

Join<MyEntity, MyEntity> selfJoin = root.join(MyEntity_.???);

Как видите, я использую статическую метамодель MyEntity. Есть ли способ сделать самостоятельное соединение с JPA Criteria-API?

Или, может быть, есть другой способ построить мой запрос, чем с помощью самостоятельного левого соединения, как было предложено в другом вопросе stackru.

Я использую Spring Data JPA Specification для построения моего запроса с помощью Predicate. Затем используется findAll(спецификация). У меня уже есть два других метода спецификации для генерации предиката для withIds() и withDate(). Это позволило мне создавать запросы с "неопределенным числом" параметров, предоставленных пользователем.

public static Specification<MyEntity> withIdAndDate(final List<Long> ids, 
        final DateTime date) {
    return new Specification<MyEntity>() {

        @Override
        public Predicate toPredicate(Root<MyEntity> root, CriteriaQuery<?> query, 
                CriteriaBuilder cb){
            Join<MyEntity, MyEntity> selfJoin = root.join();
            Predicate pIds = withIds(ids).toPredicate(root, query, cb);
            Predicate pDate = withDate(date).toPredicate(root, query, cb);

            return cb.and(pIds, pDate);
        }

    };
}

Большое спасибо за вашу помощь и предложение!

1 ответ

Решение

Наконец, мне удалось найти способ отфильтровать мою MyEntity до самой последней версии с помощью API JPA Criteria, поэтому я поделюсь с вами своим кодом, надеясь, что он кому-нибудь здесь поможет.

public static Specification<MyEntity> withIdAndDate(final List<Long> ids, 
        final DateTime date) {
    return new Specification<MyEntity>() {

        @Override
        public Predicate toPredicate(Root<MyEntity> root, CriteriaQuery<?> query, 
                CriteriaBuilder cb){

            Subquery<Long> subqueryVersion = query.subquery(Long.class);
            Root<MyEntity> entity1 = subqueryVersion.from(MyEntity.class);

            Expression<Long> expMaxVersion = cb.greatest(entity1
                .get(MyEntity_.compositeId).get(MyEntityEmbeddedId_.version));
            Expression<Long> expIdRoot = root.get(MyEntity_.compositeId)
                .get(MyEntityEmbeddedId_.id);
            Expression<Long> expIdEntity1 = entity1 .get(MyEntity_.compositeId)
                .get(MyEntityEmbeddedId_.id);
            Expression<Long> expVersionRoot = root.get(MyEntity_.compositeId)
                .get(MyEntityEmbeddedId_.version);

            Predicate pId = cb.equal(expIdRoot, expIdEntity1);
            Predicate pIds = withIds(ids).toPredicate(entity1, query, cb);
            Predicate pDate = withDate(date).toPredicate(entity1, query, cb);

            subqueryVersion.select(expMaxVersion);
            subqueryVersion.where(pId, pIds, pDate);

            Predicate pQuery = cb.equal(expVersionRoot, subqueryVersion);

            return cb.and(pQuery);
        }

    };
}

В следующей ссылке принятый ответ помогает мне построить запрос в JPQL, и после этого мне удается перевести его в JPA Criteria API. Я удаляю последнюю часть запроса, которая является частью "И НЕ СУЩЕСТВУЕТ". JPA Выберите последний экземпляр для каждого элемента

Другие вопросы по тегам