From 627836b8fd6d9a6f1ce59331d8cf35156cb64983 Mon Sep 17 00:00:00 2001 From: Moritz Becker Date: Fri, 21 Apr 2017 10:51:30 +0200 Subject: [PATCH] fixed #351 --- .../EntityViewAwareRepositoryFactory.java | 2 +- .../repository/EntityViewRepositoryImpl.java | 196 ++++++++++++++---- .../EntityViewSpecificationExecutor.java | 43 ++++ .../repository/DocumentRepository.java | 3 +- 4 files changed, 206 insertions(+), 38 deletions(-) create mode 100644 integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewSpecificationExecutor.java diff --git a/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewAwareRepositoryFactory.java b/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewAwareRepositoryFactory.java index f0f1e7413f..e156059c01 100644 --- a/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewAwareRepositoryFactory.java +++ b/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewAwareRepositoryFactory.java @@ -71,7 +71,7 @@ public JpaEntityInformation getEntityInforma protected Object getTargetRepository(RepositoryInformation information) { if (isEntityView(information.getDomainType())) { JpaEntityInformation entityInformation = getEntityInformation(information.getDomainType()); - EntityViewRepositoryImpl entityViewRepository = getTargetRepositoryViaReflection(information, entityInformation, entityManager, cbf, evm, information.getDomainType()); + EntityViewRepositoryImpl entityViewRepository = getTargetRepositoryViaReflection(information, entityInformation, entityManager, cbf, evm, information.getDomainType()); return entityViewRepository; } else { return super.getTargetRepository(information); diff --git a/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewRepositoryImpl.java b/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewRepositoryImpl.java index 1365cc61d0..4342f99deb 100644 --- a/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewRepositoryImpl.java +++ b/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewRepositoryImpl.java @@ -18,18 +18,36 @@ import com.blazebit.persistence.CriteriaBuilder; import com.blazebit.persistence.CriteriaBuilderFactory; +import com.blazebit.persistence.PagedList; +import com.blazebit.persistence.criteria.BlazeCriteriaBuilder; +import com.blazebit.persistence.criteria.BlazeCriteriaQuery; +import com.blazebit.persistence.criteria.impl.BlazeCriteria; import com.blazebit.persistence.view.EntityViewManager; import com.blazebit.persistence.view.EntityViewSetting; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.query.QueryUtils; import org.springframework.data.jpa.repository.support.CrudMethodMetadata; import org.springframework.data.jpa.repository.support.JpaEntityInformation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import javax.persistence.EntityManager; +import javax.persistence.LockModeType; +import javax.persistence.NoResultException; +import javax.persistence.Query; import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Order; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; import javax.persistence.metamodel.EntityType; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,24 +57,26 @@ * @since 1.2 */ @Transactional(readOnly = true) -public class EntityViewRepositoryImpl implements EntityViewRepository { +public class EntityViewRepositoryImpl implements EntityViewRepository, EntityViewSpecificationExecutor { private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!"; - private final JpaEntityInformation entityInformation; + private final JpaEntityInformation entityInformation; private final EntityManager entityManager; private final CriteriaBuilderFactory cbf; private final EntityViewManager evm; - private final EntityViewSetting setting; + private final EntityViewSetting> setting; + private final Class entityViewClass; private CrudMethodMetadata metadata; - public EntityViewRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager, CriteriaBuilderFactory cbf, EntityViewManager evm, Class entityViewClass) { + public EntityViewRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager, CriteriaBuilderFactory cbf, EntityViewManager evm, Class entityViewClass) { this.entityInformation = entityInformation; this.entityManager = entityManager; this.cbf = cbf; this.evm = evm; this.setting = EntityViewSetting.create(entityViewClass); + this.entityViewClass = entityViewClass; } public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) { @@ -67,11 +87,14 @@ protected CrudMethodMetadata getRepositoryMethodMetadata() { return metadata; } - protected Class getDomainClass() { + protected Class getDomainClass() { return entityInformation.getJavaType(); } protected Map getQueryHints() { + if (metadata == null) { + return Collections.emptyMap(); + } if (metadata.getEntityGraph() == null) { return metadata.getQueryHints(); @@ -87,30 +110,21 @@ protected Map getQueryHints() { * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable) */ - public T findOne(ID id) { + public V findOne(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); CriteriaBuilder cb = cbf.create(entityManager, getDomainClass()) .where(getIdAttribute()).eq(id); - TypedQuery findOneQuery = evm.applySetting(setting, cb).getQuery(); + TypedQuery findOneQuery = evm.applySetting(setting, cb).getQuery(); - if (metadata != null) { - applyQueryHints(getQueryHints(), findOneQuery); - } + applyQueryHints(findOneQuery); return findOneQuery.getSingleResult(); } @Override public long count() { - TypedQuery countQuery = cbf.create(entityManager, Long.class) - .from(getDomainClass()) - .select("COUNT(*)") - .getQuery(); - - if (metadata != null) { - applyQueryHints(getQueryHints(), countQuery); - } + TypedQuery countQuery = getCountQuery(null); return countQuery.getSingleResult(); } @@ -124,26 +138,18 @@ public boolean exists(ID id) { .where(getIdAttribute()).eq(id) .getQuery(); - if (metadata != null) { - applyQueryHints(getQueryHints(), existsQuery); - } + applyRepositoryMethodMetadata(existsQuery); return existsQuery.getSingleResult() > 0; } @Override - public Iterable findAll() { - CriteriaBuilder cb = cbf.create(entityManager, getDomainClass()); - TypedQuery findAllQuery = evm.applySetting(setting, cb).getQuery(); - - if (metadata != null) { - applyQueryHints(getQueryHints(), findAllQuery); - } - return findAllQuery.getResultList(); + public Iterable findAll() { + return getQuery(null, null, null).getResultList(); } @Override - public Iterable findAll(Iterable idIterable) { + public Iterable findAll(Iterable idIterable) { Assert.notNull(idIterable, ID_MUST_NOT_BE_NULL); List idList = new ArrayList<>(); @@ -152,11 +158,10 @@ public Iterable findAll(Iterable idIterable) { } CriteriaBuilder cb = cbf.create(entityManager, getDomainClass()) .where(getIdAttribute()).in(idList); - TypedQuery findAllByIdsQuery = evm.applySetting(setting, cb).getQuery(); + TypedQuery findAllByIdsQuery = evm.applySetting(setting, cb).getQuery(); + + applyRepositoryMethodMetadata(findAllByIdsQuery); - if (metadata != null) { - applyQueryHints(getQueryHints(), findAllByIdsQuery); - } return findAllByIdsQuery.getResultList(); } @@ -169,9 +174,128 @@ private String getIdAttribute() { return getIdAttribute(getDomainClass()); } - private void applyQueryHints(Map hints, TypedQuery query) { - for (Map.Entry hint : hints.entrySet()) { + @Override + public V findOne(Specification spec) { + try { + return getQuery(spec, (Sort) null).getSingleResult(); + } catch (NoResultException e) { + return null; + } + } + + @Override + public List findAll(Specification spec) { + return getQuery(spec, (Sort) null).getResultList(); + } + + @Override + public Page findAll(Specification spec, Pageable pageable) { + TypedQuery query = getQuery(spec, pageable); + PagedList content = (PagedList) query.getResultList(); + return new PageImpl(content, pageable, content.getTotalSize()); + } + + @Override + public List findAll(Specification spec, Sort sort) { + return getQuery(spec, sort).getResultList(); + } + + @Override + public long count(Specification spec) { + return executeCountQuery(getCountQuery(spec)); + } + + protected TypedQuery getQuery(Specification spec, Pageable pageable) { + Sort sort = pageable == null ? null : pageable.getSort(); + return this.getQuery(spec, pageable, sort); + } + + protected TypedQuery getQuery(Specification spec, Sort sort) { + return this.getQuery(spec, null, sort); + } + + protected TypedQuery getQuery(Specification spec, Pageable pageable, Sort sort) { + Class domainClass = getDomainClass(); + BlazeCriteriaQuery cq = BlazeCriteria.get(entityManager, cbf, domainClass); + Root root = this.applySpecificationToCriteria(spec, domainClass, cq); + + if (sort != null) { + cq.orderBy(QueryUtils.toOrders(sort, root, BlazeCriteria.get(entityManager, cbf))); + } + EntityViewSetting setting; + if (pageable == null) { + setting = this.setting; + } else { + setting = EntityViewSetting.create(entityViewClass, pageable.getOffset(), pageable.getPageSize()); + } + TypedQuery query = evm.applySetting(setting, cq.createCriteriaBuilder()).getQuery(); + + return this.applyRepositoryMethodMetadata(query); + } + + protected TypedQuery getCountQuery(Specification spec) { + BlazeCriteriaBuilder builder = BlazeCriteria.get(entityManager, cbf); + BlazeCriteriaQuery query = builder.createQuery(Long.class); + + Root root = applySpecificationToCriteria(spec, getDomainClass(), query); + + if (query.isDistinct()) { + query.select(builder.countDistinct(root)); + } else { + query.select(builder.count(root)); + } + + // Remove all Orders the Specifications might have applied + query.orderBy(Collections. emptyList()); + + return this.applyRepositoryMethodMetadata(query.getQuery()); + } + + private Root applySpecificationToCriteria(Specification spec, Class domainClass, CriteriaQuery query) { + Assert.notNull(domainClass, "Domain class must not be null!"); + Assert.notNull(query, "CriteriaQuery must not be null!"); + Root root = query.from(domainClass); + if (spec == null) { + return root; + } else { + javax.persistence.criteria.CriteriaBuilder builder = BlazeCriteria.get(entityManager, cbf); + Predicate predicate = spec.toPredicate(root, query, builder); + if (predicate != null) { + query.where(predicate); + } + + return root; + } + } + + private TypedQuery applyRepositoryMethodMetadata(TypedQuery query) { + if (this.metadata == null) { + return query; + } else { + LockModeType type = this.metadata.getLockModeType(); + TypedQuery toReturn = type == null ? query : query.setLockMode(type); + this.applyQueryHints(toReturn); + return toReturn; + } + } + + private void applyQueryHints(Query query) { + for (Map.Entry hint : getQueryHints().entrySet()) { query.setHint(hint.getKey(), hint.getValue()); } } + + private static Long executeCountQuery(TypedQuery query) { + + Assert.notNull(query, "TypedQuery must not be null!"); + + List totals = query.getResultList(); + Long total = 0L; + + for (Long element : totals) { + total += element == null ? 0 : element; + } + + return total; + } } diff --git a/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewSpecificationExecutor.java b/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewSpecificationExecutor.java new file mode 100644 index 0000000000..6cd3b24cf9 --- /dev/null +++ b/integration/spring-data/src/main/java/com/blazebit/persistence/impl/springdata/repository/EntityViewSpecificationExecutor.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 - 2017 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.impl.springdata.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; + +import java.util.List; + +/** + * Like {@link org.springframework.data.jpa.repository.JpaSpecificationExecutor} but allows to specify an entity view + * return type. + * + * @author Moritz Becker (moritz.becker@gmx.at) + * @since 1.2 + */ +public interface EntityViewSpecificationExecutor { + V findOne(Specification var1); + + List findAll(Specification var1); + + Page findAll(Specification var1, Pageable var2); + + List findAll(Specification var1, Sort var2); + + long count(Specification var1); +} diff --git a/integration/spring-data/src/test/java/com/blazebit/persistence/impl/springdata/repository/DocumentRepository.java b/integration/spring-data/src/test/java/com/blazebit/persistence/impl/springdata/repository/DocumentRepository.java index 329dd4f6b4..813523f296 100644 --- a/integration/spring-data/src/test/java/com/blazebit/persistence/impl/springdata/repository/DocumentRepository.java +++ b/integration/spring-data/src/test/java/com/blazebit/persistence/impl/springdata/repository/DocumentRepository.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.impl.springdata.repository; +import com.blazebit.persistence.impl.springdata.entity.Document; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -31,7 +32,7 @@ */ @Transactional(readOnly = true) @NoRepositoryBean -public interface DocumentRepository extends EntityViewRepository { +public interface DocumentRepository extends EntityViewRepository, EntityViewSpecificationExecutor { List findByName(String name);