All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.moon.spring.data.jpa.factory.AbstractRepositoryImpl Maven / Gradle / Ivy

package com.moon.spring.data.jpa.factory;

import com.moon.core.util.ListUtil;
import com.moon.core.util.logger.Logger;
import com.moon.core.util.logger.LoggerUtil;
import com.moon.data.DataRecord;
import com.moon.data.registry.LayerEnum;
import com.moon.data.registry.LayerRegistry;
import com.moon.spring.data.jpa.JpaRecord;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.query.EscapeCharacter;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import static java.util.Optional.ofNullable;

/**
 * @author moonsky
 */
@SuppressWarnings("all")
@NoRepositoryBean
@Transactional(readOnly = true)
public abstract class AbstractRepositoryImpl, ID> extends SimpleJpaRepository
    implements JpaRecordRepository {

    protected final static Logger logger = LoggerUtil.getLogger();
    /**
     * 空字符串
     * 

* 枚举序列化后可能导致多系统不能互相引用 * * @see #isPlaceholder(Object) */ protected final static Serializable PLACEHOLDER = new byte[0]; /** * 是否是占位符 * * @param value * * @return * * @see #PLACEHOLDER */ protected static boolean isPlaceholder(Object value) { return value instanceof byte[] && ((byte[]) value).length == 0; } private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT; private final Function NULL_FN = o -> null; private final JpaRecordRepositoryMetadata metadata; private final CacheManager cacheManager; private final String cacheNamespace; private final Class domainClass; private final EntityManager em; public AbstractRepositoryImpl( RepositoryInformation repositoryInformation, JpaEntityInformation ei, EntityManager em, JpaRecordRepositoryMetadata metadata ) { super(ei, em); this.em = em; this.metadata = metadata; this.domainClass = ei.getJavaType(); LayerRegistry.registry(LayerEnum.REPOSITORY, domainClass, this); this.cacheManager = RecordCacheUtil.deduceCacheManager(metadata, domainClass, NoOpCacheManager.MANAGER); this.cacheNamespace = RecordCacheUtil.deduceCacheNamespace(metadata, domainClass, PLACEHOLDER); } @Override public void setEscapeCharacter(EscapeCharacter escapeCharacter) { this.escapeCharacter = escapeCharacter; } protected JpaRecordRepositoryMetadata getMetadata() { return metadata; } protected ApplicationContext getApplicationContext() { JpaRecordRepositoryMetadata metadata = getMetadata(); return metadata == null ? null : metadata.getApplicationContext(); } /* * ***************************************************************************************************************** * ***** save ************************************************************************************************** * ***************************************************************************************************************** */ @Override @Transactional public S insert(S entity) { if (entity.isNew()) { return this.save(entity); } else { em.persist(JpaIdentifierUtil.extractPresetPrimaryKey(entity)); ID id = entity.getId(); Cache cache = getCache(); cache.put(id, entity); onCachedEntity(cache, id, getDomainClass(), entity); return entity; } } @Override @Transactional public S save(S entity) { return doSaveEntity(entity); } @Override @Transactional public List saveAll(T first, T second, T... entities) { return saveAll(asList(first, second, entities)); } @Override @Transactional public List saveAll(Iterable entities) { List list = new ArrayList<>(); if (entities == null) { return list; } else { Cache cache = getCache(); for (S entity : entities) { list.add(doSaveEntity(entity, cache)); } } return list; } @Override @Transactional public S saveAndFlush(S entity) { S saved = doSaveEntity(entity); flush(); return saved; } /* * ***************************************************************************************************************** * ***** find one ************************************************************************************************** * ***************************************************************************************************************** */ @Override public Optional findById(ID s) { return ofNullable(findFromCacheById(s, NULL_FN)); } @Override public T getOrNull(ID id) { return findFromCacheById(id, NULL_FN); } @Override public T getById(ID id) { T value = findFromCacheById(id, NULL_FN); if (value == null) { throw new NullPointerException("数据不存在: " + id); } else { return value; } } @Override public T getById(ID id, String throwsMessageIfAbsent) { T value = findFromCacheById(id, NULL_FN); if (value == null) { throw new NullPointerException(throwsMessageIfAbsent); } else { return value; } } @Override public T getById(ID id, Supplier throwIfAbsent) throws X { T value = findFromCacheById(id, NULL_FN); if (value == null) { throw throwIfAbsent.get(); } else { return value; } } /* * ***************************************************************************************************************** * ***** slice ***************************************************************************************************** * ***************************************************************************************************************** */ @Override public Slice sliceAll(Pageable pageable) { return pageable.isUnpaged() ? new SliceImpl<>(findAll()) : readSlicedEntities(getQuery(null, pageable), pageable); } @Override public Slice sliceAll(Example example, Pageable pageable) { ExampleSpecification spec = new ExampleSpecification<>(example, escapeCharacter); TypedQuery query = getQuery(spec, example.getProbeType(), pageable); return pageable.isUnpaged() ? new SliceImpl<>(query.getResultList()) : readSlicedEntities(query, pageable); } protected Slice readSlicedEntities(TypedQuery query, Pageable pageable) { query.setFirstResult((int) pageable.getOffset()); query.setMaxResults(pageable.getPageSize()); List content = query.getResultList(); return new SliceImpl<>(content, pageable, content.size() >= pageable.getPageSize()); } private static int guessSize(Iterable iter) { return iter == null ? 0 : (iter instanceof Collection ? ((Collection) iter).size() : 16); } /* * ***************************************************************************************************************** * ***** find all ************************************************************************************************** * ***************************************************************************************************************** */ @Override public List findAllById(ID first, ID second, ID... ids) { return findAllById(asList(first, second, ids)); } @Override public List findAllById(Iterable strings) { List results = new ArrayList(guessSize(strings)); Cache cache = getCache(); for (ID identity : strings) { results.add(findFromCacheById(identity, NULL_FN, cache)); } return results; } /* * ***************************************************************************************************************** * ***** disable *************************************************************************************************** * ***************************************************************************************************************** */ @Override @Transactional public void disableById(ID id) { findById(id).ifPresent(this::doDisableEntity); } @Override @Transactional public void disable(T entity) { doDisableEntity(entity); } @Override @Transactional public void disableAll(Iterable entities) { if (entities != null) { Cache cache = getCache(); for (T entity : entities) { doDisableEntity(entity, cache); } } } @Override @Transactional public void disableAll(S first, S second, S... entities) { Cache cache = getCache(); doDisableEntity(first, cache); doDisableEntity(second, cache); if (entities != null) { for (S entity : entities) { doDisableEntity(entity, cache); } } } /* * ***************************************************************************************************************** * ***** delete **************************************************************************************************** * ***************************************************************************************************************** */ @Override @Transactional public void delete(T entity) { super.delete(entity); doEvictCached(entity.getId()); } @Override @Transactional public void deleteById(ID s) { super.deleteById(s); doEvictCached(s); } @Override @Transactional public void deleteAll(Iterable entities) { if (entities != null) { Cache cache = getCache(); for (T entity : entities) { super.delete(entity); doEvictCached(entity.getId(), domainClass, cache); } } } @Override @Transactional public void deleteAll(T first, T second, T... entities) { deleteAll(asList(first, second, entities)); } @Override @Transactional public void deleteAll() { super.deleteAll(); getCache().clear(); } @Override @Transactional public void deleteAllInBatch() { super.deleteAllInBatch(); getCache().clear(); } @Override @Transactional public void deleteInBatch(Iterable entities) { super.deleteInBatch(entities); Cache cache = getCache(); Class domainClass = getDomainClass(); for (T entity : entities) { doEvictCached(entity.getId(), domainClass, cache); } } /* * ***************************************************************************************************************** * ***** getCache ************************************************************************************************** * ***************************************************************************************************************** */ protected Cache getCache() { return getCache(cacheNamespace); } protected CacheManager getCacheManager() { return cacheManager; } protected Cache getCache(String namespace) { return getCacheManager().getCache(namespace); } /* * ***************************************************************************************************************** * ***** doEvictCached ********************************************************************************************* * ***************************************************************************************************************** */ protected void doEvictCached(ID id) { doEvictCached(id, getDomainClass(), true, getCache()); } protected void doEvictCached(ID id, boolean triggerEvictEvent) { doEvictCached(id, getDomainClass(), triggerEvictEvent, getCache()); } protected void doEvictCached(ID id, Cache cache) { doEvictCached(id, true, cache); } protected void doEvictCached(ID id, boolean triggerEvictEvent, Cache cache) { doEvictCached(id, getDomainClass(), triggerEvictEvent, cache); } protected void doEvictCached(ID id, Class domainClass, Cache cache) { doEvictCached(id, domainClass, true, cache); } protected void doEvictCached(ID id, Class domainClass, boolean triggerEvictEventEvent, Cache cache) { cache.evict(id); if (triggerEvictEventEvent) { onCacheEvicted(cache, id, domainClass); } } /* * ***************************************************************************************************************** * ***** doSaveEntity ********************************************************************************************** * ***************************************************************************************************************** */ protected S doSaveEntity(S s) { return doSaveEntity(s, true); } protected S doSaveEntity(S s, boolean triggerEvictEvent) { return doSaveEntity(s, triggerEvictEvent, getCache()); } protected S doSaveEntity(S s, Cache cache) { return doSaveEntity(s, true, cache); } protected S doSaveEntity(S s, boolean triggerEvictEvent, Cache cache) { ID beforeSaveId = s.getId(); boolean newer = s.isNew(); if (!newer) { // 在更新前删除一次,防止并发下删除异常,(在某些情况下,可考虑不要这一步) cache.evict(beforeSaveId); } s = super.save(s); if (newer) { // 新数据直接缓存 ID savedId = s.getId(); cache.put(savedId, s); onCachedEntity(cache, savedId, getDomainClass(), s); } else { // 更新后再次删除,防止缓存了历史数据 doEvictCached(beforeSaveId, triggerEvictEvent, cache); } return s; } /* * ***************************************************************************************************************** * ***** doDisableEntity ******************************************************************************************* * ***************************************************************************************************************** */ protected void doDisableEntity(S entity) { doDisableEntity(entity, getCache()); } protected void doDisableEntity(S entity, Cache cache) { if (entity instanceof DataRecord) { ((DataRecord) entity).withUnavailable(); ID id = entity.getId(); cache.evict(id); doSaveEntity(entity, false); onDisableEntity(cache, id, getDomainClass(), entity); } else { delete(entity); } } /* * ***************************************************************************************************************** * ***** findFromCacheById ***************************************************************************************** * ***************************************************************************************************************** */ protected T findFromCacheById(ID id, Function convertIdIfAbsent) { return findFromCacheById(id, convertIdIfAbsent, getCache()); } protected T findFromCacheById(ID id, Function convertIdIfAbsent, Cache cache) { Cache.ValueWrapper wrapper = cache.get(id); Object value = wrapper == null ? null : wrapper.get(); if (value == null) { // 不存在缓存 value = findFromDatabaseById(id); if (value == null) { // 不存在数据,缓存占位符,并返回 null cache.put(id, PLACEHOLDER); return null; } else { // 存在合法数据,缓存后返回 cache.put(id, value); onCachedEntity(cache, id, getDomainClass(), value); return (T) value; } } else if (isPlaceholder(value)) { // 缓存过一个不存在的值 Object converted = convertIdIfAbsent.apply(id); if (converted == null) { return null; } else if (converted instanceof RuntimeException) { throw (RuntimeException) converted; } else if (converted instanceof Error) { throw (Error) converted; } else if (converted instanceof Throwable) { throw new IllegalStateException((String) converted); } return (T) converted; } else { // 命中缓存 return (T) value; } } /* * ***************************************************************************************************************** * ***** findFromDatabaseById ************************************************************************************** * ***************************************************************************************************************** */ final T findFromDatabaseById(ID id) { if (id == null) { throw new IllegalArgumentException("The given id must not be null!"); } return em.find(getDomainClass(), id); } protected static List asList(T first, T second, T... rest) { return ListUtil.addAll(ListUtil.newList(first, second), rest); } /* * ***************************************************************************************************************** * ***** trigger events ******************************************************************************************** * ***************************************************************************************************************** */ /** * 放入缓存后的操作 * * @param cache * @param id * @param domainClass * @param entity */ protected void onCachedEntity(Cache cache, ID id, Class domainClass, Object entity) {} /** * 删除缓存后的操作 * * @param cache * @param id * @param domainClass */ protected void onCacheEvicted(Cache cache, ID id, Class domainClass) {} /** * 禁用后的操作 *

* 禁用不会触发删除缓存事件 * * @param cache * @param id * @param domainClass */ protected void onDisableEntity(Cache cache, ID id, Class domainClass, Object entity) {} /* * ***************************************************************************************************************** * ***** inner classes ********************************************************************************************* * ***************************************************************************************************************** */ private static class ExampleSpecification implements Specification { private static final long serialVersionUID = 1L; private final EscapeCharacter escapeCharacter; private final Example example; ExampleSpecification(Example example, EscapeCharacter escapeCharacter) { Assert.notNull(escapeCharacter, "EscapeCharacter must not be null!"); Assert.notNull(example, "Example must not be null!"); this.escapeCharacter = escapeCharacter; this.example = example; } @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return QueryByExamplePredicateBuilder.getPredicate(root, cb, example, escapeCharacter); } } private static class NoOpCacheManager implements CacheManager { private final static NoOpCacheManager MANAGER = new NoOpCacheManager(); @Override public Cache getCache(String name) { return NoOpCache.CACHE; } @Override public Collection getCacheNames() { return NoOpSet.SET; } } private static class NoOpCache implements Cache { private final static NoOpCache CACHE = new NoOpCache(); @Override public String getName() { return null; } @Override public Object getNativeCache() { return null; } @Override public ValueWrapper get(Object key) { return null; } @Override public T get(Object key, Class type) { return null; } @Override public T get(Object key, Callable valueLoader) { return null; } @Override public void put(Object key, Object value) { } @Override public void evict(Object key) { } @Override public void clear() { } } private static class NoOpSet implements Set { private final static NoOpSet SET = new NoOpSet(); private final static Object[] OBJECTS = new Object[0]; @Override public int size() { return 0; } @Override public boolean isEmpty() { return true; } @Override public boolean contains(Object o) { return false; } @Override public Iterator iterator() { return NoOpItr.ITR; } @Override public Object[] toArray() { return OBJECTS; } @Override public T1[] toArray(T1[] a) { return a; } @Override public boolean add(T t) { return false; } @Override public boolean remove(Object o) { return false; } @Override public boolean containsAll(Collection c) { return false; } @Override public boolean addAll(Collection c) { return false; } @Override public boolean retainAll(Collection c) { return false; } @Override public boolean removeAll(Collection c) { return false; } @Override public void clear() { } @Override public boolean removeIf(java.util.function.Predicate filter) { return false; } @Override public void forEach(Consumer action) { } } private static class NoOpItr implements Iterator { private final static NoOpItr ITR = new NoOpItr(); @Override public boolean hasNext() { return false; } @Override public Object next() { return null; } @Override public void remove() { } @Override public void forEachRemaining(Consumer action) { } } }