SpringBoot系列——Spring-Data-JPA(升級版)

  前言

  在上篇博客中:SpringBoot系列——Spring-Data-JPA:http://www.javashuo.com/article/p-eciktnjw-z.html,咱們實現了單表的基礎get、save(插入/更新)、list、page、delete接口,可是這樣每一個單表都要寫着一套代碼,重複而繁雜,那能不能寫成一套通用common代碼,每一個單表去繼承從而實現這套基礎接口呢?同時,咱們應該用Vo去接收、傳輸數據,實體負責與數據庫表映射。html

 

  common代碼

  Vo與實體轉換前端

/**
 * 實體轉換工具
 */
public class FastCopy {

    /**
     * 類型轉換:實體Vo <->實體  例如:UserVo <-> User
     * 支持一級複雜對象複製
     */
    public static <T> T copy(Object src, Class<T> targetType) {
        T target = null;
        try {
            target = targetType.newInstance();
            BeanWrapper targetBean = new BeanWrapperImpl(target);
            BeanMap srcBean = new BeanMap(src);
            for (Object key : srcBean.keySet()) {
                try {
                    String srcPropertyName = key + "";
                    Object srcPropertyVal = srcBean.get(key);
                    //&& StringUtils.isEmpty(targetBean.getPropertyValue(srcPropertyName))
                    if (!StringUtils.isEmpty(srcPropertyVal) && !"class".equals(srcPropertyName)) {
                        Class srcPropertyType = srcBean.getType(srcPropertyName);
                        Class targetPropertyType = targetBean.getPropertyType(srcPropertyName);
                        if (targetPropertyType != null) {
                            if (srcPropertyType == targetPropertyType) {
                                targetBean.setPropertyValue(srcPropertyName, srcPropertyVal);
                            } else {

                                 if(srcPropertyVal == null){
                                     continue;
                                 }java

                                Object targetPropertyVal = targetPropertyType.newInstance();
                                BeanUtils.copyProperties(srcPropertyVal, targetPropertyVal);
                                targetBean.setPropertyValue(srcPropertyName, targetPropertyVal);

                                BeanWrapper targetBean2 = new BeanWrapperImpl(targetPropertyVal);
                                BeanMap srcBean2 = new BeanMap(srcPropertyVal);
                                srcBean2.keySet().forEach((srcPropertyName2) -> {
                                    Class srcPropertyType2 = srcBean2.getType((String) srcPropertyName2);
                                    Class targetPropertyType2 = targetBean2.getPropertyType((String) srcPropertyName2);
                                    if (targetPropertyType2 != null && srcPropertyType2 != targetPropertyType2
                                            && srcBean2.get(srcPropertyName2) != null && !"class".equals(srcPropertyName2)) {
                                        Object targetPropertyVal2 = null;
                                        try {
                                            targetPropertyVal2 = targetPropertyType2.newInstance();
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
                                        BeanUtils.copyProperties(srcBean2.get(srcPropertyName2), targetPropertyVal2);
                                        targetBean2.setPropertyValue((String) srcPropertyName2, targetPropertyVal2);
                                    }
                                });

                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return target;
    }

    /**
     * 類型轉換:實體Vo <->實體  例如:List<UserVo> <-> List<User>
     */
    public static <T> List<T> copyList(List srcList, Class<T> targetType) {
        List<T> newList = new ArrayList<>();
        for (Object entity : srcList) {
            newList.add(copy(entity, targetType));
        }
        return newList;
    }

    /**
     * 獲取/過濾對象的空屬性
     */
    public static String[] getNullProperties(Object src) {
        BeanWrapper srcBean = new BeanWrapperImpl(src); //1.獲取Bean
        Set<String> properties = new HashSet<>(); //3.獲取Bean的空屬性
        for (PropertyDescriptor p : srcBean.getPropertyDescriptors()) {
            String propertyName = p.getName();
            Object srcValue = srcBean.getPropertyValue(propertyName);
            if (StringUtils.isEmpty(srcValue)) {
                srcBean.setPropertyValue(propertyName, null);
                properties.add(propertyName);
            }
        }
        String[] result = new String[properties.size()];
        return properties.toArray(result);
    }

    /**
     * 獲取對象的非空屬性
     */
    public static Map<String, Object> getNotNullProperties(Object src) {
        BeanWrapper srcBean = new BeanWrapperImpl(src); //1.獲取Bean
        PropertyDescriptor[] pds = srcBean.getPropertyDescriptors(); //2.獲取Bean的屬性描述
        Map<String, Object> properties = new LinkedHashMap<>();  //3.獲取Bean的非空屬性
        for (PropertyDescriptor p : pds) {
            String key = p.getName();
            Object value = srcBean.getPropertyValue(key);
            if (!StringUtils.isEmpty(value) && !"class".equals(key)) {
                properties.put(key, value);
            }
        }

        return properties;
    }

    /**
     * 將Object數組轉爲實體類VO
     */
    public static <V> List<V> getEntityVo(List<Object[]> propertyArrayList, Class<V> voClass) {
        List<V> list = new ArrayList<>();
        try {
            if (propertyArrayList != null) {
                for (Object[] propertyArray : propertyArrayList) {
                    V vo = voClass.newInstance();
                    Field[] fields = vo.getClass().getDeclaredFields();
                    for (int i = 0; i < propertyArray.length; i++) {
                        Field voField = fields[i];
                        Object queryVal = propertyArray[i];
                        if (voField.getType() == String.class && queryVal instanceof BigDecimal) {
                            queryVal = String.valueOf(queryVal);
                        }
                        voField.setAccessible(true);//獲取受權
                        voField.set(vo, queryVal);
                    }
                    list.add(vo);
                }

            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return list;
    }

}

  注:BeanMap類引入的是:org.apache.commons.beanutils.BeanMap;git

  引入這兩個jargithub

        <!-- Vo與實體的轉換工具類須要用到 -->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.8.0</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>

   PS:2019-06-13補充,後續發現這個工具類有點複雜,並且閱讀起來很吃力,所以特地重寫了一下,邏輯更清晰,註釋健全,類名不重要(由於後面咱們進行了更名,改爲了CopyUtil),重點關注copy方法spring

package cn.huanzi.qch.springbootjpa.util;

import org.apache.commons.beanutils.BeanMap;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.util.ArrayList;
import java.util.List;

/**
 * 實體轉換工具
 */
public class CopyUtil {

    /**
     * 類型轉換:實體Vo <->實體  例如:UserVo <-> User
     * 支持一級複雜對象複製
     */
    public static <T> T copy(Object src, Class<T> targetType) {
        T target = null;
        try {
            //建立一個空目標對象,並獲取一個BeanWrapper代理器,用於屬性填充,BeanWrapperImpl在內部使用Spring的BeanUtils工具類對Bean進行反射操做,設置屬性。
            target = targetType.newInstance();
            BeanWrapper targetBean = new BeanWrapperImpl(target);

            //獲取源對象的BeanMap,屬性和屬性值直接轉換爲Map的key-value 形式
            BeanMap srcBean = new BeanMap(src);
            for (Object key : srcBean.keySet()) {
                //源對象屬性名稱
                String srcPropertyName = key + "";
                //源對象屬性值
                Object srcPropertyVal = srcBean.get(key);
                //源對象屬性類型
                Class srcPropertyType = srcBean.getType(srcPropertyName);
                //目標對象屬性類型
                Class targetPropertyType = targetBean.getPropertyType(srcPropertyName);

                //源對象屬性值非空判斷、目標對象屬性類型非空判斷,若是爲空跳出,繼續操做下一個屬性
                if ("class".equals(srcPropertyName) || targetPropertyType == null) {
                    continue;
                }

                //類型相等,可直接設置值,好比:String與String 或者 User與User
                if (srcPropertyType == targetPropertyType) {
                    targetBean.setPropertyValue(srcPropertyName, srcPropertyVal);
                }
                //類型不相等,好比:User與UserVo
                else {
                    /*     下面的步驟與上面的步驟基本一致      */

                    //若是源複雜對象爲null,直接跳過,不須要複製
                    if(srcPropertyVal == null){
                        continue;
                    }

                    Object targetPropertyVal = targetPropertyType.newInstance();
                    BeanWrapper targetPropertyBean = new BeanWrapperImpl(targetPropertyVal);

                    BeanMap srcPropertyBean = new BeanMap(srcPropertyVal);
                    for (Object srcPropertyBeanKey : srcPropertyBean.keySet()) {
                        String srcPropertyBeanPropertyName = srcPropertyBeanKey + "";
                        Object srcPropertyBeanPropertyVal = srcPropertyBean.get(srcPropertyBeanKey);
                        Class srcPropertyBeanPropertyType = srcPropertyBean.getType(srcPropertyBeanPropertyName);
                        Class targetPropertyBeanPropertyType = targetPropertyBean.getPropertyType(srcPropertyBeanPropertyName);

                        if ("class".equals(srcPropertyBeanPropertyName) || targetPropertyBeanPropertyType == null) {
                            continue;
                        }

                        if (srcPropertyBeanPropertyType == targetPropertyBeanPropertyType) {
                            targetPropertyBean.setPropertyValue(srcPropertyBeanPropertyName, srcPropertyBeanPropertyVal);
                        } else {
                            //複雜對象裏面的複雜對象再也不進行處理
                        }
                    }
                    //設置目標對象屬性值
                    targetBean.setPropertyValue(srcPropertyName, targetPropertyBean.getWrappedInstance());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return target;
    }

    /**
     * 類型轉換:實體Vo <->實體  例如:List<UserVo> <-> List<User>
     */

    public static <T> List<T> copyList(List srcList, Class<T> targetType) {
        List<T> newList = new ArrayList<>();
        for (Object entity : srcList) {
            newList.add(copy(entity, targetType));
        }
        return newList;
    }

}
View Code

 

 

  通用service、repository數據庫

/**
 * 通用Service
 *
 * @param <V> 實體類Vo
 * @param <E> 實體類
 * @param <T> id主鍵類型
 */
public interface CommonService<V, E,T> {

    Result<PageInfo<V>> page(V entityVo);

    Result<List<V>> list(V entityVo);

    Result<V> get(T id);

    Result<V> save(V entityVo);

    Result<T> delete(T id);
}
/**
 * 通用Service實現類
 *
 * @param <V> 實體類Vo
 * @param <E> 實體類
 * @param <T> id主鍵類型
 */
public class CommonServiceImpl<V, E, T> implements CommonService<V, E, T> {

    private Class<V> entityVoClass;//實體類Vo

    private Class<E> entityClass;//實體類

    @Autowired
    private CommonRepository<E, T> commonRepository;//注入實體類倉庫
 public CommonServiceImpl() {
        Type[] types = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
        this.entityVoClass = (Class<V>) types[0];
        this.entityClass = (Class<E>) types[1];
    }

    @Override
    public Result<PageInfo<V>> page(V entityVo) {
        //實體類缺失分頁信息
        if (!(entityVo instanceof PageCondition)) {
            throw new RuntimeException("實體類" + entityVoClass.getName() + "未繼承PageCondition。");
        }
        PageCondition pageCondition = (PageCondition) entityVo;
        Page<E> page = commonRepository.findAll(Example.of(FastCopy.copy(entityVo, entityClass)), pageCondition.getPageable());
        return Result.of(PageInfo.of(page, entityVoClass));
    }

    @Override
    public Result<List<V>> list(V entityVo) {
        List<E> entityList = commonRepository.findAll(Example.of(FastCopy.copy(entityVo, entityClass)));
        List<V> entityModelList = FastCopy.copyList(entityList, entityVoClass);
        return Result.of(entityModelList);
    }

    @Override
    public Result<V> get(T id) {
        Optional<E> optionalE = commonRepository.findById(id);
        if (!optionalE.isPresent()) {
            throw new RuntimeException("ID不存在!");
        }
        return Result.of(FastCopy.copy(optionalE.get(), entityVoClass));
    }

    @Override
    public Result<V> save(V entityVo) {
        E e = commonRepository.save(FastCopy.copy(entityVo, entityClass));
        return Result.of(FastCopy.copy(e, entityVoClass));
    }

    @Override
    public Result<T> delete(T id) {
        commonRepository.deleteById(id);
        return Result.of(id);
    }
}
/**
 * 通用Repository
 *
 * @param <E> 實體類
 * @param <T> id主鍵類型
 */
@NoRepositoryBean
public interface CommonRepository<E,T> extends JpaRepository<E,T>, JpaSpecificationExecutor<E> {
}

   2019-05-13更新apache

    jpa實現局部更新數組

  注意:jpa原生的save方法,更新的時候是全屬性進行updata,若是實體類的屬性沒有值它會幫你更新成null,若是你想更新部分字段請在通用CommonServiceImpl使用這個save方法,我這裏是在調用save以前先查詢數據庫獲取完整對象,將要更新的值複製到最終傳入save方法的對象中,從而實現局部更新
springboot

 

    @Override
    public Result<V> save(V entityVo) {
        //傳進來的對象(屬性可能殘缺)
        E entity = CopyUtil.copy(entityVo, entityClass);

        //最終要保存的對象
        E entityFull = entity;

        //爲空的屬性值,忽略屬性,BeanUtils複製的時候用到
        List<String> ignoreProperties = new ArrayList<String>();

        //獲取最新數據,解決部分更新時jpa其餘字段設置null問題
        try {
            //反射獲取Class的屬性(Field表示類中的成員變量)
            for (Field field : entity.getClass().getDeclaredFields()) {
                //獲取受權
                field.setAccessible(true);
                //屬性名稱
                String fieldName = field.getName();
                //屬性的值
                Object fieldValue = field.get(entity);

                //找出Id主鍵
                if (field.isAnnotationPresent(Id.class) && !StringUtils.isEmpty(fieldValue)) {
                    Optional<E> one = commonRepository.findById((T) fieldValue);
                    if (one.isPresent()) {
                        entityFull = one.get();
                    }
                }

                //找出值爲空的屬性,值爲空則爲忽略屬性,或者被NotFound標註,咱們複製的時候不進行賦值
                if(null == fieldValue || field.isAnnotationPresent(NotFound.class)){
                    ignoreProperties.add(fieldName);
                }
            }
            /*
                org.springframework.beans BeanUtils.copyProperties(A,B); 是A中的值付給B
                org.apache.commons.beanutils; BeanUtils.copyProperties(A,B);是B中的值付給A
                把entity的值賦給entityFull,第三個參數是忽略屬性,表示不進行賦值
             */
            BeanUtils.copyProperties(entity, entityFull, ignoreProperties.toArray(new String[0]));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        E e = commonRepository.save(entityFull);
        return Result.of(CopyUtil.copy(e, entityVoClass));
    }

 

   2019-09-18補充:上面的save方法實現了局部更新,也就是每次調用save以前先用id去查庫,而後替換傳進來的值;本次實現的是,若是是新增,自動添加UUID爲主鍵,同時自動判斷createTime,updateTime,也就是說若是前端不傳這兩個值,後臺來維護建立時間、更新時間,固然,這種便利是有前提的,要求實體類的Id屬性排在第一位

    @Override
    public Result<V> save(V entityVo) {
        //傳進來的對象(屬性可能殘缺)
        E entity = CopyUtil.copy(entityVo, entityClass);

        //最終要保存的對象
        E entityFull = entity;

        //爲空的屬性值,忽略屬性,BeanUtils複製的時候用到
        List<String> ignoreProperties = new ArrayList<String>();

        //獲取最新數據,解決部分更新時jpa其餘字段設置null問題
        try {
            //新增 true,更新 false,要求實體類的Id屬性排在第一位,由於for循環讀取是按照順序的
            boolean isInsert = false;

            //反射獲取Class的屬性(Field表示類中的成員變量)
            for (Field field : entity.getClass().getDeclaredFields()) {
                //獲取受權
                field.setAccessible(true);
                //屬性名稱
                String fieldName = field.getName();
                //屬性的值
                Object fieldValue = field.get(entity);

                //找出Id主鍵
                if (field.isAnnotationPresent(Id.class)) {
                    if(!StringUtils.isEmpty(fieldValue)){
                        //若是Id主鍵不爲空,則爲更新
                        Optional<E> one = commonRepository.findById((T) fieldValue);
                        if (one.isPresent()) {
                            entityFull = one.get();
                        }
                    }else{
                        //若是Id主鍵爲空,則爲新增
                        fieldValue = UUIDUtil.getUUID();
                        //set方法,第一個參數是對象
                        field.set(entity, fieldValue);
                        isInsert = true;
                    }
                }
                //若是前端不傳這兩個值,後臺來維護建立時間、更新時間
                if(isInsert && "createTime".equals(fieldName) && StringUtils.isEmpty(fieldValue)){
                    //set方法,第一個參數是對象
                    field.set(entity, new Date());
                }
                if("updateTime".equals(fieldName) && StringUtils.isEmpty(fieldValue)){
                    //set方法,第一個參數是對象
                    field.set(entity, new Date());
                }

                //找出值爲空的屬性,值爲空則爲忽略屬性,或者被NotFound標註,咱們複製的時候不進行賦值
                if(null == fieldValue || field.isAnnotationPresent(NotFound.class)){
                    ignoreProperties.add(fieldName);
                }
            }
            /*
                org.springframework.beans BeanUtils.copyProperties(A,B); 是A中的值付給B
                org.apache.commons.beanutils; BeanUtils.copyProperties(A,B);是B中的值付給A
                把entity的值賦給entityFull,第三個參數是忽略屬性,表示不進行賦值
             */
            BeanUtils.copyProperties(entity, entityFull, ignoreProperties.toArray(new String[0]));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        E e = commonRepository.save(entityFull);
        return Result.of(CopyUtil.copy(e, entityVoClass));
    }

   須要用到UUID工具類

import java.util.UUID;

/**
 * UUID工具類
 */
public class UUIDUtil {

    /** 
     * 生成32位UUID編碼
     */
    public static String getUUID(){
        return UUID.randomUUID().toString().trim().replaceAll("-", "");
    }
}

 

 

  單表使用

  單表繼承通用代碼,實現get、save(插入/更新)、list、page、delete接口

  Vo

/**
 * 用戶類Vo
 */
@Data
public class UserVo extends PageCondition implements Serializable {

    private Integer id;

    private String username;

    private String password;

    private Date created;

    private String descriptionId;

    //機架類型信息
    private DescriptionVo description;
}
/**
 * 用戶描述類Vo
 */
@Data
public class DescriptionVo implements Serializable {
    private Integer id;

    private String userId;

    private String description;
}

 

  controller、service、repository

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/getAllUser")
    public ModelAndView getAllUser(){
        Result result=userService.getAllUser();
        ModelAndView mv=new ModelAndView();
        mv.addObject("userList",result.getData());
        mv.setViewName("index.html");
        return mv;
    }

    /*
        CRUD、分頁、排序
     */

    @RequestMapping("page")
    public Result<PageInfo<UserVo>> page(UserVo entityVo) {
        return userService.page(entityVo);
    }

    @RequestMapping("list")
    public Result<List<UserVo>> list(UserVo entityVo) {
        return userService.list(entityVo);
    }

    @RequestMapping("get/{id}")
    public Result<UserVo> get(@PathVariable("id") Integer id) {
        return userService.get(id);
    }

    @RequestMapping("save")
    public Result<UserVo> save(UserVo entityVo) {
        return userService.save(entityVo);
    }

    @RequestMapping("delete/{id}")
    public Result<Integer> delete(@PathVariable("id") Integer id) {
        return userService.delete(id);
    }
}
public interface UserService extends CommonService<UserVo, User,Integer>{

    Result getAllUser();
}
@Service
@Transactional
public class UserServiceImpl extends CommonServiceImpl<UserVo, User,Integer> implements UserService { @Autowired private UserRepository userRepository; @Override public Result getAllUser() { List<User> userList = userRepository.getAllUser(); if(userList != null && userList.size()>0){ ArrayList<UserVo> userVos = new ArrayList<>(); for(User user : userList){ userVos.add(FastCopy.copy(user, UserVo.class)); } return Result.of(userVos); }else { return Result.of(userList,false,"獲取失敗!"); } } }
@Repository
public interface UserRepository extends CommonRepository<User, Integer> {

    @Query(value = "from User") //HQL
//    @Query(value = "select * from tb_user",nativeQuery = true)//原生SQL
    List<User> getAllUser();

}

 

  經測試,全部的接口均可以使用,數據傳輸正常,由於傳輸的Vo,分頁信息跟雜七雜八的字段、數據都在Vo,全部看起來會比較雜。更新接口依舊跟上一篇的同樣,接收到的是什麼就保存什麼。

 

  後記

  單表的增刪改查接口,直接繼承這一套通用代碼便可實現,無需再重複編寫,大大提高開發效率。

 

  代碼開源

  代碼已經開源、託管到個人GitHub、碼雲:

  GitHub:https://github.com/huanzi-qch/springBoot

  碼雲:https://gitee.com/huanzi-qch/springBoot

相關文章
相關標籤/搜索