1. 程式人生 > >封裝Service層公用CRUD操作,提高開發效率

封裝Service層公用CRUD操作,提高開發效率

專案架構: 專案採取前後分離開發模式,前端: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物件的互轉封裝。具體專案中可自行封裝

開發中的一點技巧封裝,不喜勿噴,謝謝。