Mybatis-Plus快速入门_小蜗牛耶的博客-CSDN博客_mybatis-plus
回顾可看我写的这篇文章
下文用boot代替springboot
mp代替mybatisplus
按照本文目录结构即可快速开发一套完整的CRUD接口,包括后面的Swagger测试
这一套东西就是写接口对吧,所以写接口也就是写业务类,写业务类有个小口诀
那么我们用boot写微服务模块也有个小套路
业务类对标我们上面的顺序
建立新项目的时候可以用spring initializr初始化boot项目,也可以用maven的方式创建。这里我采用maven的方式来创建
pom出现的问题,跟网络环境有很大关系。
pom如果不断出错,可以更换网络或者删掉本地仓库中的包,重下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
<relativePath/>
parent>
<groupId>com.caqgroupId>
<artifactId>mybatisplusdemoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>mybatisplusdemoname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<scope>providedscope>
<version>2.7.0version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.7.0version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.0.5version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
这里我采用mysql8.0以上的驱动,配置url到时候?后面的值必须要加
驱动的名称和mysql5的也不一样记得区分
#端口号
server:
port: 8003
#服务名
spring:
application:
name: service-edu
#返回json的全局时间格式
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/mybatisplus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
profiles:
active: dev
# mybatis日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
package com.caq.mybatisplusdemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MybatisplusdemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisplusdemoApplication.class, args);
}
}
下面就开始写业务类了,业务类也遵守我们的步骤来写
CREATE TABLE user01
(
id int(20) NOT NULL COMMENT '主键ID' ,
name01 VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age01 INT(11) NULL DEFAULT NULL COMMENT '年龄',
email01 VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
DELETE FROM user01;
INSERT INTO user01 (id, name01, age01, email01) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
这里我们采用mp的插件,一键完成创建
安装插件
idea中连接数据库
右键你要一键生成实体类,dao,service的表格generator即可
生成说明
service接口说明
那么我们是不是要实现这么多方法呢?
当然不用,mp给我们定义好了一个IService的实现类,我们只需要实现类继承它并实现接口即可
调用service,service调用mapper(dao)
开发controller,service,dao的过程就叫开发接口
为了前后端更好的沟通,我们可以定义一个统一的返回类
状态码定义
package com.caq.commonutils;
public interface ResultCode {
//状态码:成功
public static Integer SUCCESS = 20000;
//状态码:失败
public static Integer ERROR = 20001;
}
统一返回类型R
package com.caq.commonutils;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* 结果类
*/
@Data
public class R<T> {
@ApiModelProperty(value = "是否成功")
private Boolean success;
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private Map<String, Object> data = new HashMap<>();
//私有的构造器
private R() {}
//成功的静态方法
public static R ok() {
R r = new R();
r.setSuccess(true);
r.setCode(ResultCode.SUCCESS);
r.setMessage("您的操作成功啦(*^▽^*)");
return r;
}
//失败的静态方法
public static R error() {
R r = new R();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage("您的操作失败啦(⊙︿⊙)");
return r;
}
//the follow methods all return this,链式编程
public R success(Boolean success){
this.setSuccess(success);
return this;
}
public R code(Integer code){
this.setCode(code);
return this;
}
public R message(String message){
this.setMessage(message);
return this;
}
public R data(String key, Object value){
this.data.put(key, value);
return this;
}
public R data(Map<String, Object> map){
this.setData(map);
return this;
}
}
好,我们记住了这些固定的步骤之后其实还有个更简单的方式哦!
那就是mp里面的代码生成器!
下面直接放代码,因为它是固定的,我们只需要会更改即可
public class CodeGenerator {
@Test
public void genCode() {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("Pyy");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.AUTO); //主键策略
gc.setSwagger2(true);//开启Swagger2模式
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/srb_core?serverTimezone=GMT%2B8&characterEncoding=utf-8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.caq");
pc.setEntity("entity"); //此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok
strategy.setLogicDeleteFieldName("is_deleted");//逻辑删除字段名
strategy.setEntityBooleanColumnRemoveIsPrefix(true);//去掉布尔值的is_前缀(确保tinyint(1))
strategy.setRestControllerStyle(true); //restful api风格控制器
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}
业务类搞好后,为了让接口的功能更完善,我们最后加上mp自带的插件
https://baomidou.gitee.io/mybatis-plus-doc/#/performance-analysis-plugin
mp的2.x文档更详细一点
下面分别介绍主要的插件和一些常见知识点
下面是我对实体类字段进行的设置,
这样设置的话我的主键在每次创建新用户的时候都会自动填充为分布式全局唯一ID 字符串类型
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "讲师ID")
/**
* 分布式应用时,我们需要生成分布式ID,可以选择使用@TableId(type=IdType.ID_WORKER),数据库中的主键为:
* IdType包括以下几类:
* AUTO : 数据库主键自增
* INPUT: 用户自行输入
* ID_WORKER: 分布式全局唯一ID, 长整型
* UUID: 32位UUID字符串
* NONE: 无状态
* ID_WORKER_STR: 分布式全局唯一ID 字符串类型
*/
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
自动填充一般应用在数据库创建时间或修改时间字段
[自动填充功能官网](自动填充功能 | MyBatis-Plus (baomidou.com))
package com.caq.servicebase.handle;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入的时候填充创建和修改字段
@Override
public void insertFill(MetaObject metaObject) {
//属性名称,不是字段名称
this.setFieldValByName("gmtCreate",new Date(),metaObject);
this.setFieldValByName("gmtModified",new Date(),metaObject);
}
//修改的时候填充修改字段
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtModified",new Date(),metaObject);
}
}
@ApiModelProperty(value = "创建时间")
@TableField(fill= FieldFill.INSERT)
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
@TableField(fill=FieldFill.INSERT_UPDATE)
private Date gmtModified;
锁是针对数据冲突的解决方案
悲观锁
正如其名,它指的是对数据被外界修改持保守(悲观),因此在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制
乐观锁
相对悲观锁而言,乐观锁假设认为数据一般情况下不会有冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现了冲突,则让返回用户错误的信息,让用户决定如何去做。乐观锁的实现方式一般是记录数据版本
乐观锁的实现方式
乐观锁配置需要两步
//乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
@Version
注解@Version
private Integer version;
只对自动注入的 sql 起效:
- 插入: 不作限制
- 查找: 追加 where 条件过滤掉已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
- 更新: 追加 where 条件防止更新到已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
- 删除: 转变为 更新
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
字段类型支持说明:
- 支持所有数据类型(推荐使用
Integer
,Boolean
,LocalDateTime
)- 如果数据库字段使用
datetime
,逻辑未删除值和已删除值支持配置为字符串null
,另一个值支持配置为函数来获取值如now()
附录:
- 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
- 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
@TableLogic
注解@TableLogic
private Integer deleted;
- 自定义查询语句分页(自己写sql/mapper)
- spring 注入 mybatis 配置分页插件
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
性能分析拦截器,用于输出每条 SQL 语句及其执行时间
注意!该插件只用于开发环境,不建议生产环境使用。。。
//性能分析插件
@Bean
@Profile({"dev","test"}) //设置dev test环境开启,保证效率
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//设置sql执行的最大时间ms
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
介绍完了mp的组件,要想使用只需要把它们写进一个配置类中让boot扫描即可
注入mp插件,完善接口功能
package com.caq.mybatisplusdemo.controller;
@Api("crud测试")
@RestController
@RequestMapping("/testMP/user")
public class crudDemo {
@Autowired
UserService userService;
@ApiOperation("增加用户")
@PostMapping("save")
public R saveUser(@RequestBody User user){
boolean save = userService.save(user);
if (save){
return R.ok();
}else {
return R.error();
}
}
@ApiOperation("查看所有用户")
@GetMapping("list")
public R listUser(){
List<User> list = userService.list(null);
return R.ok().data("items",list);
}
@ApiOperation("查看某个用户")
@GetMapping("getByIdUser")
public R getByIdUser(@PathVariable String id){
User user = userService.getById(id);
return R.ok().data("user",user);
}
@ApiOperation("按ID删除user")
@DeleteMapping("delete")
public R removeUser(@ApiParam(name = "id",value = "讲师ID",required = true)@PathVariable String id){
boolean delete = userService.removeById(id);
if (delete){
return R.ok();
}else {
return R.error();
}
}
@ApiOperation("按ID更改user")
@PostMapping
public R updateUser(@RequestBody User user){
boolean update = userService.updateById(user);
if (update){
return R.ok();
}else {
return R.error();
}
}
}
还是一样的套路,开局三连问
一款接口测试工具
对于后端开发人员来说
对于前端开发来说
对于测试
引入swagger的依赖
目前推荐使用2.7.0版本,因为2.6.0版本有bug
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<scope>providedscope>
<version>2.7.0version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.7.0version>
dependency>
@Configuration
@MapperScan("com.caq.mybatisplusdemo.mapper")
@EnableSwagger2
public class MpConfig {
@Bean
public Docket webApiConfig() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}
private ApiInfo webApiInfo() {
return new ApiInfoBuilder()
.title("mp测试")
.description("本文档描述了mp接口定义")
.version("1.0")
.contact(new Contact("java", "http://java.com", "[email protected]"))
.build();
}
}
@Api():用在请求的类上,表示对类的说明,也代表了这个类是swagger2的资源
参数:
tags:说明该类的作用,参数是个数组,可以填多个。
value="该参数没什么意义,在UI界面上不显示,所以不用配置"
description = "用户基本信息操作"
@ApiOperation():用于方法,表示一个http请求访问该方法的操作
参数:
value="方法的用途和作用"
notes="方法的注意事项和备注"
tags:说明该方法的作用,参数是个数组,可以填多个。
格式:tags={"作用1","作用2"}
(在这里建议不使用这个参数,会使界面看上去有点乱,前两个常用)
@ApiModel():用于响应实体类上,用于说明实体作用
参数:
description="描述实体的作用"
@ApiModelProperty:用在属性上,描述实体类的属性
参数:
value="用户名" 描述参数的意义
name="name" 参数的变量名
required=true 参数是否必选
@ApiImplicitParams:用在请求的方法上,包含多@ApiImplicitParam
@ApiImplicitParam:用于方法,表示单独的请求参数
参数:
name="参数ming"
value="参数说明"
dataType="数据类型"
paramType="query" 表示参数放在哪里
· header 请求参数的获取:@RequestHeader
· query 请求参数的获取:@RequestParam
· path(用于restful接口) 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
defaultValue="参数的默认值"
required="true" 表示参数是否必须传
@ApiParam():用于方法,参数,字段说明 表示对参数的要求和说明
参数:
name="参数名称"
value="参数的简要说明"
defaultValue="参数默认值"
required="true" 表示属性是否必填,默认为false
@ApiResponses:用于请求的方法上,根据响应码表示不同响应
一个@ApiResponses包含多个@ApiResponse
@ApiResponse:用在请求的方法上,表示不同的响应
参数:
code="404" 表示响应码(int型),可自定义
message="状态码对应的响应信息"
@ApiIgnore():用于类或者方法上,不被显示在页面上
@Profile({“dev”, “test”}):用于配置类上,表示只对开发和测试环境有用
下面我们就开始用Swagger来测试我们写的接口
登录swaggerUI
ip:prot/swagger-ui.html
逻辑删除我们是用Mp中的插件来实现的
所以在mp的配置类中添加逻辑删除插件即可
- 插入: 不作限制
- 查找: 追加 where 条件过滤掉已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
- 更新: 追加 where 条件防止更新到已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
- 删除: 转变为 更新
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
在开发中,我们一般做逻辑删除
所谓逻辑删除不是真正的删除,而是在逻辑上删除不是在数据库中删除
步骤一
//逻辑删除插件
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
步骤二
实体类字段上加上@TableLogic
注解
@TableLogic
private Integer deleted;
分页功能我们也是用Mp中的插件来实现的
所以在mp的配置类中添加分页插件即可
步骤一
分页插件
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
步骤二
分页Controller方法
@ApiOperation("分页查询讲师功能")
@GetMapping("pageTeacher/{current}/{limit}")
public R pageTeacher(@PathVariable long current,
@PathVariable long limit) {
//创建page对象
Page<EduTeacher> pageTeacher = new Page<>(current, limit);
//调用方法实现分页
//调用方法的时候,底层封装,把分页所有数据封装到pageTeacher对象里面
teacherService.page(pageTeacher, null);
long total = pageTeacher.getTotal();
List<EduTeacher> records = pageTeacher.getRecords();
Map<String, Object> map = new HashMap();
map.put("total", total);
map.put("rows", records);
return R.ok().data(map);
// 两种方式都可以
// return R.ok().data("total",total).data("rows",records);
}
@requestbody注解的作用是使用json传递数据,并把json封装到对应对象里面
面试题补充:
你经常用springboot中的那些注解?
@RequestBody、@ResponseBody、@PathVariable
前者是以json格式传递数据
后者是返回json格式数据
步骤一
创建查询对象
package com.caq.eduservice.entity.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class TeacherQuery {
// private Long current;
// private Long limit;
@ApiModelProperty(value = "教师名称,模糊查询")
private String name;
@ApiModelProperty(value = "头衔 1普通讲师 2高级讲师 3超级讲师")
private Integer level;
@ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换
// private Date begin;
@ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
private String end;
// private Date end;
}
步骤二
分页查询和多条件查询接口
TeacherQuery的属性根据前端需要的查询条件来设置
@RequestBody(required = false)表示增加参数TeacherQuery teacherQuery,非必选
@ApiOperation("分页查询和多条件查询")
@PostMapping("pageTeacherCondition/{current}/{limit}")
public R pageTeacherCondition(@PathVariable long current,
@PathVariable long limit,
@RequestBody(required = false) TeacherQuery teacherQuery) {
Page<EduTeacher> pageTeacher = new Page<>(current, limit);
//构建条件
QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
// 多条件组合查询
Integer level = teacherQuery.getLevel();
String name = teacherQuery.getName();
String begin = teacherQuery.getBegin();
String end = teacherQuery.getEnd();
//判断条件值是否为空,如果不为空拼接条件
if (!StringUtils.isEmpty(name)) {
//构建条件
wrapper.like("name", name);
}
if (!StringUtils.isEmpty(level)) {
wrapper.eq("level", level);
}
if (!StringUtils.isEmpty(begin)) {
wrapper.ge("gmt_create", begin);
}
if (!StringUtils.isEmpty(end)) {
wrapper.eq("gmt_modified", end);
}
//排序,新创建的在后面
wrapper.orderByDesc("gmt_create");
// 调用方法实现条件查询分页
teacherService.page(pageTeacher, wrapper);
long total = pageTeacher.getTotal();
List<EduTeacher> records = pageTeacher.getRecords();
return R.ok().data("total", total).data("rows", records);
}
当然我们也可以写条件表达式的形式
service
public interface EduTeacherService extends IService<EduTeacher> {
IPage<EduTeacher> pageList(Long current, Long limit, TeacherQuery teacherQuery);
}
impl
@Service
public class EduTeacherServiceImpl extends ServiceImpl<EduTeacherDao, EduTeacher> implements EduTeacherService {
public IPage<EduTeacher> pageList(Long current, Long limit, TeacherQuery teacherQuery) {
Integer level = teacherQuery.getLevel();
String name = teacherQuery.getName();
String begin = teacherQuery.getBegin();
String end = teacherQuery.getEnd();
LambdaQueryWrapper<EduTeacher> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(level!=null, EduTeacher::getLevel,level)
.like(StringUtils.isNotBlank(name),EduTeacher::getName,name)
.ge(begin!=null,EduTeacher::getGmtCreate,begin)
.le(end!=null, EduTeacher::getGmtModified,end);
return this.page(new Page<>(current,limit), queryWrapper);
}
}
@PostMapping("addTeacher")
public R addTeacher(@RequestBody EduTeacher eduTeacher){
boolean save = teacherService.save(eduTeacher);
if (save){
return R.ok();
}else {
return R.error();
}
}
@GetMapping("getTeacher/{id}")
public R getTeacher(@PathVariable String id){
EduTeacher eduTeacher = teacherService.getById(id);
return R.ok().data("teacher",eduTeacher);
}
我们修改讲师用的传入的参数是一个讲师对象,讲师对象里必须有id,因为我们修改讲师用的是id
//讲师修改功能
@PostMapping("updateTeacher")
public R updateTeacher(@RequestBody EduTeacher eduTeacher){
boolean flag = teacherService.updateById(eduTeacher);
if (flag){
return R.ok();
}else {
return R.error();
}
}
全局异常
//全局异常处理,当遇见Exception异常的时候调用error方法
@ExceptionHandler(Exception.class)
@ResponseBody//返回数据(它不在controller中所以要加上ResponseBody注解)
public R error(Exception e){
e.printStackTrace();
return R.error().message("执行了全局异常处理....");
}
特定和全局异常怎么选择呢?
先找特定异常,如果没有则找全局异常
// 特定异常
@ExceptionHandler(ArithmeticException.class)
@ResponseBody//返回数据(它不在controller中所以要加上ResponseBody注解)
public R error(ArithmeticException e){
e.printStackTrace();
return R.error().message("执行了ArithmeticException异常处理....");
}
第一步、
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Guliexception extends RuntimeException {
private Integer code;//状态码
private String msg;//异常信息
}
第二步、
// 自定义异常处理
@ExceptionHandler(Guliexception.class)
@ResponseBody//返回数据(它不在controller中所以要加上ResponseBody注解)
public R error(Guliexception e){
e.printStackTrace();
//这一套链式调用记得多debug
return R.error().code(e.getCode()).message(e.getMsg());
}
第三步、
//模拟一个异常
try {
int i = 10/0;
} catch (Exception e) {
/**执行自定义异常
* 传入的参数是自己写的异常类的构造方法的参数,这样能让代码更通用
*/
throw new Guliexception(20001,"执行了自定义异常处理.....");
}
测试
spring boot内部使用Logback作为日志实现的框架。
Logback和log4j非常相似,如果你对log4j很熟悉,那对logback很快就会得心应手。
logback相对于log4j的一些优点:https://blog.csdn.net/caisini_vc/article/details/48551287
删除 application.yml 中的日志配置
resources 中创建 logback-spring.xml
,名字固定的,不建议改
<configuration scan="true" scanPeriod="10 seconds">
<contextName>logbackcontextName>
<property name="log.path" value="D:\JavaStudy\gulixueyuan\logback"/>
<property name="CONSOLE_LOG_PATTERN"
value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level)
|%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFOlevel>
filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}Pattern>
<charset>UTF-8charset>
encoder>
appender>
<appender name="INFO_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/log_info.logfile>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
%logger{50} - %msg%n
pattern>
<charset>UTF-8charset>
encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-
dd}.%i.log
fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15maxHistory>
rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFOlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<appender name="WARN_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/log_warn.logfile>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
%logger{50} - %msg%n
pattern>
<charset>UTF-8charset>
encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-
dd}.%i.log
fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15maxHistory>
rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warnlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<appender name="ERROR_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/log_error.logfile>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
%logger{50} - %msg%n
pattern>
<charset>UTF-8charset>
encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-
dd}.%i.log
fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15maxHistory>
rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERRORlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<springProfile name="dev">
<logger name="com.guli" level="INFO"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
root>
springProfile>
<springProfile name="pro">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="WARN_FILE"/>
root>
springProfile>
configuration>
GlobalExceptionHandler.java 中
类上添加注解
不勾选根据当前时间戳更新即可
齐全的swagger注解介绍 - 知乎 (zhihu.com)
[后端 API 接口文档 Swagger 使用指南 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/98560871#:~:text=一:swagger是什么? Swagger是一款RESTFUL接口的文档在线自动生成%2B功能测试功能软件。,Swagger是一个规范和完整的框架%2C用于生成、描述、调用和可视化RESTful风格的Web服务。 目标是使客户端和文件系统作为服务器以同样的速度来更新文件的方法%2C参数和模型紧密集成到服务器。)
建模块之前,统一一下模块结构