Spring Boot+Gradle+ MyBatis-Plus3.x搭建企业级的后台分离框架

    科技2022-08-01  99

    点击上方“Java基基”,选择“设为星标”

    做积极的人,而不是积极废人!

    源码精品专栏

     

    原创 | Java 2020 超神之路,很肝~

    中文详细注释的开源项目

    RPC 框架 Dubbo 源码解析

    网络应用框架 Netty 源码解析

    消息中间件 RocketMQ 源码解析

    数据库中间件 Sharding-JDBC 和 MyCAT 源码解析

    作业调度中间件 Elastic-Job 源码解析

    分布式事务中间件 TCC-Transaction 源码解析

    Eureka 和 Hystrix 源码解析

    Java 并发源码

    来源:toutiao.com/i6861456496740270604

    1、技术选型

    解析器:FastJSON

    开发工具:JDK1.8 、Gradle、IDEA

    技术框架:SpringBoot 2.1.5.RELEASE

    ORM技术:MyBatisPlus3.1.2

    数据库:MySQL8.0.21

    Apache 工具:HttpClient、Lang3

    Git代码版本控制

    Web服务器:undertow

    hutool 国产工具类包

    lombok 简化代码工具 -druid 数据库连接池框架

    2、Spring Boot 发展路线简要描述

    随着动态语言的流行 (Ruby、Groovy、Scala、Node.js),Java 的开发显得格外的笨重:繁多的配置、低下的开发效率、复杂的部署流程以及第三方技术集成难度大。

    在上述环境下,Spring Boot 应运而生。它使用“习惯优于配置”(项目中存在大量的配置,此外还内置了一个习惯性的配置,让你无需手动进行配置)的理念让你的项目快速的运行起来。使用 Spring Boot 很容易创建一个独立运行(运行 Jar,内嵌 Servlet 容器)准生产级别的基于 Spring 框架的项目,使用 Spring Boot 你可以不用或者只需很少的 Spring 配置。

    3、SpringBoot插件使用

    spring-boot-devtools 实现热部署,实际开发过程中,修改应用的业务逻辑时常常需要重启应用,这显得非常繁琐,降低了开发效率,所以热部署对于开发来说显得十分必要了

    spring-boot-starter-aop 此插件没什么好说的了,aop是spring的两大功能模块之一,功能非常强大,为解耦提供了非常优秀的解决方案。如:面向方面编程

    spring-boot-starter-undertow 与spring boot 内置undertow 插件

    spring-boot-starter-test 测试工具

    mybatis-plus-boot-starter 与spring boot整合MyBatisPlus的jar

    spring-boot-configuration-processor 整合SpringBoot配置提示

    4、fastJson

    阿里JSON解析器,详细文档请看官方 https://github.com/alibaba/fastjson

    5、Hutool

    Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。

    6、Gradle

    Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,目前也增加了基于Kotlin语言的kotlin-based DSL,抛弃了基于XML的各种繁琐配置

    官方 https://gradle.org/

    不会Gradle的先自己补习一下,比如:安装Gradle,配置环境变量,一些jar引入如何配置,基本使用怎么使用

    7、工程结构

    此工程是通过Kotlin+SpringBoot+MyBatisPlus搭建最简洁的前后端分离框架 框架升级Java语言SpringBoot+MyBatisPlus3.X+Gradle版本的框架,想学习Kotlin版本的请点击蓝色文章进行下载源代码。

    8、Gradle配置

    plugins {     id 'java'     id 'idea' } /**  * 使用Groovy语言语法定义版本号变量  */ def spring_boot_version = "2.1.5.RELEASE" def mybatis_plus_version = "3.1.2" def mysql_version = "8.0.21" def druid_version = "1.1.23" def logback_version = "1.2.1" def fastjson_version = "1.2.73" def lombok_version = "1.18.12" def lang_version = "3.4" def io_version = "2.6" def guava_version = "18.0" def hutool_version = "5.3.10" group = 'com.flong' version = '0.0.1-SNAPSHOT' //jdk版本 sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 repositories {     //指定阿里云镜像     maven {         url 'http://maven.aliyun.com/nexus/content/groups/public/'     }     mavenLocal()     mavenCentral() } /**  * 1、implementation 履行 、compile 编译  * 2、Gradle使用双引号可 ${变量}可以放入引号里面,单引号是不可以的。  * 3、Gragle使用lombok需要引入annotationProcessor注解,否则不能使用lombok.  * 4、mybatis-plus3.2.x以上版本引用了Kotlin的支持  * 5、高版本Springboogt在spring-boot-dependencies-2.3.0.RELEASE.pom里面引入了mysql8.0.2的`<mysql.version>8.0.20</mysql.version>`配置  */ dependencies {     implementation "org.springframework.boot:spring-boot-starter:${spring_boot_version}"     //排除tomcat使用undertow     compile("org.springframework.boot:spring-boot-starter-web:${spring_boot_version}") {         exclude module: "spring-boot-starter-tomcat"     }     compile "org.springframework.boot:spring-boot-starter-undertow:${spring_boot_version}"     //runtime group: 'mysql', name: 'mysql-connector-java', version: '5.1.42'     compile "org.springframework.boot:spring-boot-devtools:${spring_boot_version}"     compile "org.springframework.boot:spring-boot-configuration-processor:${spring_boot_version}"     compile "org.springframework.boot:spring-boot-starter-test:${spring_boot_version}"     compile "com.baomidou:mybatis-plus-extension:${mybatis_plus_version}"     compile "com.baomidou:mybatis-plus-boot-starter:${mybatis_plus_version}"     compile "mysql:mysql-connector-java:${mysql_version}"     compile "com.alibaba:druid:${druid_version}"     compile "ch.qos.logback:logback-classic:${logback_version}"     compile "com.alibaba:fastjson:${fastjson_version}"     annotationProcessor "org.projectlombok:lombok:${lombok_version}"     compileOnly "org.projectlombok:lombok:${lombok_version}"     //testAnnotationProcessor "org.projectlombok:lombok:${lombok_version}"     //testCompileOnly "org.projectlombok:lombok:${lombok_version}"     compile "org.apache.commons:commons-lang3:${lang_version}"     compile "commons-io:commons-io:${io_version}"     compile "com.google.guava:guava:${guava_version}"     compile  "cn.hutool:hutool-all:${hutool_version}" } tasks.withType(JavaCompile) {     options.encoding = "UTF-8" } [compileJava, javadoc, compileTestJava]*.options*.encoding = "UTF-8"

    9、数据库SQL脚本

    -- 创建表字段不建议用is开头,在我Kotlin+Springboot+MyBatisPlus2.x整合也提到此问题,

    -- 故此整合MyBatisPlus3.x版本,把表的is_deleted字段修改成del_flag,阿里开发手册也提到此问题.

    DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` (   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',   `user_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用户Id主键,IdWork生成',   `user_name` varchar(255) DEFAULT '' COMMENT '用户名',   `pass_word` varchar(255) DEFAULT '' COMMENT '密码',   `del_flag` int(2) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除,0-不删除,1-删除',   `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',   PRIMARY KEY (`user_id`) USING BTREE,   UNIQUE KEY `id` (`id`)USING BTREE )ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

    10、SpringBoot与MyBatisPlus3整合分页代码

    @Configuration public class MyBatisPlusConfig {     /**      * 分页插件      */     @Bean     public PaginationInterceptor paginationInterceptor() {         return new PaginationInterceptor();     } }

    11、SpringBoot与MyBatisPlus3分页条件组装器

    /**  * 将condition数据转换成wrapper  */ public class BuildConditionWrapper {     public static <T> QueryWrapper<T> build(List<Condition> conditions, Class<T> clazz) {         //初始化mybatis条件构造器         QueryWrapper wrapper = Wrappers.query();         if (conditions == null || conditions.size() == 0) {             return wrapper;         }         try {             for (int i = 0; i < conditions.size(); i++) {                 Condition condition = conditions.get(i);                 if (condition.getFieldName() == null) {                     throw new BaseException("调用搜索接口时,缺少关键字[fieldName]!");                 }                 //列名称                 String columnName = getColumnName(condition.getFieldName(), clazz);                 if (condition == null || condition.getOperation() == null) {                     throw new BaseException("操作符不能为空!");                 }                 switch (condition.getOperation()) {                     //等于                     case EQ:                         wrapper.eq(columnName, condition.getValue());                         break;                     //大于                     case GT:                         wrapper.gt(columnName, condition.getValue());                         break;                     //小于                     case LT:                         wrapper.lt(columnName, condition.getValue());                         break;                     //不等于                     case NEQ:                         wrapper.ne(columnName, condition.getValue());                         break;                     //大于等于                     case GTANDEQ:                         wrapper.ge(columnName, condition.getValue());                         break;                     //小于等于                     case LTANDEQ:                         wrapper.le(columnName, condition.getValue());                         break;                     case LIKE:                         wrapper.like(columnName, condition.getValue());                         break;                     case ISNULL:                         wrapper.isNull(columnName);                         break;                     case IN:                         //value :1,2,3,4,5,6                         wrapper.inSql(columnName, condition.getValue());                         break;                     default:                         break;                 }                 if (condition.getJoinType() == JoinType.OR && i < conditions.size() - 1) {                     //下个条件为or连接且非最后一个条件,使用or进行连接                     wrapper.or();                 }             }             return wrapper;         } catch (Exception e) {             throw new BaseException("查询条件不存在");         }     }     /**      * @Descript 此条件构建包装器方法是支持多个表组装成SQL字段的虚拟表,不支持实际存在的表      * @Date 2019/6/21 13:32      * @Author liangjl      */     public static <T> QueryWrapper<T> buildWarpper(List<Condition> conditions) {         //初始化mybatis条件构造器         QueryWrapper wrapper = Wrappers.query();         if (conditions == null || conditions.size() == 0) {             return wrapper;         }         try {             for (int i = 0; i < conditions.size(); i++) {                 Condition condition = conditions.get(i);                 if (condition.getFieldName() == null) {                     throw new BaseException("调用搜索接口时,缺少关键字[fieldName]!");                 }                 //列名称                 String columnName = condition.getFieldName();                 if (condition == null || condition.getOperation() == null) {                     throw new BaseException("操作符不能为空!");                 }                 switch (condition.getOperation()) {                     //等于                     case EQ:                         wrapper.eq(columnName, condition.getValue());                         break;                     //大于                     case GT:                         wrapper.gt(columnName, condition.getValue());                         break;                     //小于                     case LT:                         wrapper.lt(columnName, condition.getValue());                         break;                     //不等于                     case NEQ:                         wrapper.ne(columnName, condition.getValue());                         break;                     //大于等于                     case GTANDEQ:                         wrapper.ge(columnName, condition.getValue());                         break;                     //小于等于                     case LTANDEQ:                         wrapper.le(columnName, condition.getValue());                         break;                     case LIKE:                         wrapper.like(columnName, condition.getValue());                         break;                     case IN:                         //value :1,2,3,4,5,6                         wrapper.inSql(columnName, condition.getValue());                         break;                     default:                         break;                 }                 if (condition.getJoinType() == JoinType.OR && i < conditions.size() - 1) {                     //下个条件为or连接且非最后一个条件,使用or进行连接                     wrapper.or();                 }             }             return wrapper;         } catch (Exception e) {             throw new BaseException("查询条件不存在");         }     }     /***      * @Descript 获取指定实体Bean的字段属性      * @Date 2019/6/19 14:51      * @Author liangjl      */     public static String getColumnName(String fieldName, Class clazz) {         try {             //获取泛型类型字段             Field field = clazz.getDeclaredField(fieldName);             TableField tableFieldAnno = field.getAnnotation(TableField.class);             String columnName = "";             //获取对应数据库字段             if (tableFieldAnno != null && StrUtil.isNotBlank(tableFieldAnno.value())) {                 //已定义数据库字段,取定义值                 columnName = tableFieldAnno.value();             } else {                 //未指定数据库字段,默认驼峰转下划线                 columnName = NamingStrategyUtils.camelToUnderline(field.getName());             }             return columnName;         } catch (NoSuchFieldException e) {             throw new BaseException("查询条件不存在");         }     } }

    12、 实体

    @Data @Builder @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode(callSuper=false) @TableName("t_user") public class User extends Model<User> implements Serializable {     @TableId(type = IdType.ID_WORKER)     private Long userId;     /**      * 用户名      */     private String userName;     /**      * 密码      */     private String passWord;     /**      * 逻辑删除(0-未删除,1-已删除)      */     @TableLogic     private String delFlag;     /**      * 创建时间,允许为空,让数据库自动生成即可      */     private Date createTime; }

    13、 Mapper

    BaseMapper是继承了mybatisplus底层的代码

    public interface UserMapper extends BaseMapper<User> { }

    14、 Service

    ServiceImplr是继承了mybatisplus底层的代码

    @Service public class UserService extends ServiceImpl<UserMapper, User> { }

    15 、controller

    控制层主要实现CURD,增加,修改,查询,删除、分页无大常规操作接口操作

    用户分页,参数有多个使用下标索引进行处理.如果有两个参数(如用户名和地址):conditionList[0].fieldName=userName、 conditionList[0].fieldName=address

    查询是通过反射fieldName进行去获取表结构userName、address 字段的。

    未转码请求分页地址: http://localhost:7011/user/page?conditionList[0].fieldName=userName&conditionList[0].operation=LIKE&conditionList[0].value=周

    已转码请求分页地址: http://localhost:7011/user/page?conditionList[0].fieldName=userName&conditionList[0].operation=LIKE&conditionList[0].value=周

    /**  * @Author:liangjl  * @Date:2020-08-16  * @Description:用户控制层  */ @RestController @RequestMapping("/user") public class UserController {     @Autowired     private UserMapper userMapper;     @Autowired     private UserService userService;     /**      * 添加      */     @RequestMapping("/add")     public void add() {         userMapper.insert(User.builder().userName("周伯通").passWord("123456").build());     }     /**      * 修改      * @param user      */     @PutMapping("/updateById")     public void updateById(@RequestBody User user) {         userMapper.updateById(user);     }     /**      * 删除通过多个主键Id进行删除      * @param ids      */     @DeleteMapping("/deleteByIds")     public void deleteByIds(@RequestBody List<String> ids) {         userMapper.deleteBatchIds(ids);     }     /**      * 通过指定Id进行查询      *      * @param userId      */     @GetMapping("/getOne/{userId}")     public void getOne(@PathVariable("userId") Long userId) {         User user = userMapper.selectById(userId);         System.out.println(JSON.toJSON(user));     }     /**      * 用户分页,参数有多个使用下标索引进行处理.如果有两个参数(如用户名和地址):conditionList[0].fieldName=userName、 conditionList[0].fieldName=address      * 未转码请求分页地址: http://localhost:7011/user/page?conditionList[0].fieldName=userName&conditionList[0].operation=LIKE&conditionList[0].value=周      * 已转码请求分页地址: http://localhost:7011/user/page?conditionList[0].fieldName=userName&conditionList[0].operation=LIKE&conditionList[0].value=周      * @param page      * @param conditions 条件      * @return      */     @GetMapping("/page")     public IPage<User> page(Page page, Conditions conditions) {         QueryWrapper<User> build = BuildConditionWrapper.build(conditions.getConditionList(), User.class);         //通过lambda反射找到User实体类的createTime自动进行排序         build.lambda().orderByDesc(User::getCreateTime);         return userService.page(page, build);     } }

    16、WebCofig工具类统一处理配置

    消息转换器,中文乱码,Long的精度长度问题,时间格式等问题

    cors 跨域支持 可以用@CrossOrigin在controller上单独设置

    统一处理请求URL拦截器

    @Configuration @ConditionalOnClass(WebMvcConfigurer.class) @Order(Ordered.HIGHEST_PRECEDENCE) public class WebConfig implements WebMvcConfigurer {   @Bean   public HttpMessageConverters customConverters() {     //创建fastJson消息转换器     FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();     //创建配置类     FastJsonConfig fastJsonConfig = new FastJsonConfig();     //修改配置返回内容的过滤     fastJsonConfig.setSerializerFeatures(         // 格式化         SerializerFeature.PrettyFormat,         // 可解决long精度丢失 但会有带来相应的中文问题         //SerializerFeature.BrowserCompatible,         // 消除对同一对象循环引用的问题,默认为false(如果不配置有可能会进入死循环)         SerializerFeature.DisableCircularReferenceDetect,         // 是否输出值为null的字段,默认为false         SerializerFeature.WriteMapNullValue,         // 字符类型字段如果为null,输出为"",而非null         SerializerFeature.WriteNullStringAsEmpty,         // List字段如果为null,输出为[],而非null         SerializerFeature.WriteNullListAsEmpty     );     // 日期格式     fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");     // long精度问题     SerializeConfig serializeConfig = SerializeConfig.globalInstance;     serializeConfig.put(BigInteger.class, ToStringSerializer.instance);     serializeConfig.put(Long.class, ToStringSerializer.instance);     serializeConfig.put(Long.TYPE, ToStringSerializer.instance);     fastJsonConfig.setSerializeConfig(serializeConfig);     //处理中文乱码问题     List<MediaType> fastMediaTypes = new ArrayList<>();     fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);     fastJsonConverter.setSupportedMediaTypes(fastMediaTypes);     fastJsonConverter.setFastJsonConfig(fastJsonConfig);     //将fastjson添加到视图消息转换器列表内     return new HttpMessageConverters(fastJsonConverter);   }   /**    * 拦截器    */   @Override   public void addInterceptors(InterceptorRegistry registry) {     //registry.addInterceptor(logInterceptor).addPathPatterns("/**");     //registry.addInterceptor(apiInterceptor).addPathPatterns("/**");   }   /**    * cors 跨域支持 可以用@CrossOrigin在controller上单独设置    */   @Override   public void addCorsMappings(CorsRegistry registry) {     registry.addMapping("/**")         //设置允许跨域请求的域名         .allowedOrigins("*")         //设置允许的方法         .allowedMethods("*")         //设置允许的头信息         .allowedHeaders("*")         //是否允许证书 不再默认开启         .allowCredentials(Boolean.TRUE);   } }

    17、运行结果

    添加 http://localhost:7011/user/add

    分页 http://localhost:7011/user/page?conditionList[0].fieldName=userName&conditionList[0].operation=LIKE&conditionList[0].value=周

    默认当前current 为1当前第一页,size为10,当前页可以显示10条数据,也可以根据自己的情况进行自定义

    http://localhost:7011/user/page?current=1&size=20&conditionList[0].fieldName=userName&conditionList[0].operation=LIKE&conditionList[0].value=周

    18、工程代码与说明

    1 、以上问题都是根据自己学习实际情况进行总结整理,除了技术问题查很多网上资料通过进行学习之后梳理。

    2、 在学习过程中也遇到很多困难和疑点,如有问题或误点,望各位老司机多多指出或者提出建议。本人会采纳各种好建议和正确方式不断完善现况,人在成长过程中的需要优质的养料。

    3、 导入代码的时候遇到最多的问题,我想应该是Maven较多,此时不懂maven的童鞋们可以通过自身情况,进行网上查资料学习。如通过网上找资料长时间解决不了,或者框架有不明白可以通过博客留言,在能力范围内会尽力帮助大家解决问题所在,希望在过程中一起进步,一起成长。

    工程代码在 base 分支 https://github.com/jilongliang/springboot/tree/base



    欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

    已在知识星球更新源码解析如下:

    最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

    提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

    获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

    Processed: 0.017, SQL: 8