`
wjx
  • 浏览: 66575 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

在 Spring Data Jpa 中使用逻辑删除需做的工作

阅读更多

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));
	}
 

 

收工走人。。。
2
4
分享到:
评论
6 楼 gozap_lanbo 2012-10-29  
最近我也在做这个事情,我想问下如何让自己定义的JpaQueryCreator生效?
5 楼 tag13346 2012-08-02  
好丑的API
4 楼 kjj 2012-08-02  
本身就是变态的做法
3 楼 ufopw 2012-08-02  
update不行?
2 楼 ynby369258 2012-08-02  
何必这么麻烦啊~ 自己再定义自己的接口以及接口实现。配合datajpa使用不是很好么?datajpa不能完全满足我们的要求, 看看springside4的做法,你就懂了...
1 楼 jianglijia 2012-08-02  
说了一大堆,这是嘛啊?

相关推荐

Global site tag (gtag.js) - Google Analytics