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

  前言

  在上篇博客中:SpringBoot系列——Spring-Data-JPA:https://www.cnblogs.com/huanzi-qch/p/9970545.html,我们实现了单表的基础get、save(插入/更新)、list、page、delete接口,但是这样每个单表都要写着一套代码,重复而繁杂,那能不能写成一套通用common代码,每个单表去继承从而实现这套基础接口呢?同时,我们应该用Vo去接收、传输数据,实体负责与数据库表映射。

  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;
                                 }

                                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;

  引入这两个jar

        <!-- 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方法

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更新

    jpa实现局部更新

  注意:jpa原生的save方法,更新的时候是全属性进行updata,如果实体类的属性没有值它会帮你更新成null,如果你想更新部分字段请在通用CommonServiceImpl使用这个save方法,我这里是在调用save之前先查询数据库获取完整对象,将要更新的值复制到最终传入save方法的对象中,从而实现局部更新

   另外,直接调用EntityManager的merge,也是传什么就保存什么

@PersistenceContext
private EntityManager em;

//注意:直接调用EntityManager的merge,传进去的实体字段是什么就保存什么
E e = em.merge(entity);
em.flush();
    @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)){
                    //先赋值给fieldValue,以免后续进行copy对象判断属性是否为忽略属性是出错
                    fieldValue = new Date();

                    //set方法,第一个参数是对象
                    field.set(entity, fieldValue);
                }
                if("updateTime".equals(fieldName) && StringUtils.isEmpty(fieldValue)){
                    //先赋值给fieldValue,以免后续进行copy对象判断属性是否为忽略属性是出错
                    fieldValue = new Date();

                    //set方法,第一个参数是对象
                    field.set(entity, fieldValue);
                }

                //找出值为空的属性,值为空则为忽略属性,或者被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

原文地址:https://www.cnblogs.com/huanzi-qch/p/9984261.html