⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 toutiao.com/i6861456496740270604 「任务加油站」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

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=%E5%91%A8

/**
* @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=%E5%91%A8
* @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=%E5%91%A8
  • 默认当前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=%E5%91%A8

18、工程代码与说明

  • 1 、以上问题都是根据自己学习实际情况进行总结整理,除了技术问题查很多网上资料通过进行学习之后梳理。
  • 2、 在学习过程中也遇到很多困难和疑点,如有问题或误点,望各位老司机多多指出或者提出建议。本人会采纳各种好建议和正确方式不断完善现况,人在成长过程中的需要优质的养料。
  • 3、 导入代码的时候遇到最多的问题,我想应该是Maven较多,此时不懂maven的童鞋们可以通过自身情况,进行网上查资料学习。如通过网上找资料长时间解决不了,或者框架有不明白可以通过博客留言,在能力范围内会尽力帮助大家解决问题所在,希望在过程中一起进步,一起成长。
  • 工程代码在 base 分支 https://github.com/jilongliang/springboot/tree/base
文章目录
  1. 1. 1、技术选型
  2. 2. 2、Spring Boot 发展路线简要描述
  3. 3. 3、SpringBoot插件使用
  4. 4. 4、fastJson
  5. 5. 5、Hutool
  6. 6. 6、Gradle
  7. 7. 7、工程结构
  8. 8. 8、Gradle配置
  9. 9. 9、数据库SQL脚本
  10. 10. 10、SpringBoot与MyBatisPlus3整合分页代码
  11. 11. 11、SpringBoot与MyBatisPlus3分页条件组装器
  12. 12. 12、 实体
  13. 13. 13、 Mapper
  14. 14. 14、 Service
  15. 15. 15 、controller
  16. 16. 16、WebCofig工具类统一处理配置
  17. 17. 17、运行结果
  18. 18. 18、工程代码与说明