datasupport类删除_在 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 implements

BaseDao {

/**

* 逻辑删除字段名.

*/

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 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 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 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 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 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 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 findAll() {

return getQuery(null, (Sort) null).getResultList();

}

/*

* (non-Javadoc)

*

* @see org.springframework.data.repository.CrudRepository#findAll(ID[])

*/

public List findAll(Iterable ids) {

return getQuery(new Specification() {

public Predicate toPredicate(Root 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 findAll(Sort sort) {

return getQuery(null, sort).getResultList();

}

/*

* (non-Javadoc)

*

* @see

* org.springframework.data.repository.PagingAndSortingRepository#findAll

* (org.springframework.data.domain.Pageable)

*/

public Page findAll(Pageable pageable) {

if (null == pageable) {

return new PageImpl(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 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 findAll(Specification 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 findAll(Specification spec, Pageable pageable) {

TypedQuery query = getQuery(spec, pageable);

return pageable == null ? new PageImpl(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 findAll(Specification 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 spec) {

return getCountQuery(spec).getSingleResult();

}

/*

* (non-Javadoc)

*

* @see

* org.springframework.data.repository.CrudRepository#save(java.lang.Object)

*/

@Transactional

public 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 List save(Iterable entities) {

List result = new ArrayList();

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 readPage(TypedQuery query, Pageable pageable,

Specification spec) {

query.setFirstResult(pageable.getOffset());

query.setMaxResults(pageable.getPageSize());

Long total = getCountQuery(spec).getSingleResult();

List content = total > pageable.getOffset() ? query.getResultList()

: Collections. emptyList();

return new PageImpl(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 getQuery(Specification 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 getQuery(Specification spec, Sort sort) {

CriteriaBuilder builder = em.getCriteriaBuilder();

CriteriaQuery query = builder.createQuery(getDomainClass());

Root 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 getCountQuery(Specification spec) {

CriteriaBuilder builder = em.getCriteriaBuilder();

CriteriaQuery query = builder.createQuery(Long.class);

Root 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 Root applySpecificationToCriteria(Specification spec,

CriteriaQuery query) {

Assert.notNull(query);

Root root = query.from(getDomainClass());

CriteriaBuilder builder = em.getCriteriaBuilder();

// 增加了删除条件判断,从而将被逻辑删除的数据过滤掉

Predicate deletedPredicate = null;

if (BaseDefaultModel.class.isAssignableFrom(getDomainClass())) {

Path deletedPath = root. 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 applyLockMode(TypedQuery 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, CriteriaBuilder, Root>)

具体修改方法如下:

protected CriteriaQuery complete(Predicate predicate, Sort sort,

CriteriaQuery query, CriteriaBuilder builder, Root> root) {

// 增加了删除条件判断,从而将被逻辑删除的数据过滤掉

Predicate deletedPredicate = null;

if (BaseDefaultModel.class.isAssignableFrom(this.domainClass)) {

Path deletedPath = root. 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));

}

收工走人。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值