diff --git a/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/entiy/BaseEntity.java b/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/entity/BaseEntity.java similarity index 94% rename from easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/entiy/BaseEntity.java rename to easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/entity/BaseEntity.java index c7a8dc0..7db68d5 100644 --- a/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/entiy/BaseEntity.java +++ b/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/entity/BaseEntity.java @@ -1,4 +1,4 @@ -package cn.org.easysite.spring.boot.jpa.entiy; +package cn.org.easysite.spring.boot.jpa.entity; import java.util.Date; @@ -7,6 +7,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; +import javax.persistence.OrderBy; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -32,6 +33,7 @@ public class BaseEntity extends BaseObject { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", columnDefinition = "bigint(20) COMMENT '主键ID,自动生成'") + @OrderBy("DESC") private Long id; /** diff --git a/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/factory/bean/LogicJpaRepositoryFactoryBean.java b/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/factory/bean/LogicJpaRepositoryFactoryBean.java new file mode 100644 index 0000000..3004c69 --- /dev/null +++ b/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/factory/bean/LogicJpaRepositoryFactoryBean.java @@ -0,0 +1,66 @@ +package cn.org.easysite.spring.boot.jpa.factory.bean; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; +import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation; +import org.springframework.data.jpa.repository.support.LogicJpaRepositoryImpl; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; + +import java.io.Serializable; + +import javax.persistence.EntityManager; + +import cn.org.easysite.spring.boot.jpa.entity.BaseEntity; + +/** + * @author : yinlin + * @version : 1.0 + * @date : 2019-05-16 17:44 + * @Description : + * @Copyright : Copyright (c) 2018 + * @Company : EasySite Technology Chengdu Co. Ltd. + * @link : cn.org.easysite.spring.boot.jpa.factory.bean.LogicJpaRespositoryFactoryBean + */ +public class LogicJpaRepositoryFactoryBean, T extends BaseEntity, ID extends Serializable> extends JpaRepositoryFactoryBean { + /** + * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + public LogicJpaRepositoryFactoryBean(Class repositoryInterface) { + super(repositoryInterface); + } + + @Override + protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { + return new LogicRepositoryFactory(entityManager); + } + + public static class LogicRepositoryFactory extends JpaRepositoryFactory { + + private final EntityManager em; + /** + * Creates a new {@link JpaRepositoryFactory}. + * + * @param entityManager must not be {@literal null} + */ + public LogicRepositoryFactory(EntityManager entityManager) { + super(entityManager); + this.em = entityManager; + } + + @Override + protected JpaRepositoryImplementation getTargetRepository(RepositoryInformation information, EntityManager entityManager) { + return new LogicJpaRepositoryImpl(JpaEntityInformationSupport.getEntityInformation((Class) information.getDomainType(), em), entityManager); + } + + @Override + protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { + return LogicJpaRepositoryImpl.class; + } + } +} diff --git a/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/factory/package-info.java b/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/factory/package-info.java new file mode 100644 index 0000000..def43aa --- /dev/null +++ b/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/factory/package-info.java @@ -0,0 +1,10 @@ +/** + * @author : yinlin + * @date : 2019-05-16 17:44 + * @version : 1.0 + * @Description : + * @Copyright : Copyright (c) 2018 + * @Company : EasySite Technology Chengdu Co. Ltd. + * @link : cn.org.easysite.spring.boot.jpa.factory.package-info + */ +package cn.org.easysite.spring.boot.jpa.factory; diff --git a/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/repository/LogicJpaRepository.java b/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/repository/LogicJpaRepository.java new file mode 100644 index 0000000..692f2e2 --- /dev/null +++ b/easy-spring-boot-data-jpa/src/main/java/cn/org/easysite/spring/boot/jpa/repository/LogicJpaRepository.java @@ -0,0 +1,22 @@ +package cn.org.easysite.spring.boot.jpa.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +import java.io.Serializable; + +import cn.org.easysite.spring.boot.jpa.entity.BaseEntity; + +/** + * @author : yinlin + * @version : 1.0 + * @date : 2019-04-30 10:06 + * @Description : + * @Copyright : Copyright (c) 2018 + * @Company : EasySite Technology Chengdu Co. Ltd. + * @link : cn.org.easysite.spring.boot.jpa.repository.LogicJpaRepository + */ +@NoRepositoryBean +public interface LogicJpaRepository extends JpaRepository { + +} diff --git a/easy-spring-boot-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/LogicJpaRepositoryImpl.java b/easy-spring-boot-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/LogicJpaRepositoryImpl.java new file mode 100644 index 0000000..0a81878 --- /dev/null +++ b/easy-spring-boot-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/LogicJpaRepositoryImpl.java @@ -0,0 +1,228 @@ +package org.springframework.data.jpa.repository.support; + +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.lang.Nullable; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; + +import javax.persistence.EntityManager; +import javax.persistence.LockModeType; +import javax.persistence.Query; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Order; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import cn.org.easysite.spring.boot.jpa.entity.BaseEntity; +import cn.org.easysite.spring.boot.jpa.repository.LogicJpaRepository; + +import static org.springframework.data.jpa.repository.query.QueryUtils.applyAndBind; +import static org.springframework.data.jpa.repository.query.QueryUtils.getQueryString; +import static org.springframework.data.jpa.repository.query.QueryUtils.toOrders; + +/** + * @author : yinlin + * @version : 1.0 + * @date : 2019-04-30 10:10 + * @Description : + * @Copyright : Copyright (c) 2018 + * @Company : EasySite Technology Chengdu Co. Ltd. + * @link : cn.org.easysite.spring.boot.jpa.repository.LogicJpaRepositoryImpl + */ +public class LogicJpaRepositoryImpl extends SimpleJpaRepository implements LogicJpaRepository { + + /** + * 逻辑删除字段名. + */ + public final static String USABLE_FIELD = "usable"; + + + public static final String COUNT_QUERY_STRING = "select count(%s) from %s x where x.usable = true"; + + public static final String EXISTS_QUERY_STRING = "select count(%s) from %s x where x.%s = :id and x.usable = true"; + + public static final String DELETE_ALL_QUERY_STRING = "update %s x set s.usable = false"; + + private final JpaEntityInformation entityInformation; + + private final EntityManager em; + + private final PersistenceProvider provider; + + private @Nullable CrudMethodMetadata metadata; + + public LogicJpaRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { + super(entityInformation, entityManager); + this.em = entityManager; + this.entityInformation = entityInformation; + this.provider = PersistenceProvider.fromEntityManager(entityManager); + } + + /** + * Creates a {@link TypedQuery} for the given {@link Specification} and {@link Sort}. + * + * @param spec can be {@literal null}. + * @param domainClass must not be {@literal null}. + * @param sort must not be {@literal null}. + * @return + */ + @Override + public TypedQuery getQuery(@Nullable Specification spec, Class domainClass, Sort sort) { + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(domainClass); + + Root root = applySpecificationToCriteria(spec, domainClass, query); + query.select(root); + + if (sort.isSorted()) { + query.orderBy(toOrders(sort, root, builder)); + } + return applyRepositoryMethodMetadata(em.createQuery(query)); + } + + /** + * Creates a new count query for the given {@link Specification}. + * + * @param spec can be {@literal null}. + * @param domainClass must not be {@literal null}. + * @return + */ + @Override + protected TypedQuery getCountQuery(@Nullable Specification spec, Class domainClass) { + + CriteriaBuilder builder = em.getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(Long.class); + + Root root = applySpecificationToCriteria(spec, domainClass, 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 em.createQuery(query); + } + + protected TypedQuery applyRepositoryMethodMetadata(TypedQuery query) { + + if (metadata == null) { + return query; + } + + LockModeType type = metadata.getLockModeType(); + TypedQuery toReturn = type == null ? query : query.setLockMode(type); + + applyQueryHints(toReturn); + return toReturn; + } + + private void applyQueryHints(Query query) { + + for (Map.Entry hint : getQueryHints().withFetchGraphs(em)) { + query.setHint(hint.getKey(), hint.getValue()); + } + } + + /** + * Applies the given {@link Specification} to the given {@link CriteriaQuery}. + * + * @param spec can be {@literal null}. + * @param domainClass must not be {@literal null}. + * @param query must not be {@literal null}. + * @return + */ + protected Root applySpecificationToCriteria(@Nullable 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); + CriteriaBuilder builder = em.getCriteriaBuilder(); + Path deletedPath = root.get(USABLE_FIELD); + Predicate deletedPredicate = builder.isTrue(deletedPath); + if (spec == null) { + query.where(deletedPath); + return root; + } + + Predicate predicate = spec.toPredicate(root, query, builder); + + if (predicate != null) { + predicate = builder.and(deletedPredicate); + query.where(predicate); + } + + return root; + } + + @Override + public long count() { + return em.createQuery(getCountQueryString(), Long.class).getSingleResult(); + } + + private String getCountQueryString() { + String countQuery = String.format(COUNT_QUERY_STRING, provider.getCountQueryPlaceholder(), "%s"); + return getQueryString(countQuery, entityInformation.getEntityName()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(T entity) { + Assert.notNull(entity, "The entity must not be null!"); + entity.setUsable(false); + em.persist(entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.JpaRepository#deleteInBatch(java.lang.Iterable) + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void deleteInBatch(Iterable entities) { + + Assert.notNull(entities, "The given Iterable of entities not be null!"); + + if (!entities.iterator().hasNext()) { + return; + } + + applyAndBind(getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()), entities, em) + .executeUpdate(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.JpaRepository#deleteAllInBatch() + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void deleteAllInBatch() { + em.createQuery(getDeleteAllQueryString()).executeUpdate(); + } + + protected String getDeleteAllQueryString() { + return getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()); + } + + @Override + public T getOne(ID id) { + T t = super.getOne(id); + return t.getUsable() ? t : null; + } +} diff --git a/easy-spring-boot-starters/easy-spring-boot-starter-data-jpa/src/main/java/cn/org/easysite/spring/boot/data/jpa/autoconfigure/LogicSpringDataJpaAutoConfiguration.java b/easy-spring-boot-starters/easy-spring-boot-starter-data-jpa/src/main/java/cn/org/easysite/spring/boot/data/jpa/autoconfigure/LogicSpringDataJpaAutoConfiguration.java new file mode 100644 index 0000000..0f83278 --- /dev/null +++ b/easy-spring-boot-starters/easy-spring-boot-starter-data-jpa/src/main/java/cn/org/easysite/spring/boot/data/jpa/autoconfigure/LogicSpringDataJpaAutoConfiguration.java @@ -0,0 +1,22 @@ +package cn.org.easysite.spring.boot.data.jpa.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import cn.org.easysite.spring.boot.jpa.factory.bean.LogicJpaRepositoryFactoryBean; + +/** + * @author : yinlin + * @version : 1.0 + * @date : 2019-05-16 18:14 + * @Description : + * @Copyright : Copyright (c) 2018 + * @Company : EasySite Technology Chengdu Co. Ltd. + * @link : cn.org.easysite.spring.boot.data.jpa.autoconfigure.LogicSpringDataJpaAutoConfiguration + */ +@Configuration +@ConditionalOnProperty(prefix = "spring.data.jpa.logic.repositories", name = "enabled", havingValue = "true", matchIfMissing = true) +@EnableJpaRepositories(repositoryBaseClass = LogicJpaRepositoryFactoryBean.class) +public class LogicSpringDataJpaAutoConfiguration { +} diff --git a/easy-spring-boot-starters/easy-spring-boot-starter-data-jpa/src/main/resources/META-INF/spring.factories b/easy-spring-boot-starters/easy-spring-boot-starter-data-jpa/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..6fc608d --- /dev/null +++ b/easy-spring-boot-starters/easy-spring-boot-starter-data-jpa/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +cn.org.easysite.spring.boot.data.jpa.autoconfigure.LogicSpringDataJpaAutoConfiguration diff --git a/easy-spring-boot-starters/easy-spring-boot-starter-data-jpa/src/main/resources/META-INF/spring.provides b/easy-spring-boot-starters/easy-spring-boot-starter-data-jpa/src/main/resources/META-INF/spring.provides new file mode 100644 index 0000000..0ddae05 --- /dev/null +++ b/easy-spring-boot-starters/easy-spring-boot-starter-data-jpa/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: easy-spring-boot-distributed-lock