一、简介
因公司框架更新的缘故,需实现于JAVA17版本下的SpringBoot项目搭建,过程中也有遇到一些问题,希望借本文分享给大家。创作时间较紧,难免存在纰漏,望在评论区指出。
1.1 配置版本
具体使用到的各配置版本信息如下:
- JAVA17 当前最新版本为20,但害怕功能不稳定故选用JAVA17;
- SpringBoot 3.0.5 当前最新版本为3.1.0(SNAPSHOT),但是镜像上找不到该版本的spring-boot-starter-parent包,故退而求其次;
- MyBatis-Plus 3.5.3.1 官网上标注的最新版本为3.5.2,3.5.3.1是Maven能拉取到的最高版本的包。选用该版本是因为SpringBoot 3.0.0以上的版本无法使用springfox的包,而是使用springdoc的包,3.5.2版本不支持生成该包的对应注解,但是官网的文档并未对3.5.3.1版本的更新作说明;
- MySql 8.0.30 对应自己的安装版本即可;
- Swagger OpenAI 3 版本2无法支持SpringBoot 3.0.0以上的版本使用。
1.2 配置文件
pom.xml文件部分代码如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<!-- Spring Boot 相关配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 解决:No active profile set, falling back to default profiles: default -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Plus 相关配置 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!-- MySQL相关配置 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- Lombox相关配置 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Swagger配置 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.4</version>
</dependency>
1.3 项目结构
项目结构如下图所示:
二、项目搭建
2.1 SpringBoot项目搭建
进入Spring Initializr,根据下图完成配置,点击生成。
2.2 项目的初次启动
首次启动项目会发现控制台输出No active profile set, falling back to default profiles: default后即终止,在pom.xml中添加如下配置即可解决:
<!-- 解决:No active profile set, falling back to default profiles: default -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.3 MyBatis-Plus配置与自动生成代码实现
2.3.1 application.properties文件
配置信息如下:
server.port = 8081
spring.profiles.active=default
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/saas?useLegacyDatetimeCode=false&
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
2.3.2 Application文件
添加MapperScan扫描Dao层,代码如下:
package com.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.demo.dao")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2.3.3 MySQL代码生成器
⚠️ 注意:
- 笔者使用MacOS开发,WinOS可能需要修改对应的代码输出路径;
- MyBatis-Plus 3.5.3.1版本下,包配置中不存在other自定义文件的输出路径配置,需要通过重写配置引擎的自定义文件输出方法实现,自定义文件输出通过注入配置实现;
- 执行Main方法,即可生成对应表的代码;
- 自定义的模版文件放在resources下的templates包中,需对应自己引入的配置引擎选择,笔者选用的是velocity。自定义模版文件可以在mybatis-plus-generator库下的templates包中复制。
代码如下:
package com.example.demo.generator;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import jakarta.validation.constraints.NotNull;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MySQLGenerator{
public static void main(String[] args) {
Generation("saas", "sys_user");
}
/**
* 根据表名生成相应结构代码
*
* @param databaseName 数据库名
* @param tableName 表名
*/
public static void Generation(String databaseName, String tableName) {
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/" + databaseName + "?&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai", "root", "240010")
// 全局配置
.globalConfig(builder -> {
builder
// 禁止打开输出目录
.disableOpenDir()
// 作者名称
.author("")
// 启用swagger
.enableSpringdoc()
// 时间策略
.dateType(DateType.TIME_PACK)
// 注释日期
.commentDate("yyyy-MM-dd")
// 指定输出目录
.outputDir("src/main/java");
})
// 包配置
.packageConfig(builder -> {
builder
// 父包名
.parent("com.example.demo")
// 实体类包名
.entity("bean")
// 控制层包名
.controller("controller")
// 服务层包名
.service("service")
// 服务层实现类包名
.serviceImpl("service.impl")
// 映射层包名
.mapper("dao")
// 映射层XML包名
.xml("dao.xml")
.joinPackage("dto");
})
// 模版配置
.templateConfig(builder -> {
builder
// 实体类
.entity("/templates/entity.java")
// 控制类
.controller(("/templates/controller.java"))
// 服务类
.service("/templates/service.java")
// 服务实现类
.serviceImpl("/templates/serviceImpl.java")
// 映射
.mapper("/templates/mapper.java")
// 映射XML
.xml("/templates/mapper.xml");
})
// 策略配置
.strategyConfig(builder -> {
builder
// 增加表匹配(内存过滤)
.addInclude(tableName)
// 实体类策略配置
.entityBuilder()
// 实体类:开启lombok模型
.enableLombok()
// 实体类:开启链式模型
.enableChainModel()
// 实体类:数据库表映射到实体的命名策略,默认下划线转驼峰命名:NamingStrategy.underline_to_camel
.naming(NamingStrategy.underline_to_camel)
// 实体类:表字段映射实体属性命名规则,默认null,不指定按照naming执行
.columnNaming(NamingStrategy.underline_to_camel)
// 实体类:添加全局主键类型
.idType(IdType.AUTO)
// 实体类:格式化文件名称
.formatFileName("%s")
// 实体类:覆盖已生成文件
.enableFileOverride()
// 映射层策略配置
.mapperBuilder()
// 映射层:启用 BaseResultMap 生成
.enableBaseResultMap()
// 映射层:启用 BaseColumnList
.enableBaseColumnList()
// 映射层:格式化 mapper 文件名称
.formatMapperFileName("%sDao")
// 映射层:格式化 xml 实现类文件名称
.formatXmlFileName("%sMapper")
// 映射层:覆盖已生成文件
.enableFileOverride()
// 服务层策略配置
.serviceBuilder()
// 服务层:格式化 service 接口文件名称
.formatServiceFileName("I%sService")
// 服务层:格式化 service 实现类文件名称
.formatServiceImplFileName("%sServiceImpl")
// 服务层:覆盖已生成文件
.enableFileOverride()
// 控制层策略配置
.controllerBuilder()
// 控制层:覆盖已生成文件
.enableFileOverride()
// 控制层:开启生成@RestController 控制器
.enableRestStyle();
})
// 注入配置
.injectionConfig(consumer -> {
Map<String, Object> dtoMap = new HashMap<>();
// 传输类包名
dtoMap.put("dtoPackage", "dto");
// 传输类覆盖已生成文件
dtoMap.put("dtoOverride", true);
consumer
// 传输类自定义参数
.customMap(dtoMap)
// 传输类模版配置
.customFile(Collections.singletonMap("Dto.java", "/templates/dto.java.vm"));
})
// 配置引擎
.templateEngine(new EnhanceFreemarkerTemplateEngine())
.execute();
}
/**
* 重写Dto生成方法的路径
*/
public static final class EnhanceFreemarkerTemplateEngine extends VelocityTemplateEngine {
public void outputCustomFile(@NotNull List<CustomFile> customFiles, @NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap) {
String entityName = tableInfo.getEntityName();
String parentPath = this.getPathInfo(OutputFile.parent) + "/" + objectMap.get("dtoPackage");
customFiles.forEach((file) -> {
String filePath = StringUtils.isNotBlank(file.getFilePath()) ? file.getFilePath() : parentPath;
if (StringUtils.isNotBlank(file.getPackageName())) {
filePath = filePath + File.separator + file.getPackageName();
filePath = filePath.replaceAll("\\.", "\\" + File.separator);
}
String fileName = filePath + File.separator + entityName + file.getFileName();
this.outputFile(new File(fileName), objectMap, file.getTemplatePath(), (boolean) objectMap.get("dtoOverride"));
});
}
}
}
2.3.4 项目地址
我的GitCode代码生成器项目地址,希望多多Star。
2.4 Swagger OpenAI 3.0配置
官方文档:https://springdoc.org/v2/index.html#Introduction
2.4.1 application.properties文件
配置信息如下:
springdoc.packagesToScan=com.example.demo
springdoc.api-docs.groups.enabled=true
springdoc.group-configs[0].group=service
springdoc.group-configs[0].paths-to-match=/service/**
springdoc.group-configs[1].group=api
springdoc.group-configs[1].paths-to-match=/api/**
springdoc.api-docs.resolve-schema-properties=true
springdoc.swagger-ui.urlsPrimaryName=service
springdoc.swagger-ui.operations-sorter=method
2.4.2 SwaggerConfig配置文件
package com.example.demo;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI().info(new Info().title("基于Swagger3【DEMO】接口管理系统").version("v1.0").description("API接口"))
.components(new Components().addSecuritySchemes("bearer-key",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")));
}
}
2.5 MyBatis-Plus官方分页实现
2.5.1 源码分析
官网分页配置如下图所示
可以看到官网推荐的是在分页拦截器中配置对应的数据库类型,这个地方容易产生误导。我们的目标是实现一套工具类,如果每次都需要去修改是不切实际的。
找到源码设置数据库类型的位置:
public PaginationInnerInterceptor(DbType dbType) {
this.dbType = dbType;
}
protected IDialect findIDialect(Executor executor) {
if (this.dialect != null) {
return this.dialect;
} else if (this.dbType != null) {
this.dialect = DialectFactory.getDialect(this.dbType);
return this.dialect;
} else {
return DialectFactory.getDialect(JdbcUtils.getDbType(executor));
}
}
public static DbType getDbType(Executor executor) {
try {
Connection conn = executor.getTransaction().getConnection();
return (DbType)CollectionUtils.computeIfAbsent(JDBC_DB_TYPE_CACHE, conn.getMetaData().getURL(), JdbcUtils::getDbType);
} catch (SQLException var2) {
throw ExceptionUtils.mpe(var2);
}
}
可以看到,当传入的数据库参数为空时,方法会根据数据库连接自动判断对应的数据库类型。
2.5.2 MybatisPlusPageConfig配置代码
⚠️ 注意:如果配置成工具类,需要在Application中添加ComponentScan扫描该配置代码所属的包,否则代码无法执行。
@Configuration
public class MybatisPlusPageConfig {
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}