封裝Service層公用CRUD操作,提高開發效率
阿新 • • 發佈:2018-12-21
專案架構: 專案採取前後分離開發模式,前端:antd pro 2.0,後端:spring cloud + spring boot + JPA。資料交換:json
最近在專案組開發中使用到JPA,初次使用JPA感覺很強大,解放了repository層的實現,提高了開發效率。
在git上評審專案組成員程式碼時,發現各應用模組在Service層都對其下業務建立了CRUD操作,檢視完專案程式碼,感覺80%都類似,只是呼叫的業務型別和引數不同而已。因此跳出一個將Service層CRUD操作進行封裝的想法。思考過後,開始搬磚...
1. 建立service層公共介面BaseService
package com.****.common; import java.util.List; import java.util.Map; /** * 封裝公共基礎操作介面,定義基本CRUD操作,避免程式碼冗餘 * @param <V> VO類 * @param <E> Entity實體類 * @author Abin * @Date 2018/11/07 */ public interface BaseService<V, E> { /** * 根據ID,檢測Entity是否存在 * @param id Entity實體類主鍵 * @return boolean 是否存在 */ boolean checkExists(Long id); /** * 根據名稱,檢測Entity是否存在 * @param name Entity實體類名稱屬性 * @return boolean 是否存在 */ boolean checkExists(String name); /** * 根據ID,獲取單一Entity實體類,並轉為VO物件返回 * @param id Entity實體類ID * @return V 返回Entity實體類對應的VO物件 */ V findOne(Long id); /** * 獲取所有Entity實體,並轉為VO集合返回 * @return List<VO> */ List<V> findAll(); /** * 根據map<屬性名,屬性值>多條件模糊查詢獲取所有Entity實體,並轉為VO集合返回 * @param map 條件集合 * @return List<VO> VO物件集合 */ List<V> findByLike(Map<String, String> map); /** * 根據map<屬性名,屬性值>多條件查詢、排序、分頁獲取所有Entity實體,並轉為VO集合返回 * @param condMap 條件集合 * @param sortProperties 排序屬性集合 * @param currentPage 當前頁面 * @param pageSize 每頁顯示條數 * @return Map<String, Object> VO物件集合 */ Map<String, Object> getPageByConditional(Map<String, Object> condMap, List<String> sortProperties, Integer currentPage, Integer pageSize); /** * 新增Entity實體 * @param v VO物件 * @return boolean 是否新增成功 */ boolean save(V v); /** * 修改Entity實體 * @param v VO物件 * @return boolean 是否修改成功 */ boolean update(V v); /** * 根據ID,Map<屬性名,值>,更新Entity實體 * @param id Entity實體ID * @param map 需要更新的欄位名、值集合 * @return boolean 是否更新成功 */ boolean updateValuesByMap(Long id, Map<String, Object> map); /** * 根據ID, 邏輯刪除Entity實體 * @param id Entity物件ID * @return boolean 是否刪除成功 */ boolean updateValid(Long id); /** * 根據ID, 刪除Entity實體,非邏輯刪除 * @param id Entity物件ID * @return boolean 是否刪除成功 */ void delete(Long id); }
2. 建立BaseService介面實現類BaseServiceImpl,實現類中根據傳入的業務模組實體E,建立SimpleJpaRepository
package com.******.common.impl; import com.******.common.util.BaseConverter; import com.******.constant.Constants; import com.******.common.BaseService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; 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.SimpleJpaRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.persistence.criteria.*; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; /** * 封裝公共基礎實現類,實現基本CRUD操作,避免程式碼冗餘 * @param <V> VO類 * @param <E> Entity實體類 * @author Abin * @date 2018/10/14 */ @Service @Slf4j public class BaseServiceImpl<V, E> implements BaseService<V, E> { @Autowired private BaseConverter baseConverter; @PersistenceContext EntityManager em; private Class<V> voClass; private Class<E> eClass; /** * 根據domain class建立JPA repository 實現類SimpleJpaRepository * @return SimpleJpaRepository物件 */ private SimpleJpaRepository<E, Long> createRepository() { return new SimpleJpaRepository(eClass, em); } /** * 根據ID,檢測Entity是否存在 * @param id Entity實體類主鍵 * @return boolean 是否存在 */ @Override @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public boolean checkExists(Long id) { return this.createRepository().exists(id); } /** * 根據名稱,檢測Entity是否存在 * @param name Entity實體類名稱屬性 * @return boolean 是否存在 */ @Override @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public boolean checkExists(String name) { return true; } /** * 根據ID,獲取單一Entity實體類,並轉為VO物件返回 * @param id 業務型別ID * @return VO物件 */ @Override @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public V findOne(Long id) { this.init(); E e = this.createRepository().findOne(id); return this.baseConverter.convertSingleObject(e, voClass); } /** * 獲取所有Entity實體,並轉為VO集合返回 * @return List<VO> VO物件集合 */ @Override @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public List<V> findAll() { this.init(); Specification<E> spec = (Root<E> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> { Path<Integer> pvalid = root.get(Constants.VALID); Predicate p = cb.equal(pvalid, Constants.DEFAULT_VALUE_ONE); query.where(p); return null; }; List<E> list = this.createRepository().findAll(spec); List<V> voList = baseConverter.convertMultiObjectToList(list, voClass); return voList; } /** * 根據map<屬性名,屬性值>多條件模糊查詢獲取所有Entity實體,並轉為VO集合返回 * @param map 條件集合 * @return List<VO> VO物件集合 */ @Override @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public List<V> findByLike(Map<String, String> map) { this.init(); Specification<E> spec = new Specification<E>() { @Override public Predicate toPredicate(Root<E> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { List<Predicate> list = new ArrayList<>(); if(null != map && map.size() > Constants.DEFAULT_VALUE_ZERO) { map.forEach((k, v) -> { if(Constants.ID.equalsIgnoreCase(k)) { list.add(criteriaBuilder.equal(root.get(k).as(String.class), v)); } else if(Constants.FACTORY_ID.equalsIgnoreCase(k)) { list.add(criteriaBuilder.equal(root.get(k).as(String.class), v)); } else { list.add(criteriaBuilder.like(root.get(k).as(String.class), Constants.DEFAULT_PERCENT + v + Constants.DEFAULT_PERCENT)); } }); Predicate[] p = new Predicate[list.size()]; Predicate predicate = criteriaBuilder.and(list.toArray(p)); criteriaQuery.where(predicate); } return null; } }; List<E> elist = this.createRepository().findAll(spec); return baseConverter.convertMultiObjectToList(elist, voClass); } /** * 根據map<屬性名,屬性值>多條件查詢、排序、分頁獲取所有Entity實體,並轉為VO集合返回 * @param condMap 條件集合 * @param sortProperties 排序屬性集合 * @param currentPage 當前頁面 * @param pageSize 每頁顯示條數 * @return Map<String, Object> VO物件集合 */ @Override @Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public Map<String, Object> getPageByConditional(Map<String, Object> condMap, List<String> sortProperties, Integer currentPage, Integer pageSize) { this.init(); if(null == sortProperties) { sortProperties = new ArrayList<>(); } if(sortProperties.isEmpty()) { sortProperties.add(Constants.ID); } Sort sort = new Sort(Sort.Direction.DESC, sortProperties); Pageable pageable = new PageRequest(currentPage-Constants.DEFAULT_VALUE_ONE, pageSize, sort); Specification<E> spec = new Specification<E>() { @Override public Predicate toPredicate(Root<E> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { List<Predicate> list = new ArrayList<>(); if(null !=condMap && condMap.size() > Constants.DEFAULT_VALUE_ZERO) { condMap.forEach((k, v) -> { if(Constants.ID.equalsIgnoreCase(k)) { list.add(criteriaBuilder.equal(root.get(k).as(String.class), v)); } else if(Constants.FACTORY_ID.equalsIgnoreCase(k)) { list.add(criteriaBuilder.equal(root.get(k).as(String.class), v)); } else { list.add(criteriaBuilder.like(root.get(k).as(String.class), Constants.DEFAULT_PERCENT + v + Constants.DEFAULT_PERCENT)); } }); Predicate[] p = new Predicate[list.size()]; Predicate predicate = criteriaBuilder.and(list.toArray(p)); criteriaQuery.where(predicate); } return null; } }; Page<E> page = this.createRepository().findAll(spec, pageable); return baseConverter.convertMultiObjectToMap(page, voClass); } /** * 新增Entity實體 * @param v VO物件 * @return boolean 是否新增成功 */ @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public boolean save(V v) { this.init(); E e = baseConverter.convertSingleObject(v, eClass); setDefaultValue(e); return null != this.createRepository().save(e); } /** * 修改Entity實體 * @param v VO物件 * @return boolean 是否修改成功 */ @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public boolean update(V v) { if (null != v) { try { Field idField= v.getClass().getDeclaredField(Constants.ID); idField.setAccessible(true); Long id = (Long)idField.get(v); if(null == id) { return false; } E origin = this.createRepository().findOne(id); if(null == origin) { return false; } vo2Entity(v, origin); this.createRepository().save(origin); return true; } catch (NoSuchFieldException ex) { log.error("獲取實體類ID異常" + v, ex); } catch (IllegalAccessException ex) { log.error("獲取實體類屬性值異常" + v, ex); } } return false; } /** * 根據ID,Map<屬性名,值>,更新Entity實體 * @param id Entity實體ID * @param map 需要更新的欄位名、值集合 * @return boolean 是否更新成功 */ @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public boolean updateValuesByMap(Long id, Map<String, Object> map) { this.init(); if (null == id || null == map || map.size() <= Constants.DEFAULT_VALUE_ZERO) { return false; } CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaUpdate<E> update = cb.createCriteriaUpdate(eClass); Root<E> root = update.from(eClass); map.forEach((k, v) -> { // set屬性、值 update.set(root.get(k), v); }); update.where(cb.equal(root.get(Constants.ID),id)); Query q = em.createQuery(update); return q.executeUpdate() > Constants.DEFAULT_VALUE_ZERO; } /** * 根據ID, 邏輯刪除Entity實體 * @param id Entity物件ID * @return boolean 是否刪除成功 */ @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public boolean updateValid(Long id) { if(null == id ){ return false; } Map<String, Object> map = new HashMap<>(5); map.put(Constants.VALID, Constants.DEFAULT_VALUE_ZERO); map.put(Constants.GMT_MODIFIED, new Date()); return this.updateValuesByMap(id, map); } /** * 根據ID, 刪除Entity實體,非邏輯刪除 * @param id Entity物件ID * @return boolean 是否刪除成功 */ @Override @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) public void delete(Long id) { this.createRepository().delete(id); } /** * 初始化, 通過反射獲取泛型V、E的Class */ private void init() { Type genType = getClass().getGenericSuperclass(); Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); voClass = (Class<V>) params[Constants.DEFAULT_VALUE_ZERO]; eClass = (Class<E>) params[Constants.DEFAULT_VALUE_ONE]; } /** * 新增操作時,為entity實體類中部分屬性設定預設值 * @param entity 實體類 */ private void setDefaultValue(E entity) { if (null != entity) { Map<String, Object> map = new HashMap<>(5); map.put(Constants.VALID, Constants.DEFAULT_VALUE_ONE); map.put(Constants.REF_COUNT, Constants.DEFAULT_VALUE_ZERO); map.put(Constants.GMT_CREATE, new Date()); Field[] fields = entity.getClass().getDeclaredFields(); Arrays.asList(fields).forEach(field -> { field.setAccessible(true); map.forEach((k, v) -> { if(k.equalsIgnoreCase(field.getName())) { try{ field.set(entity, v); } catch (IllegalAccessException e) { log.error("屬性賦值異常" + entity, e); } } }); }); } } /** * 用VO物件的屬性值,替換Entity物件中相同屬性的值,返回Entity實體類 * @param v VO物件 * @param e Entity實體類 */ private void vo2Entity(V v, E e) { if (null == v || null == e) { return; } Field[] voFields = v.getClass().getDeclaredFields(); Class clazz = e.getClass(); Arrays.asList(voFields).forEach(voField -> { voField.setAccessible(true); try { Field eField = clazz.getDeclaredField(voField.getName()); eField.setAccessible(true); eField.set(e, voField.get(v)); // 更新修改時間為當前時間 Field gmtField = clazz.getDeclaredField(Constants.GMT_MODIFIED); gmtField.setAccessible(true); gmtField.set(e, new Date()); } catch (NoSuchFieldException ex) { log.error("獲取實體類ID異常" + v, ex); } catch (IllegalAccessException ex) { log.error("獲取實體類屬性值異常" + v, ex); } }); } }
3. Repository層基礎BaseRepository封裝
package com.*****.common.util; import java.io.Serializable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.repository.NoRepositoryBean; @NoRepositoryBean public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> { }
4. 業務模組BusinessTypeRepository
package com.****.repository.basic;
import com.*****.util.BaseRepository;
import com.*****.entity.basic.BusinessType;
import org.springframework.stereotype.Repository;
/**
* 基礎管理-業務型別Repository類
* @author Abin
* @date 2018/11/07
*/
@Repository
public interface BusinessTypeRepository extends BaseRepository<BusinessType, Long> {
}
5. 業務模組使用公共模組,業務模組介面BusinessTypeService
(僅舉一個業務層示例,其他業務模組類似)
package com.*******.service.basic;
import com.*******.vo.basic.BusinessTypeVO;
import com.*******.entity.basic.BusinessType;
import com.*******.common.BaseService;
import java.util.List;
/**
* 業務型別介面,定義業務型別相關操作,繼承基礎介面
* @author Abin
* @Date 2018/11/06
*/
public interface BusinessTypeService extends BaseService<BusinessTypeVO, BusinessType> {
/**
* 批量刪除業務型別,非邏輯刪除
* @param list 業務型別集合
*/
public void delete(List<BusinessTypeVO> list);
}
6 業務模組實現類BusinessTypeServiceImpl
package com.********.basic.impl;
import com.********.common.util.BaseConverter;
import com.********.controller.vo.basic.BusinessTypeVO;
import com.********.entity.basic.BusinessType;
import com.********.repository.basic.BusinessTypeRepository;
import com.********.basic.BusinessTypeService;
import com.********.common.impl.BaseServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 業務型別Service實現類,實現業務型別的相關操作,繼承基礎實現類
* @author Abin
* @Date 2018/11/07
*/
@Service
public class BusinessTypeServiceImpl extends BaseServiceImpl<BusinessTypeVO, BusinessType> implements BusinessTypeService {
@Autowired
private BusinessTypeRepository repository;
@Autowired
private BaseConverter baseConverter;
/**
* 批量刪除業務型別,非邏輯刪除
* @param list 業務型別集合
*/
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
public void delete(List<BusinessTypeVO> list) {
List<BusinessType> eList = baseConverter.convertMultiObjectToList(list, BusinessType.class);
repository.delete(eList);
}
}
7. controller層示例BusinessTypeController
package com.******.controller.basic;
import com.******.constant.Constants;
import com.******.controller.vo.basic.BusinessTypeVO;
import com.******.service.basic.BusinessTypeService;
import com.******.util.MapUtils;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 基礎管理-業務型別控制類
* @author Abin
* @date 2018/11/07
*/
@RestController
@RequestMapping(value = "/basic")
public class BusinessTypeController {
@Autowired
private BusinessTypeService service;
/**
* 根據ID,單個獲取業務型別
* @param id 業務型別ID
* @return VO物件
*/
@GetMapping(value = "/busiType/{id}")
public BusinessTypeVO findOne(@NotBlank @PathVariable(value = "id") Long id) {
return service.findOne(id);
}
/**
* 批量獲取業務型別集合
* @param params 含分頁屬性、過濾條件K-V對
* @return VO集合
*/
@GetMapping(value = "/busiTypes")
public Map<String, Object> getBusiTypePage(@RequestParam Map<String, Object> params) {
Integer currentPage = 1;
Integer pageSize = 20;
if(null != params.get(Constants.CURRENT_PAGE)) {
currentPage = Integer.parseInt(params.get(Constants.CURRENT_PAGE).toString());
}
if(null != params.get(Constants.PAGE_SIZE)) {
pageSize = Integer.parseInt(params.get(Constants.PAGE_SIZE).toString());
}
Map<String, Object> queryParamMap = MapUtils.filterPageParams4Map(params);
return service.getPageByConditional(queryParamMap, null, currentPage, pageSize);
}
/**
* 新增業務型別
* @param vo VO物件
* @return boolean 是否新增成功
*/
@PostMapping(value = "/busiType")
public void saveBusiType(@RequestBody BusinessTypeVO vo) {
service.save(vo);
}
/**
* 編輯業務型別
* @param vo VO物件
* @return boolean 是否修改成功
*/
@PutMapping(value = "/busiType")
public void updateBusiType(@RequestBody BusinessTypeVO vo) {
service.update(vo);
}
/**
* 邏輯刪除業務型別
* @param id 業務型別ID
* @return boolean 是否刪除成功
*/
@PutMapping(value = "/busiType/{id}")
public void deleteLogic(@PathVariable Long id) {
service.updateValid(id);
}
/**
* 刪除業務型別
* @param id 業務型別ID
*/
@DeleteMapping(value = "/busiType/{id}")
public void delete(@PathVariable Long id) {
service.delete(id);
}
}
這樣一個業務模組的CRUD操作就開發完成了。並且分頁查詢中可分頁、排序、過濾。封裝後,業務模組的sevice層幾乎不需要開發,開發效率提高不止一倍。已在專案中使用,親測可行。
注: 未給出的幾個類,是些常用的工具類,如BaseConverter為VO與entity物件的互轉封裝。具體專案中可自行封裝
開發中的一點技巧封裝,不喜勿噴,謝謝。