博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在 Spring Data Jpa 中使用逻辑删除需做的工作
阅读量:6240 次
发布时间:2019-06-22

本文共 16298 字,大约阅读时间需要 54 分钟。

  hot3.png

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;/** * 修改自 * {
org.springframework.data.jpa.repository.support.SimpleJpaRepository}, * 提供逻辑删除功能(logicDelete),为适应这个要求,相关的查询也都做了修改。 * * 需要注意的是那些 delete 方法仍然是物理删除,而新增的那些 logic 开头的方法才是逻辑删除, 用时候需要注意这点. * * 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 {
SimpleJpaRepository} to manage objects of the given * {
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 {
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 {
LockMetadataProvider} to be used to detect * {
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) * * 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) * * * 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) * * * org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable * ) */ @Transactional public void delete(Iterable
entities) { Assert.notNull(entities, "The given Iterable of entities not be null!"); for (T entity : entities) { delete(entity); } } /* * (non-Javadoc) * * * 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) * * org.springframework.data.repository.Repository#deleteAll() */ @Transactional public void deleteAll() { for (T element : findAll()) { delete(element); } } /* * (non-Javadoc) * * * org.springframework.data.jpa.repository.JpaRepository#deleteAllInBatch() */ @Transactional public void deleteAllInBatch() { em.createQuery(getDeleteAllQueryString()).executeUpdate(); } /* * (non-Javadoc) * * * 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) * * 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) * * org.springframework.data.jpa.repository.JpaRepository#findAll() */ public List
findAll() { return getQuery(null, (Sort) null).getResultList(); } /* * (non-Javadoc) * * 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) * * org.springframework.data.jpa.repository.JpaRepository#findAll(org. * springframework.data.domain.Sort) */ public List
findAll(Sort sort) { return getQuery(null, sort).getResultList(); } /* * (non-Javadoc) * * * 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) * * * 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) * * * 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) * * * 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) * * * 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) * * org.springframework.data.repository.CrudRepository#count() */ public long count() { return em.createQuery(getCountQueryString(), Long.class) .getSingleResult(); } /* * (non-Javadoc) * * * org.springframework.data.jpa.repository.JpaSpecificationExecutor#count * (org.springframework.data.jpa.domain.Specification) */ public long count(Specification
spec) { return getCountQuery(spec).getSingleResult(); } /* * (non-Javadoc) * * * 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) * * * 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) * * * 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) * * org.springframework.data.jpa.repository.JpaRepository#flush() */ @Transactional public void flush() { em.flush(); } /** * Reads the given { TypedQuery} into a { Page} applying the given * { Pageable} and { Specification}. * * @param query * must not be {@literal null}. * @param spec * can be {@literal null}. * @param pageable * can be {@literal null}. * */ 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 { TypedQuery} from the given { Specification}. * * @param spec * can be {@literal null}. * @param pageable * can be {@literal null}. * */ private TypedQuery
getQuery(Specification
spec, Pageable pageable) { Sort sort = pageable == null ? null : pageable.getSort(); return getQuery(spec, sort); } /** * Creates a { TypedQuery} for the given { Specification} and * { Sort}. * * @param spec * can be {@literal null}. * @param sort * can be {@literal null}. * */ 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 { Specification}. * * @param spec * can be {@literal null}. * */ 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 { Specification} to the given * { CriteriaQuery}. * * @param spec * can be {@literal null}. * @param query * must not be {@literal null}. * */ 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) * * 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) * * 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) * * com.someok.common.base.mvc.BaseDao#logicDelete(java.lang.Iterable) */ @Override public void logicDelete(Iterable
entities) { if (null == entities) { return; } for (T entity : entities) { logicDelete(entity); } } protected String getCountQueryPlaceholder() { return "x"; }}

主要的改动是 applySpecificationToCriteria 方法,与 SimpleJpaRepository 比对下就知道改了啥了。

findByXXX 操作

CURD 的修改还是比较简单的,不过那些根据接口方法自动实现查询修改起来就比较麻烦了。当然,不做任何修改也可以用,只需要在dao接口的方法上面加个 就行了,但是这样就需要写大量的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 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)); }

收工走人。。。

转载于:https://my.oschina.net/someok/blog/70341

你可能感兴趣的文章
3.mybatis实战教程(mybatis in action)之三:实现数据的增删改查
查看>>
Caused by: Unable to load bean: type: class:com.opensymphony.xwork2.ObjectFactory - bean - jar
查看>>
让你拥有超能力:程序员应该掌握的统计学公式
查看>>
互联网组织的未来:剖析 GitHub 员工的任性之源
查看>>
Java 开源博客 Solo 1.4.0 发布 - 简化
查看>>
Oracle巡检
查看>>
【转载】胜者树
查看>>
查看mysql数据库存放的路径|Linux下查看MySQL的安装路径
查看>>
selenium+testNG+Ant
查看>>
1024程序员节,你屯书了吗?(内含福利)
查看>>
移动端JS 触摸事件基础
查看>>
Flex拖动原来如此简单
查看>>
温故而知新:什么是wcf
查看>>
centos语言设置
查看>>
php安装
查看>>
Fragment在getUserVisibleHint时进行加载数据的问题记录
查看>>
使用线程池模拟处理耗时任务,通过websocket提高用户体验
查看>>
Java 内部类种类及使用解析
查看>>
Axure产品原型设计工具
查看>>
spice在桌面虚拟化中的应用系列之三(USB映射实现,SSL加密,密码认证,多客户端支持)...
查看>>