资讯专栏INFORMATION COLUMN

Spring Boot 中 crud如何优雅的实现-附代码

wing324 / 3347人阅读

摘要:以下内容基于如果你使用的也是相同的技术栈可以继续往下阅读,如果不是可以当作参考。编写的四种方式裸写最简单最粗暴也是使用最多的一种方式,在写的多了之后可以用生成工具生成。

导读

在目前接触过的项目中大多数的项目都会涉及到: crud相关的操作, 哪如何优雅的编写crud操作呢?
带着这个问题,我们发现项目中大量的操作多是 创建实体 、删除实例、 修改实体、 查询单个实体、 分页查询多个实体, 我们有没有好的方式解决呢?
下面我给出crud编写的四种方式 循序渐进 ,并分析其优势劣势,希望有一种能适合你,如果你有其他方式可以留言讨论,在此权当抛砖引玉。

以下内容基于Spring Boot 、Spring MVC、 Spring Data JPA 如果你使用的也是相同的技术栈可以继续往下阅读,如果不是可以当作参考。

crud编写的四种方式 1 裸写crud
最简单最粗暴也是使用最多的一种方式,在写的多了之后可以用生成工具生成。
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

/**
 * @author yangrd
 * @date 2019/3/4
 **/
@AllArgsConstructor

@RestController
@RequestMapping("/api/banner")
public class BannerController {

    private BannerRepository repository;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Banner save(Banner banner) {
        return repository.save(banner);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) {
        repository.deleteById(id);
    }

    @PutMapping("/{id}")
    public void update(@PathVariable("id") Banner db, @RequestBody Banner banner) {
        BeanUtils.copyProperties(banner, db);
        repository.save(db);
    }

    @GetMapping
    public Page findAll(Pageable pageable) {
        return repository.findAll(pageable);
    }

    @GetMapping("/{id}")
    public Banner finOne(@PathVariable("id") Banner banner) {
        return banner;
    }
}

优势:能控制到代码的每一行并非所有的增删改查都需要

劣势:在业务简单的情况下会编写大量的类似代码 这个时候我们可以用泛型与继承解决 引出 AbstractCrudController

2 extend BaseCrudController
使用抽象的能力,通过抽象类对相同的代码进行封装,减少子类继续编写重复的代码。
import com.st.cms.common.spring.jpa.AbstractEntity;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;

/**
 * @author yangrd
 * @date 2019/3/1
 **/
public abstract class BaseCrudController> {

    @Autowired
    protected D repository;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public T save(@RequestBody T t) {
        return repository.save(t);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable("id") String id) {
        repository.deleteById(id);
    }

    @PutMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void update(@PathVariable("id") T dbData, @RequestBody T t) {
        BeanUtils.copyProperties(t, dbData,"id");
        repository.saveAndFlush(dbData);
    }


    @GetMapping("/{id}")
    public T get(@PathVariable("id") T t) {
        return t;
    }

}

-

import com.st.cms.common.spring.mvc.BaseCrudController;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author yangrd
 * @date 2019/3/4
 **/
@AllArgsConstructor

@RestController
@RequestMapping("/api/banner")
public class BannerController extends BaseCrudController {

}

优势:在简单的crud操作中通过泛型与继承减少编写大量增删改查的方法

劣势:在findAll方法中入参数不好控制,通过HttpServletRequest可以解决这个问题 但有会引入大量的获取值的方法 因此BaseCrudController中不提供 findAll 方法 由用户编写

3 spring data rest
引入spring-boot-starter-data-rest,crud操作可以直接http调用 ,感兴趣的可以翻看官方文档
    
        org.springframework.boot
        spring-boot-starter-data-rest
    

优势:spring 家的东西 可以很好的与spring boot 整合 只需引入一个依赖即可

劣势:和之前业务中返回的数据格式内容不同 (此处也是好处 更统一规范 ,如果一开始前后端约定好数据格式就没有什么太大的问题)

4 ControllerHelper
重点来了哈哈 直接上代码
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;

import javax.persistence.EntityNotFoundException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author yangrd
 * @date 2019/3/22
 **/
@Slf4j

@RestController
@RequestMapping("/api")
public class ControllerHelper implements ApplicationContextAware {

    private MappingManager mappingManager;

    @PostMapping("/{repository}")
    public ResponseEntity create(@PathVariable String repository, @RequestBody String reqJSON) {
        return mappingManager.getJpaRepository(repository).map(repo -> {
            Object object = mappingManager.getEntityObj(repository);
            Object req = JSON.parseObject(reqJSON, object.getClass());
            BeanUtils.copyProperties(req, object);
            return ResponseEntity.status(HttpStatus.CREATED).body(repo.saveAndFlush(object));
        }).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{repository}/{id}")
    public ResponseEntity delete(@PathVariable String repository, @PathVariable Long id) {
        return mappingManager.getJpaRepository(repository).map(repo -> {
            repo.deleteById(id);
            return ResponseEntity.noContent().build();
        }).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    @PutMapping("/{repository}/{id}")
    public ResponseEntity update(@PathVariable String repository, @PathVariable Long id, @RequestBody String reqJSON) {
        return mappingManager.getJpaRepository(repository).map(repo -> {
            repo.findById(id).ifPresent(db -> {
                Object req = JSON.parseObject(reqJSON, db.getClass());
                BeanUtils.copyProperties(req, db);
                repo.saveAndFlush(db);
            });
            return ResponseEntity.noContent().build();
        }).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    @GetMapping("/{repository}/{id}")
    public ResponseEntity get(@PathVariable String repository, @PathVariable Long id) {
        return mappingManager.getJpaRepository(repository).map(repo -> ResponseEntity.ok(repo.findById(id))).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    @GetMapping("/{repository}")
    public ResponseEntity get(@PathVariable String repository, Pageable pageable) {
        return mappingManager.getJpaRepository(repository).map(repo -> ResponseEntity.ok(repo.findAll(pageable))).
                orElseGet(() -> ResponseEntity.notFound().build());
    }

    class MappingManager {
        private Map entity4Repositories = new HashMap<>();
        private Map entity4Class = new HashMap<>();

        MappingManager(ApplicationContext applicationContext) {
            Map repositoryBeans = applicationContext.getBeansOfType(JpaRepository.class);
            repositoryBeans.forEach((repositoryName, repositoryBean) -> {
                Class entityClass = MappingSupport.getEntityClass(repositoryBean);
                String entityClassName = MappingSupport.getEntityName(entityClass);
                entity4Repositories.put(entityClassName, repositoryBean);
                entity4Class.put(entityClassName, entityClass);
            });
        }

        public Optional getJpaRepository(String repository) {
            return Optional.ofNullable(entity4Repositories.get(repository));
        }

        public Object getEntityObj(String repository) {
            return Optional.ofNullable(entity4Class.get(repository)).map(clazz -> {
                try {
                    return clazz.newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
                return null;
            }).orElseThrow(EntityNotFoundException::new);
        }
    }

    static class MappingSupport {
        static Class getEntityClass(JpaRepository jpaRepository) {
            Type[] jpaInterfacesTypes = jpaRepository.getClass().getGenericInterfaces();
            Type[] type = ((ParameterizedType) ((Class) jpaInterfacesTypes[0]).getGenericInterfaces()[0]).getActualTypeArguments();
            return (Class) type[0];
        }

        static String getEntityName(Class clazz) {
            String simpleName = clazz.getSimpleName();
            return simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
        }
    }

    /**
     * @author yangrd
     * @date 2018/8/30
     **/
    static class BeanUtils {

        /**
         * 只拷贝不为null的属性
         *
         * @param source the source bean
         * @param target the target bean
         * @throws BeansException if the copying failed
         */
        public static void copyProperties(Object source, Object target) throws BeansException {
            org.springframework.beans.BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
        }

        private static String[] getNullPropertyNames(Object source) {
            final BeanWrapper src = new BeanWrapperImpl(source);
            PropertyDescriptor[] pds = src.getPropertyDescriptors();

            Set emptyNames = new HashSet<>();
            for (PropertyDescriptor pd : pds) {
                Object srcValue = src.getPropertyValue(pd.getName());
                if (srcValue == null) {
                    emptyNames.add(pd.getName());
                }
            }
            String[] result = new String[emptyNames.size()];

            return emptyNames.toArray(result);
        }
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Assert.notNull(applicationContext, "");
        this.mappingManager = new MappingManager(applicationContext);
    }
}

优势:对spring data rest弱势的补充可以在不改变 之前习惯的数据格式的情况下狠方便的前移, 最重要的是相比于 Abstract 方法可以被覆盖 如 findAll 如果你使用 如 api/user/{id} spring mvc会优先匹配它 而不是 api/{repository}/{id} ,后期可以根据自身业务需要打成jar包放在私服上面方便其他项目使用

劣势: 也是除了 第一种方式 之外其余三种都存在的问题 如果并不需要常用所有的五种操作 如何禁用 哈哈 解决方法是有的 我们下次再说 >_<

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73512.html

相关文章

  • Java学习路线总结,搬砖工逆袭Java架构师(全网最强)

    摘要:哪吒社区技能树打卡打卡贴函数式接口简介领域优质创作者哪吒公众号作者架构师奋斗者扫描主页左侧二维码,加入群聊,一起学习一起进步欢迎点赞收藏留言前情提要无意间听到领导们的谈话,现在公司的现状是码农太多,但能独立带队的人太少,简而言之,不缺干 ? 哪吒社区Java技能树打卡 【打卡贴 day2...

    Scorpion 评论0 收藏0
  • SpringBoot-vue 基于Java微服务全栈快速开发实践

    摘要:本项目将使用配合最简单的逻辑来展示一个基于的微服务全栈快速开发实践的。提供一系列大型项目常用的非功能性特征,比如内嵌服务器,安全,指标,健康检测,外部化配置。 SprintBoot-Vue SpringBoot + 前端MVVM 基于Java的微服务全栈快速开发实践 showImg(https://segmentfault.com/img/remote/1460000010167913...

    FleyX 评论0 收藏0
  • SpringBoot-vue 基于Java微服务全栈快速开发实践

    摘要:本项目将使用配合最简单的逻辑来展示一个基于的微服务全栈快速开发实践的。提供一系列大型项目常用的非功能性特征,比如内嵌服务器,安全,指标,健康检测,外部化配置。 SprintBoot-Vue SpringBoot + 前端MVVM 基于Java的微服务全栈快速开发实践 showImg(https://segmentfault.com/img/remote/1460000010167913...

    neu 评论0 收藏0
  • Spring Boot学习笔记(六)结合MyBatis实现较为复杂RESTful API

    摘要:前两篇已经构建了标准工程实例,也整合了实现了简单数据库访问,本篇主要更深入的学习下,实现较为完整的数据库的标准服务。到这里,最复杂的数据访问基本就算编写完了。 前两篇已经构建了RESTful API标准工程实例,也整合了MyBatis实现了简单数据库访问,本篇主要更深入的学习下,实现较为完整的数据库CRUD的标准服务。 首先看下要实现的效果吧,完成下面截图部分的API,除了CRUD之外...

    CntChen 评论0 收藏0
  • Spring Boot 2.x(五):整合Mybatis-Plus

    摘要:的作用可以看到,它给我们提供了一些核心的功能代码生成器和现成的接口以及可以结合的条件构造器使我们的代码变得足够优雅,分页的使用也是相当的方便,以及提供了不同的主键生成策略。 简介 Mybatis-Plus是在Mybatis的基础上,国人开发的一款持久层框架。 showImg(https://segmentfault.com/img/bVbvFk4?w=2022&h=862); 并且荣获...

    AaronYuan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<