Spring Data 是个好东西,极大简化了后端dao的操作,只需要在 dao 接口写个 findByXXX 的方法就能自动实现按条件查询这个简直太爽了。
不过问题也出现了,我的应用对于数据的操作没有物理删除,全是逻辑删除,也就是每个表都有个字段 deleted,1表示此记录已删除,默认值为 0 。这就与 spring data 提供的模式有冲突了,那剩下的就是:改之。
CRUD 操作
对于基础的CRUD 操作搞起来比较简单,按照其官方文档重新实现个 factory-class 就ok了,具体的 repository 类可以继承 org.springframework.data.jpa.repository.support.SimpleJpaRepository 进行修改,不过我为了省事,直接把这个类复制过来然后下手:
- /*
- * $Id$
- */
- package com.someok.common.base.spring.data;
- import static org.springframework.data.jpa.repository.query.QueryUtils.DELETE_ALL_QUERY_STRING;
- 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;
- import java.io.Serializable;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
- import javax.persistence.EntityManager;
- import javax.persistence.LockModeType;
- import javax.persistence.NoResultException;
- import javax.persistence.TypedQuery;
- import javax.persistence.criteria.CriteriaBuilder;
- import javax.persistence.criteria.CriteriaQuery;
- import javax.persistence.criteria.Path;
- import javax.persistence.criteria.Predicate;
- import javax.persistence.criteria.Root;
- import org.springframework.dao.EmptyResultDataAccessException;
- 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.support.JpaEntityInformation;
- import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
- import org.springframework.data.jpa.repository.support.LockMetadataProvider;
- import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
- import org.springframework.transaction.annotation.Transactional;
- import org.springframework.util.Assert;
- import com.someok.common.base.mvc.BaseDao;
- import com.someok.common.base.mvc.BaseDefaultModel;
- import com.someok.common.utils.StringUtil;
- /**
- * 修改自
- * {@link org.springframework.data.jpa.repository.support.SimpleJpaRepository},
- * 提供逻辑删除功能(logicDelete),为适应这个要求,相关的查询也都做了修改。
- *
- * 需要注意的是那些 delete 方法仍然是物理删除,而新增的那些 logic 开头的方法才是逻辑删除, 用时候需要注意这点.
- *
- * @author wangjxe
- *
- */
- @org.springframework.stereotype.Repository
- @Transactional(readOnly = true)
- public class CustomSimpleJpaRepository<T, ID extends Serializable> implements
- BaseDao<T, ID> {
- /**
- * 逻辑删除字段名.
- */
- public final static String DELETEED_FIELD = "deleted";
- public static final String COUNT_QUERY_STRING = "select count(%s) from %s x where x.deleted = false";
- public static final String EXISTS_QUERY_STRING = "select count(%s) from %s x where x.%s = :id and x.deleted = false";
- private final JpaEntityInformation<T, ?> entityInformation;
- private final EntityManager em;
- // private final PersistenceProvider provider;
- private LockMetadataProvider lockMetadataProvider;
- /**
- * Creates a new {@link SimpleJpaRepository} to manage objects of the given
- * {@link JpaEntityInformation}.
- *
- * @param entityInformation
- * must not be {@literal null}.
- * @param entityManager
- * must not be {@literal null}.
- */
- public CustomSimpleJpaRepository(
- JpaEntityInformation<T, ?> entityInformation,
- EntityManager entityManager) {
- Assert.notNull(entityInformation);
- Assert.notNull(entityManager);
- this.entityInformation = entityInformation;
- this.em = entityManager;
- // this.provider = PersistenceProvider.fromEntityManager(entityManager);
- }
- /**
- * Creates a new {@link SimpleJpaRepository} to manage objects of the given
- * domain type.
- *
- * @param domainClass
- * must not be {@literal null}.
- * @param em
- * must not be {@literal null}.
- */
- public CustomSimpleJpaRepository(Class<T> domainClass, EntityManager em) {
- this(JpaEntityInformationSupport.getMetadata(domainClass, em), em);
- }
- /**
- * Configures a custom {@link LockMetadataProvider} to be used to detect
- * {@link LockModeType}s to be applied to queries.
- *
- * @param lockMetadataProvider
- */
- public void setLockMetadataProvider(
- LockMetadataProvider lockMetadataProvider) {
- this.lockMetadataProvider = lockMetadataProvider;
- }
- private Class<T> getDomainClass() {
- return entityInformation.getJavaType();
- }
- private String getDeleteAllQueryString() {
- return getQueryString(DELETE_ALL_QUERY_STRING,
- entityInformation.getEntityName());
- }
- private String getCountQueryString() {
- String countQuery = String.format(COUNT_QUERY_STRING,
- getCountQueryPlaceholder(), "%s");
- return getQueryString(countQuery, entityInformation.getEntityName());
- }
- /*
- * (non-Javadoc)
- *
- * @see org.springframework.data.repository.CrudRepository#delete(java.io.
- * Serializable)
- */
- @Transactional
- public void delete(ID id) {
- Assert.notNull(id, "The given id must not be null!");
- if (!exists(id)) {
- throw new EmptyResultDataAccessException(String.format(
- "No %s entity with id %s exists!",
- entityInformation.getJavaType(), id), 1);
- }
- delete(findOne(id));
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.repository.CrudRepository#delete(java.lang.Object
- * )
- */
- @Transactional
- public void delete(T entity) {
- Assert.notNull(entity, "The entity must not be null!");
- em.remove(em.contains(entity) ? entity : em.merge(entity));
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable
- * )
- */
- @Transactional
- public void delete(Iterable<? extends T> entities) {
- Assert.notNull(entities, "The given Iterable of entities not be null!");
- for (T entity : entities) {
- delete(entity);
- }
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.jpa.repository.JpaRepository#deleteInBatch(java
- * .lang.Iterable)
- */
- @Transactional
- public void deleteInBatch(Iterable<T> 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.repository.Repository#deleteAll()
- */
- @Transactional
- public void deleteAll() {
- for (T element : findAll()) {
- delete(element);
- }
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.jpa.repository.JpaRepository#deleteAllInBatch()
- */
- @Transactional
- public void deleteAllInBatch() {
- em.createQuery(getDeleteAllQueryString()).executeUpdate();
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.repository.Repository#readById(java.io.Serializable
- * )
- */
- public T findOne(ID id) {
- Assert.notNull(id, "The given id must not be null!");
- return em.find(getDomainClass(), id);
- }
- /*
- * (non-Javadoc)
- *
- * @see org.springframework.data.repository.CrudRepository#exists(java.io.
- * Serializable)
- */
- public boolean exists(ID id) {
- Assert.notNull(id, "The given id must not be null!");
- if (entityInformation.getIdAttribute() != null) {
- String placeholder = getCountQueryPlaceholder();
- String entityName = entityInformation.getEntityName();
- String idAttributeName = entityInformation.getIdAttribute()
- .getName();
- String existsQuery = String.format(EXISTS_QUERY_STRING,
- placeholder, entityName, idAttributeName);
- TypedQuery<Long> query = em.createQuery(existsQuery, Long.class);
- query.setParameter("id", id);
- return query.getSingleResult() == 1;
- } else {
- return findOne(id) != null;
- }
- }
- /*
- * (non-Javadoc)
- *
- * @see org.springframework.data.jpa.repository.JpaRepository#findAll()
- */
- public List<T> findAll() {
- return getQuery(null, (Sort) null).getResultList();
- }
- /*
- * (non-Javadoc)
- *
- * @see org.springframework.data.repository.CrudRepository#findAll(ID[])
- */
- public List<T> findAll(Iterable<ID> ids) {
- return getQuery(new Specification<T>() {
- public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
- CriteriaBuilder cb) {
- Path<?> path = root.get(entityInformation.getIdAttribute());
- return path.in(cb.parameter(List.class, "ids"));
- }
- }, (Sort) null).setParameter("ids", ids).getResultList();
- }
- /*
- * (non-Javadoc)
- *
- * @see org.springframework.data.jpa.repository.JpaRepository#findAll(org.
- * springframework.data.domain.Sort)
- */
- public List<T> findAll(Sort sort) {
- return getQuery(null, sort).getResultList();
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.repository.PagingAndSortingRepository#findAll
- * (org.springframework.data.domain.Pageable)
- */
- public Page<T> findAll(Pageable pageable) {
- if (null == pageable) {
- return new PageImpl<T>(findAll());
- }
- return findAll(null, pageable);
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.jpa.repository.JpaSpecificationExecutor#findOne
- * (org.springframework.data.jpa.domain.Specification)
- */
- public T findOne(Specification<T> spec) {
- try {
- return getQuery(spec, (Sort) null).getSingleResult();
- } catch (NoResultException e) {
- return null;
- }
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll
- * (org.springframework.data.jpa.domain.Specification)
- */
- public List<T> findAll(Specification<T> spec) {
- return getQuery(spec, (Sort) null).getResultList();
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll
- * (org.springframework.data.jpa.domain.Specification,
- * org.springframework.data.domain.Pageable)
- */
- public Page<T> findAll(Specification<T> spec, Pageable pageable) {
- TypedQuery<T> query = getQuery(spec, pageable);
- return pageable == null ? new PageImpl<T>(query.getResultList())
- : readPage(query, pageable, spec);
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll
- * (org.springframework.data.jpa.domain.Specification,
- * org.springframework.data.domain.Sort)
- */
- public List<T> findAll(Specification<T> spec, Sort sort) {
- return getQuery(spec, sort).getResultList();
- }
- /*
- * (non-Javadoc)
- *
- * @see org.springframework.data.repository.CrudRepository#count()
- */
- public long count() {
- return em.createQuery(getCountQueryString(), Long.class)
- .getSingleResult();
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.jpa.repository.JpaSpecificationExecutor#count
- * (org.springframework.data.jpa.domain.Specification)
- */
- public long count(Specification<T> spec) {
- return getCountQuery(spec).getSingleResult();
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.repository.CrudRepository#save(java.lang.Object)
- */
- @Transactional
- public <S extends T> S save(S entity) {
- if (entityInformation.isNew(entity)) {
- em.persist(entity);
- return entity;
- } else {
- return em.merge(entity);
- }
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.jpa.repository.JpaRepository#saveAndFlush(java
- * .lang.Object)
- */
- @Transactional
- public T saveAndFlush(T entity) {
- T result = save(entity);
- flush();
- return result;
- }
- /*
- * (non-Javadoc)
- *
- * @see
- * org.springframework.data.jpa.repository.JpaRepository#save(java.lang.
- * Iterable)
- */
- @Transactional
- public <S extends T> List<S> save(Iterable<S> entities) {
- List<S> result = new ArrayList<S>();
- if (entities == null) {
- return result;
- }
- for (S entity : entities) {
- result.add(save(entity));
- }
- return result;
- }
- /*
- * (non-Javadoc)
- *
- * @see org.springframework.data.jpa.repository.JpaRepository#flush()
- */
- @Transactional
- public void flush() {
- em.flush();
- }
- /**
- * Reads the given {@link TypedQuery} into a {@link Page} applying the given
- * {@link Pageable} and {@link Specification}.
- *
- * @param query
- * must not be {@literal null}.
- * @param spec
- * can be {@literal null}.
- * @param pageable
- * can be {@literal null}.
- * @return
- */
- private Page<T> readPage(TypedQuery<T> query, Pageable pageable,
- Specification<T> spec) {
- query.setFirstResult(pageable.getOffset());
- query.setMaxResults(pageable.getPageSize());
- Long total = getCountQuery(spec).getSingleResult();
- List<T> content = total > pageable.getOffset() ? query.getResultList()
- : Collections.<T> emptyList();
- return new PageImpl<T>(content, pageable, total);
- }
- /**
- * Creates a new {@link TypedQuery} from the given {@link Specification}.
- *
- * @param spec
- * can be {@literal null}.
- * @param pageable
- * can be {@literal null}.
- * @return
- */
- private TypedQuery<T> getQuery(Specification<T> spec, Pageable pageable) {
- Sort sort = pageable == null ? null : pageable.getSort();
- return getQuery(spec, sort);
- }
- /**
- * Creates a {@link TypedQuery} for the given {@link Specification} and
- * {@link Sort}.
- *
- * @param spec
- * can be {@literal null}.
- * @param sort
- * can be {@literal null}.
- * @return
- */
- private TypedQuery<T> getQuery(Specification<T> spec, Sort sort) {
- CriteriaBuilder builder = em.getCriteriaBuilder();
- CriteriaQuery<T> query = builder.createQuery(getDomainClass());
- Root<T> root = applySpecificationToCriteria(spec, query);
- query.select(root);
- if (sort != null) {
- query.orderBy(toOrders(sort, root, builder));
- }
- return applyLockMode(em.createQuery(query));
- }
- /**
- * Creates a new count query for the given {@link Specification}.
- *
- * @param spec
- * can be {@literal null}.
- * @return
- */
- private TypedQuery<Long> getCountQuery(Specification<T> spec) {
- CriteriaBuilder builder = em.getCriteriaBuilder();
- CriteriaQuery<Long> query = builder.createQuery(Long.class);
- Root<T> root = applySpecificationToCriteria(spec, query);
- query.select(builder.count(root));
- return em.createQuery(query);
- }
- /**
- * Applies the given {@link Specification} to the given
- * {@link CriteriaQuery}.
- *
- * @param spec
- * can be {@literal null}.
- * @param query
- * must not be {@literal null}.
- * @return
- */
- private <S> Root<T> applySpecificationToCriteria(Specification<T> spec,
- CriteriaQuery<S> query) {
- Assert.notNull(query);
- Root<T> root = query.from(getDomainClass());
- CriteriaBuilder builder = em.getCriteriaBuilder();
- // 增加了删除条件判断,从而将被逻辑删除的数据过滤掉
- Predicate deletedPredicate = null;
- if (BaseDefaultModel.class.isAssignableFrom(getDomainClass())) {
- Path<Boolean> deletedPath = root.<Boolean> get(DELETEED_FIELD);
- deletedPredicate = builder.isFalse(deletedPath);
- }
- if (spec == null) {
- // 没有其它条件的时候只判断deleted字段
- query.where(deletedPredicate);
- return root;
- }
- Predicate predicate = spec.toPredicate(root, query, builder);
- if (predicate != null) {
- // 存在其它条件的时候还需要组合一下 deleted 条件
- if (null != deletedPredicate) {
- predicate = builder.and(predicate, deletedPredicate);
- }
- query.where(predicate);
- }
- return root;
- }
- private TypedQuery<T> applyLockMode(TypedQuery<T> query) {
- LockModeType type = lockMetadataProvider == null ? null
- : lockMetadataProvider.getLockModeType();
- return type == null ? query : query.setLockMode(type);
- }
- /*
- * (non-Javadoc)
- *
- * @see com.someok.common.base.mvc.BaseDao#logicDelete(java.io.Serializable)
- */
- @Override
- public void logicDelete(ID id) {
- T entity = findOne(id);
- if (null == entity || !(entity instanceof BaseDefaultModel)) {
- return;
- }
- BaseDefaultModel model = (BaseDefaultModel) entity;
- model.setDeleted(true);
- this.em.merge(model);
- }
- /*
- * (non-Javadoc)
- *
- * @see com.someok.common.base.mvc.BaseDao#logicDelete(java.lang.Object)
- */
- @Override
- public void logicDelete(T entity) {
- if (null == entity || !(entity instanceof BaseDefaultModel)) {
- return;
- }
- BaseDefaultModel model = (BaseDefaultModel) entity;
- model.setDeleted(true);
- if (StringUtil.isBlank(model.getId())) {
- em.persist(model);
- } else {
- em.merge(model);
- }
- }
- /*
- * (non-Javadoc)
- *
- * @see com.someok.common.base.mvc.BaseDao#logicDelete(java.lang.Iterable)
- */
- @Override
- public void logicDelete(Iterable<? extends T> entities) {
- if (null == entities) {
- return;
- }
- for (T entity : entities) {
- logicDelete(entity);
- }
- }
- protected String getCountQueryPlaceholder() {
- return "x";
- }
- }
主要的改动是 applySpecificationToCriteria 方法,与 SimpleJpaRepository 比对下就知道改了啥了。
findByXXX 操作
CURD 的修改还是比较简单的,不过那些根据接口方法自动实现查询修改起来就比较麻烦了。当然,不做任何修改也可以用,只需要在dao接口的方法上面加个 @Query 就行了,但是这样就需要写大量的jpql了,与采用 spring data的原意不符,咱用这玩意目的不就是为了个简单嘛。
本来的想法是继承某些类来对需要调整的方法重新实现就ok了,可惜spring data 这块的实现有点太封闭了,多个类没有 public,只能包内可见,更多的需要的方法只提供了 private 属性。没办法,只好把 org.springframework.data.jpa.repository.query 包内的代码都拷贝过来,其实真正需要改动的地方只有一处:
com.someok.common.base.spring.data.query.JpaQueryCreator.complete(Predicate, Sort, CriteriaQuery<Object>, CriteriaBuilder, Root<?>)
具体修改方法如下:
- protected CriteriaQuery<Object> complete(Predicate predicate, Sort sort,
- CriteriaQuery<Object> query, CriteriaBuilder builder, Root<?> root) {
- // 增加了删除条件判断,从而将被逻辑删除的数据过滤掉
- Predicate deletedPredicate = null;
- if (BaseDefaultModel.class.isAssignableFrom(this.domainClass)) {
- Path<Boolean> deletedPath = root.<Boolean> get(CustomSimpleJpaRepository.DELETEED_FIELD);
- deletedPredicate = builder.isFalse(deletedPath);
- }
- // 在原有条件基础上组合 deleted 条件
- if (null != deletedPredicate) {
- predicate = builder.and(predicate, deletedPredicate);
- }
- return this.query.select(root).where(predicate)
- .orderBy(QueryUtils.toOrders(sort, root, builder));
- }