SpringBoot整合MyBatis-Plus、新版-代码生成器、分页
一、添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<!-- mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<!-- mybatis plus 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<!-- 模板引擎 依赖 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!--pagehelper分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
<exclusion>
<artifactId>jsqlparser</artifactId>
<groupId>com.github.jsqlparser</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
二、配置yml、添加注解
server:
port: 9000
mybatis-plus:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
lazy-loading-enabled: true
multiple-result-sets-enabled: true
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置MyBatis日志,执行sql的时候,将sql打印到控制台
global-config:
banner: false
db-config:
id-type: auto
table-underline: true
logging:
config: classpath:logback-spring.xml
level:
com.shr.patrol.mapper: debug
并在启动类添加注解,如果在 mapper 文件中添加了 @Mapper
注解,下面的注解可以不加,否则会报重复扫描;
@MapperScan("com.shr.patrol")
三、创建代码生成类 CodeGenerator
/**
* 新版-代码生成
* 官网:https://www.baomidou.com/pages/981406/#%E6%95%B0%E6%8D%AE%E5%BA%93%E9%85%8D%E7%BD%AE-datasourceconfig
*/
public class CodeGenerator {
private static final String MYSQL_URL = "jdbc:mysql://nj-cdb-mw5khn49.sql.tencentcdb.com:63948/xsw_db";
private static final String MYSQL_USERNAME = "dev";
private static final String MYSQL_PASSWORD = "shrdb2021A@";
// 父包名
private static final String PARENT_PACKAGE = "com.shr.patrol";
public static void main(String[] args) {
//创建一个代码生成器
FastAutoGenerator fastAutoGenerator =
FastAutoGenerator.create(MYSQL_URL, MYSQL_USERNAME, MYSQL_PASSWORD);
// 全局配置(GlobalConfig)
globalConfig(fastAutoGenerator);
packageConfig(fastAutoGenerator);
strategyConfig(fastAutoGenerator);
templateConfig(fastAutoGenerator);
fastAutoGenerator.execute(); //执行以上配置
}
/**
* 全局配置(GlobalConfig)
*/
public static void globalConfig(FastAutoGenerator generator) {
// 项目路径
String projectPath = System.getProperty("user.dir");
generator.globalConfig(builder -> {
builder
// 设置作者,可以写自己名字
.author("orange")
// 开启 swagger 模式,这个是接口文档生成器,如果开启的话,就还需要导入swagger依赖
.enableSwagger()
// 覆盖已生成文件
.fileOverride()
//时间策略
.dateType(DateType.TIME_PACK)
//注释日期
.commentDate("yyyy-MM-dd")
// 指定输出目录,一般指定到java目录
.outputDir(projectPath + "\\src\\main\\java");
});
}
/**
* 包配置(PackageConfig)
*/
public static void packageConfig(FastAutoGenerator generator) {
// 项目路径
String projectPath = System.getProperty("user.dir");
generator.packageConfig(builder -> {
// 设置父包名
builder.parent(PARENT_PACKAGE)
// 设置父包模块名,这里一般不设置
.moduleName("")
// 设置mapperXml生成路径,这里是Mapper配置文件的路径,建议使用绝对路径
.pathInfo(
Collections.singletonMap(
OutputFile.mapperXml,
projectPath + "/src/main/resources/mapper")
);
});
}
/**
* 策略配置(StrategyConfig)
*/
public static void strategyConfig(FastAutoGenerator generator) {
generator.strategyConfig(builder -> {
builder.addInclude("biz_weight_log") // 设置需要生成的表名
.addTablePrefix("biz_"); // 设置过滤表前缀
// .addInclude("tbl_found") // 设置需要生成的表名
// 开启 lombok 模型
builder.entityBuilder().enableLombok();
builder.serviceBuilder()
// 设置service的命名策略,没有这个配置的话,生成的service和serviceImpl类前面会有一个I,比如IUserService和IUserServiceImpl
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl");
builder.controllerBuilder()
// 开启生成@RestController 控制器,不配置这个默认是Controller注解,RestController是返回Json字符串的,多用于前后端分离项目。
.enableRestStyle();
builder.mapperBuilder()
//开启 @Mapper 注解,也就是在dao接口上添加一个@Mapper注解,这个注解的作用是开启注解模式,就可以在接口的抽象方法上面直接使用@Select和@Insert和@Update和@Delete注解。
.enableMapperAnnotation();
});
}
/**
* 模板配置(TemplateConfig)
*/
public static void templateConfig(FastAutoGenerator generator) {
// .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
generator
.templateEngine(new VelocityTemplateEngine())
.templateConfig(builder -> {
builder.controller("/templates/controller.java");
});
}
}
四、创建统一响应结果返回对象
4.1、返回对象R
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class R<T> {
/**
* 状态码
*/
private Integer code;
/**
* 返回信息
*/
private String msg;
/**
* 数据
*/
private T data;
public static <T> R<T> response(Integer code, String msg, T data) {
R<T> result = new R<>();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
public static <T> R<T> success() {
return response(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getMsg(), null);
}
public static <T> R<T> success(T data) {
return response(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getMsg(), data);
}
public static <T> R<T> success(String msg, T data) {
return response(ResultCodeEnum.SUCCESS.getCode(), msg, data);
}
public static <T> R<T> fail() {
return response(ResultCodeEnum.FAIL.getCode(), ResultCodeEnum.FAIL.getMsg(), null);
}
public static <T> R<T> fail(String msg) {
return response(ResultCodeEnum.FAIL.getCode(), msg, null);
}
public static <T> R<T> fail(String msg, T data) {
return response(ResultCodeEnum.FAIL.getCode(), msg, data);
}
public static <T> R<T> response(ResultCodeEnum resultCodeEnum,T data) {
return response(resultCodeEnum.getCode(), resultCodeEnum.getMsg(), data);
}
}
4.2、枚举类
@NoArgsConstructor
@AllArgsConstructor
@Getter
public enum ResultCodeEnum {
SUCCESS(200, "操作成功"),
LOGOUT_SUCCESS(200, "注销成功"),
AUTHENTICATION_SUCCESS(200, "登录成功"),
FAIL(-1, "操作失败"),
NOT_AUTHENTICATION(401, "未认证请求"),
ACCESS_DENIED(403, "权限不足,访问被拒绝");
private int code;
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
五、添加模板
5.1、找到源码的生成模板文件
5.2、controller.java.vm 文件存放位置
5.3、编写 controller.java.vm
package ${package.Controller};
##自动生成代码模板
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
import org.springframework.web.bind.annotation.RequestMapping;
#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end
/**
* <p>
* $!{table.comment} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
@Api(tags = "${table.comment}接口")
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
#if(${kotlin})
class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end
#else
#if(${superControllerClass})
public class ${table.controllerName} extends ${superControllerClass} {
#else
public class ${table.controllerName} {
#end
@Autowired private ${table.serviceName} ${table.entityPath}Service;
@ApiOperation("分页")
@GetMapping("/page")
public R<Page<${entity}>> findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize) {
QueryWrapper<${entity}> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
return R.success(${table.entityPath}Service.page(new Page<>(pageNum, pageSize), queryWrapper));
}
@ApiOperation("保存")
@PostMapping("/save")
public R save(@RequestBody ${entity} ${table.entityPath}) {
return R.success(${table.entityPath}Service.saveOrUpdate(${table.entityPath}));
}
@ApiOperation("删除")
@DeleteMapping("/{id}")
public R delete(@PathVariable Integer id) {
return R.success(${table.entityPath}Service.removeById(id));
}
@ApiOperation("列表")
@GetMapping("/findAll")
public R<List<${entity}>> findAll() {
return R.success(${table.entityPath}Service.list());
}
@ApiOperation("获取")
@GetMapping("/{id}")
public R<${entity}> findOne(@PathVariable Integer id) {
return R.success(${table.entityPath}Service.getById(id));
}
}
#end
六、配置分页功能
package com.cyun.springsecurity.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
七、SQL日志配置
9.1、通过Mybatis日志配置【控制台输出】
-
修改yml
-
日志效果
9.2、通过日志框架配置【控制台输出】
推荐:适合用于开发,在控制台输出
-
修改yml
-
日志效果
9.3、通过xml配置【日志文件输出】
推荐:适合用于生产环境
- 编写xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<springProperty scope="context" name="log.path" source="spring.application.name"/>
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- <file>${log.path}.log</file>-->
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>logs/${log.path}/${log.path}_%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过10M,日志文件会以索引0开始,名日志文件,例如log-error-2013-12-21.0.log -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>2MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志最大的历史 30天 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<!--<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!– 过滤的级别 –>
<level>INFO</level>
<!– 匹配时的操作:接收(记录) –>
<onMatch>ACCEPT</onMatch>
<!– 不匹配时的操作:拒绝(不记录) –>
<onMismatch>DENY</onMismatch>
</filter>-->
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.shr" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<!--myibatis log configure-->
<logger name="com.apache.ibatis" level="debug"/>
<logger name="java.sql.Connection" level="info"/>
<logger name="java.sql.Statement" level="debug"/>
<logger name="java.sql.PreparedStatement" level="info"/>
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
</root>
</configuration>
- 修改yml
须同时开启 日志写入文件 及 开启控制台日志
- 日志效果
9.4、通过拦截器自定义日志输出【实现sql美化拼接】
9.4.1、定义一个拦截器及配置类
拦截器
package com.cyun.springsecurity.interceptor;
import com.cyun.springsecurity.config.SqlLogConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Properties;
/**
* mybatis拦截器
*/
@Component
@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MybatisInterceptor implements Interceptor {
@Autowired
private SqlLogConfig sqlLogConfig;
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (!sqlLogConfig.isEnabled()){
return invocation.proceed();
}
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
String sqlId = mappedStatement.getId();
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
long startTime = System.currentTimeMillis();
Object result = null;
try {
result = invocation.proceed();
} finally {
try {
long sqlCostTime = System.currentTimeMillis() - startTime;
String sql = getSql(configuration, boundSql);
formatSqlLog(mappedStatement.getSqlCommandType(), sqlId, sql, sqlCostTime, result);
} catch (Exception ignored) {
}
}
return result;
}
private String getSql(Configuration configuration, BoundSql boundSql) {
// 输入sql字符串空判断
String sql = boundSql.getSql();
if (StringUtils.isBlank(sql)) {
return "";
}
//美化sql
sql = beautifySql(sql);
//填充占位符, 目前基本不用mybatis存储过程调用,故此处不做考虑
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (!parameterMappings.isEmpty() && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = this.replacePlaceholder(sql, parameterObject);
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = replacePlaceholder(sql, obj);
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = replacePlaceholder(sql, obj);
}
}
}
}
return sql;
}
private String beautifySql(String sql) {
return sql.replaceAll("[\s\n ]+", " ");
}
private String replacePlaceholder(String sql, Object parameterObject) {
String result;
if (parameterObject instanceof String) {
result = "'" + parameterObject.toString() + "'";
} else if (parameterObject instanceof Date) {
result = "'" + getDate2String((Date) parameterObject) + "'";
} else {
result = parameterObject.toString();
}
return sql.replaceFirst("\?", result);
}
private String getDate2String(Date parameterObject) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(parameterObject);
}
private void formatSqlLog(SqlCommandType sqlCommandType, String sqlId, String sql, long costTime, Object obj) {
String log1 = String.format("DAO [%s]\n[%dms] ===> %s\n", sqlId, costTime, sql);
if (sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.INSERT || sqlCommandType == SqlCommandType.DELETE) {
log1 += "Count ===> " + obj;
}
if (costTime > 0) {
log.info(log1);
}
}
}
配置类
package com.cyun.springsecurity.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component("sqlLogConfig")
@ConfigurationProperties(prefix = "sqllog")
@Data
public class SqlLogConfig {
/**
* 是否开启记录SQL日志,默认为false.
*/
private boolean enabled;
/**
* 记录执行时间超过多少毫秒的语句,默认0,记录所有语句.
*/
private int minCost;
}
9.4.2、配置yml
新增配置
logging:
config: classpath:logback-spring.xml
# 这个配置要去掉,不然会以两种方式同时输出日志
# level:
# com.cyun.springsecurity.mapper: debug # 指定DAO层打印SQL日志
#################### sqllog ####################
# 记录sql
sqllog:
enabled: true
# 耗时下限ms
min-cost: 0