- 一、模块开发
- 快速开发套路
- 建module
- 改pom
- yml
- 主启动
- 二、业务类开发
- 业务类(手动)
- 建表写sql
- 实体类、dao、service
- controller
- 统一返回类
- 业务类(自动)
- MP插件
- 主键生成策略
- 自动填充详解
- 配置类
- 定义字段
- 测试自动填充
- 乐观锁
- 配置插件
- 在实体类的字段上加上`@Version`注解
- 逻辑删除
- 配置
- 实体类字段上加上`@TableLogic`注解
- 分页
- 配置类
- 性能分析
- 配置类
- 通用CRUD
- 配置类
- CRUD接口
- 三、Swagger
- 是什么?
- 有什么好处?
- 怎么用?
- 引入依赖
- springBoot整合swagger
- swagger的注解
- 使用swagger需要注意的问题
- 四、功能测试
- Swagger测试
- 删除功能测试
- 讲师分页功能
- 分页查询和多条件查询
- 添加讲师
- 查询讲师
- 修改讲师
- 异常处理
- 特定异常处理
- 自定义异常处理
- 统一日志处理
- 配置logback日志
- 将错误日志输出到文件
- 五、Bug记录
- userMapper爆红
- MP自动填充时,修改时间和创建时间同步更改
- 六、优秀文章参考
Mybatis-Plus快速入门_小蜗牛耶的博客-CSDN博客_mybatis-plus
回顾可看我写的这篇文章
下文用boot代替springboot
mp代替mybatisplus
按照本文目录结构即可快速开发一套完整的CRUD接口,包括后面的Swagger测试
快速开发套路这一套东西就是写接口对吧,所以写接口也就是写业务类,写业务类有个小口诀
- 建表写sql
- 定义实体类
- dao与mapper
- service和impl
- controller
那么我们用boot写微服务模块也有个小套路
- 建module
- 改pom
- 写yml
- 主启动
- 业务类
业务类对标我们上面的顺序
建module建立新项目的时候可以用spring initializr初始化boot项目,也可以用maven的方式创建。这里我采用maven的方式来创建
改pompom出现的问题,跟网络环境有很大关系。
pom如果不断出错,可以更换网络或者删掉本地仓库中的包,重下
yml4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE com.caq mybatisplusdemo 0.0.1-SNAPSHOT mybatisplusdemo Demo project for Spring Boot 1.8 io.springfox springfox-swagger-ui provided 2.7.0 io.springfox springfox-swagger2 2.7.0 com.baomidou mybatis-plus-boot-starter 3.0.5 org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter mysql mysql-connector-java org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok
这里我采用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);
}
}
二、业务类开发
业务类(手动)
下面就开始写业务类了,业务类也遵守我们的步骤来写
建表写sqlCREATE 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, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
实体类、dao、service
这里我们采用mp的插件,一键完成创建
安装插件
idea中连接数据库
右键你要一键生成实体类,dao,service的表格generator即可
生成说明
service接口说明
那么我们是不是要实现这么多方法呢?
当然不用,mp给我们定义好了一个IService的实现类,我们只需要实现类继承它并实现接口即可
controller调用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业务类(自动){ @ApiModelProperty(value = "是否成功") private Boolean success; @ApiModelProperty(value = "返回码") private Integer code; @ApiModelProperty(value = "返回消息") private String message; @ApiModelProperty(value = "返回数据") private Map 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 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插件
业务类搞好后,为了让接口的功能更完善,我们最后加上mp自带的插件
https://baomidou.gitee.io/mybatis-plus-doc/#/performance-analysis-plugin
mp的2.x文档更详细一点
下面分别介绍主要的插件和一些常见知识点
主键生成策略下面是我对实体类字段进行的设置,
这样设置的话我的主键在每次创建新用户的时候都会自动填充为分布式全局唯一ID 字符串类型
private static final long serialVersionUID = 1L; @ApiModelProperty(value = "讲师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;测试自动填充 乐观锁
锁是针对数据冲突的解决方案
悲观锁
正如其名,它指的是对数据被外界修改持保守(悲观),因此在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制
乐观锁
相对悲观锁而言,乐观锁假设认为数据一般情况下不会有冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现了冲突,则让返回用户错误的信息,让用户决定如何去做。乐观锁的实现方式一般是记录数据版本
乐观锁的实现方式
- 取出记录,获取当前Version
- 更新时,带上这个version
- 执行更新时,set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
乐观锁配置需要两步
配置插件//乐观锁插件
@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;
}
通用CRUD
介绍完了mp的组件,要想使用只需要把它们写进一个配置类中让boot扫描即可
配置类CRUD接口注入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 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
还是一样的套路,开局三连问
是什么?一款接口测试工具
有什么好处?对于后端开发人员来说
- 不用再手写WiKi接口拼大量的参数,避免手写错误
- 对代码侵入性低,采用全注解的方式,开发简单
- 方法参数名修改、增加、减少参数都可以直接生效,不用手动维护
- 缺点:增加了开发成本,写接口还得再写一套参数配置
对于前端开发来说
- 后端只需要定义好接口,会自动生成文档,接口功能、参数一目了然
- 联调方便,如果出问题,直接测试接口,实时检查参数和返回值,就可以快速定位是前端还是后端的问题
对于测试
- 对于某些没有前端界面UI的功能,可以用它来测试接口
- 操作简单,不用了解具体代码就可以操作
- 操作简单,不用了解具体代码就可以操作
引入依赖引入swagger的依赖
目前推荐使用2.7.0版本,因为2.6.0版本有bug
springBoot整合swaggerio.springfox springfox-swagger-ui provided 2.7.0 io.springfox springfox-swagger2 2.7.0
@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", "534215342@qq.com"))
.build();
}
}
swagger的注解
@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需要注意的问题- 对于只有一个HttpServletRequest参数的方法,如果参数小于5个,推荐使用 @ApiImplicitParams的方式单独封装每一个参数;如果参数大于5个,采用定义一个对象去封装所有参数的属性,然后使用@APiParam的方式
- 默认的访问地址:ip:port/swagger-ui.html#/,但是在shiro中,会拦截所有的请求,必须加上默认访问路径(比如项目中,就是ip:port/context/swagger-ui.html#/),然后登陆后才可以看到
- 在GET请求中,参数在Body体里面,不能使用@RequestBody。在POST请求,可以使用@RequestBody和@RequestParam,如果使用@RequestBody,对于参数转化的配置必须统一
- controller必须指定请求类型,否则swagger会把所有的类型(6种)都生成出来
- swagger在生产环境不能对外暴露,可以使用@Profile({“dev”, “prod”,“pre”})指定可以使用的环境
下面我们就开始用Swagger来测试我们写的接口
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 pageTeacher = new Page<>(current, limit);
//调用方法实现分页
//调用方法的时候,底层封装,把分页所有数据封装到pageTeacher对象里面
teacherService.page(pageTeacher, null);
long total = pageTeacher.getTotal();
List records = pageTeacher.getRecords();
Map 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 pageTeacher = new Page<>(current, limit);
//构建条件
QueryWrapper 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 records = pageTeacher.getRecords();
return R.ok().data("total", total).data("rows", records);
}
当然我们也可以写条件表达式的形式
service
public interface EduTeacherService extends IService{ IPage pageList(Long current, Long limit, TeacherQuery teacherQuery); }
impl
@Service public class EduTeacherServiceImpl extends ServiceImpl添加讲师implements EduTeacherService { public IPage pageList(Long current, Long limit, TeacherQuery teacherQuery) { Integer level = teacherQuery.getLevel(); String name = teacherQuery.getName(); String begin = teacherQuery.getBegin(); String end = teacherQuery.getEnd(); LambdaQueryWrapper 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异常处理....");
}
自定义异常处理
- 创建自定义异常类继承RuntimeException,写异常属性
- 在统一异常类添加规则
- 执行自定义异常
第一步、
@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
配置logback日志删除 application.yml 中的日志配置
resources 中创建 logback-spring.xml,名字固定的,不建议改
将错误日志输出到文件logback INFO ${CONSOLE_LOG_PATTERN} UTF-8 ${log.path}/log_info.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/info/log-info-%d{yyyy-MM- dd}.%i.log 100MB 15 INFO ACCEPT DENY ${log.path}/log_warn.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/warn/log-warn-%d{yyyy-MM- dd}.%i.log 100MB 15 warn ACCEPT DENY ${log.path}/log_error.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/error/log-error-%d{yyyy-MM- dd}.%i.log 100MB 15 ERROR ACCEPT DENY
GlobalExceptionHandler.java 中
类上添加注解
五、Bug记录 userMapper爆红 MP自动填充时,修改时间和创建时间同步更改不勾选根据当前时间戳更新即可
六、优秀文章参考齐全的swagger注解介绍 - 知乎 (zhihu.com)
[后端 API 接口文档 Swagger 使用指南 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/98560871#:~:text=一:swagger是什么? Swagger是一款RESTFUL接口的文档在线自动生成%2B功能测试功能软件。,Swagger是一个规范和完整的框架%2C用于生成、描述、调用和可视化RESTful风格的Web服务。 目标是使客户端和文件系统作为服务器以同样的速度来更新文件的方法%2C参数和模型紧密集成到服务器。)
建模块之前,统一一下模块结构



