zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    ReactNative typescript路径别名配置,vscode不识别@/youpath配置方法
    代码片段:js数组对象排序
    ScreenToGif SharpDx 下载失败问题
    springboot的@CrossOrigin注解解决细粒度的配置跨域
    java 连接Kafka报错java.nio.channels.ClosedChannelExcep
    zookeeper日常报错总结
    Spring Aop、拦截器、过滤器的区别
    搭建zookeeper单机版以及简单命令的使用
    java后台代码发送邮件
    java后台调用http请求
  • 原文地址:https://www.cnblogs.com/huanzi-qch/p/9984261.html
Copyright © 2011-2022 走看看