Java – SpringBoot – 基础整合实例
简介
本文通过SpringBoot完整的整合实例,讲解如何使用SpringBoot从搭建到页面RESTful请求的全过程分析。
整合项目下载
https://www.tzming.com/wp-content/uploads/2022/filedown/SpringBoot_SSMP.rar
创建SpringBoot项目
创建SpringBoot项目可参阅以下文章
整合Mybatis-Plus
用于控制SQL数据操作的技术。
关于整合Mybatis-Plus可参阅以下文章
Mybatis-Plus 配置
# mybatis-plus 配置
# table-prefix 设置mp 访问数据表的前缀
# id-type 设置mp插入数据的id自增做生成规则
# log-impl 设置mp查询数据时的日志
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
创建POJO实体类
用于绑定数据表与JavaBean对象关系的类。
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
使用lombok生成get/set方法
如果不想自己创寻get/set方法,可以使用lombok插件自动生成
<!-- lombok不需要版本是因为SpringBoot中的Starter中已包含对应版本 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
通过【@Data】注解自动生成get/set方法
@Data
public class Book { }
创建DAO/Mapper层
数据层,也称DAO或Mapper层,它是用于直接控制数据库操作的类对象,Mybatis中被称为Mapper映射,在Spring中,数据层会交由Spring管理,并自动装配到Services层中。
Mybatis-Plus 预生成Dao查询
为了方便Mapper层对数据库的操作,Mybatis-Plus 提供了预先编写好的通用数据库操作方法,其类【BaseMapper<T>】已经为我们创建好了一般常用的查询数据库的方法,我们的Dao层中只需要继承它,就可以不需编写代码实现大部分的通用数据库查询操作。
// 记得定义 Bean 管理
@Mapper
public interface BookMapper extends BaseMapper<Book> { }
测试用例
我们可以通过测试用例,来测试Dao层工作是否正常
@SpringBootTest
public class MyBatisPlusTestCase {
@Autowired
private BookMapper bookMapper;
@Test
public void SelectById() {
System.out.println(bookMapper.selectById(1));
}
@Test
public void save() {
Book b = new Book();
b.setName("Java 从入门到精通");
b.setDescription("Java 从入门到精通");
b.setType("Java");
bookMapper.insert(b);
}
@Test
public void SelectAll() {
System.out.println(bookMapper.selectList(null));
}
/**
* 修改一个数据
*/
@Test
public void Update() {
Book b = new Book();
b.setId(2);
b.setName("Python 从入门到入狱");
b.setDescription("Python 从入门到入狱");
b.setType("Python");
bookMapper.updateById(b);
}
/**
* 删除一个数据
*/
@Test
public void Delete() {
bookMapper.deleteById(1);
}
/**
* 查询分页
* 分页查询MybatisPlus中默认不自带
* 需要我们通过配置过滤器增加分页功能
*/
@Test
public void GetPage() {
IPage page = new Page(1, 5);
bookMapper.selectPage(page, null);
System.out.println(page.getRecords());
}
/**
* 按条件查询
*/
@Test
public void GetBy() {
String name = "java";
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
lqw.like(Book::getName,name);
bookMapper.selectList(lqw);
}
}
关于分页查询
分页查询,可详细查看以下文章的selectPage
章节了解
我们知道,分页实际上是Mybatis-Plus的一个插件对象,默认情况下不自动创建对象,而是由【过滤器】进行管理,而【过滤器】是一个对象,在Spring中,对象应作为Bean给于Spring进行管理。因此,我们需要在Bean中配置【过滤器】,在本章节中,我们使用配置类来创建一个【过滤器】
// 添加注解以让Spring进行扫描
@Configuration
public class MPConfig {
/**
* MybatisPlus 中分页查询作为一个插件(或另一个实例类)实现
* 所以可以配置一个Bean留给Spring管理,并注入到MybatisPlus中即可实现
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
创建Services层
控制层,用于控制Dao层数据操作的中间层,它桥接了表现层(Controller)和数据层(Dao)中间的数据处理。它通常是用于接收表现层任务后,对表现层的请求进行加工处理后,操作Dao层操作数据库取得数据记录的作用。
自定义Services层
通过定义方法,控制Dao层的数据处理,并返回处理结果。
// 定义接口规范
public interface BookServiceMPInterface {
Book getById(Integer id);
List<Book> getAll();
Boolean update(Book book);
Boolean delete(Book book);
Boolean save(Book book);
IPage getPage(Integer currentPage, Integer pageSize);
}
// 实现接口规范
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookMapper bookMapper;
@Override
public Book getById(Integer id) {
return bookMapper.selectById(id);
}
@Override
public List<Book> getAll() {
return bookMapper.selectList(null);
}
@Override
public Boolean update(Book book) {
return bookMapper.updateById(book) > 0;
}
@Override
public Boolean delete(Book book) {
return bookMapper.deleteById(book) > 0;
}
@Override
public Boolean save(Book book) {
return bookMapper.insert(book) > 0;
}
@Override
public IPage getPage(Integer currentPage, Integer pageSize) {
IPage page = new Page(currentPage,pageSize);
bookMapper.selectPage(page,null);
return page;
}
}
预生成Services层
当我们创建比较多Service层的时候,我们会发现,大多数的Service层都包含了相同的处理方法,如插入,查询数据,更新数据等等,如果每一个Service层都要重复写的话,会比较麻烦且重复,Mybatis-Plus 也提供了一种预生成通用Service层的类【ServiceImpl<Dao, POJO>】,它实现了Services层的通用接口规范【IService<POJO>】.
在【IService】接口中,提供了大部分通用的Services层控制方法,而且提供了实现了【IService】接口的通用实现方法【ServiceImpl】,我们只需要提供Dao层和POJO实体类,【ServiceImpl】能提供完善的通用Service控制方法。
@Service
public class BookServiceMPImpl extends ServiceImpl<BookMapper, Book> implements BookServiceMPInterface { }
当【ServiceImpl】中没有提供的自定义Service处理方法时,可以通过重写,或按照【自定义Services层】的方法来定义我们的Service方法。
创建Controller层
表现层,通常是用于与浏览器请求响应相关的控制器层,控制器层通过接收浏览器请求后,对接收的请求发送至Service层进行处理,处理后的响应以JOSN数据返回(@RESTController注解)
/**
* 表现层代码
* 通过使用REST规范对增删改查进行操作
*/
@RestController
@RequestMapping("/books")
public class BookController {
/**
* 取得Services层控制
*/
@Autowired
private BookServiceMPInterface bookService;
/**
* 查询所有数据
* - 使用GET请求
* - 请求参数为空
*/
@GetMapping
public List<Book> getAll() {
return bookService.list();
}
/**
* 查询单个数据
* - 使用GET请求
* - 参数为 id
*/
@GetMapping("{id}")
public Book getById(@PathVariable Integer id) {
return bookService.getById(id);
}
/**
* 增加数据
* - 使用POST请求
*/
@PostMapping
public boolean save(@RequestBody Book book) {
return bookService.save(book);
}
/**
* 修改数据
* - 使用PUT请求
* - 参数为 Book
* 与 增加数据 不同的是,修改数据的Book带有 id 值
*/
@PutMapping
public boolean update(@RequestBody Book book) {
return bookService.updateById(book);
}
/**
* 删除数据
* - 使用 Delete 请求
* - 接收参数 id
*/
@DeleteMapping
public boolean delete(@PathVariable Integer id) {
return bookService.removeById(id);
}
/**
* 分页处理
* - 使用GET请求
* - 使用自定义Mapper方法处虣
* - 接收参数 currentPage 和 pageSize
*/
@GetMapping("{currentPage}/{pageSize}")
public IPage getPage(@PathVariable Integer currentPage, @PathVariable Integer pageSize) {
return bookService.getPage(currentPage, pageSize);
}
}
JSON统一化处理
经过上面的表现层开发后,可以通过浏览器请求获得对应的JSON数据,但是这些JSON数据的格式都非常不统一,有一些是 数组,有一些是对象,有一些是 true 和 false
为了统一JSON数据的格式,我们可以另外创建一个实体类,用于包装数据,返回时,则返回该包装实体类
@Data
public class ResponseData {
/**
* struts 用于表示请求是否成功
*/
private boolean struts;
/**
* msg 请求消息说明
*/
private String msg;
/**
* data 请求数据
*/
private Object data;
public ResponseData(){}
/**
* 用于提交数据,或删除数据用的构造方法
* @param struts
*/
public ResponseData(boolean struts){
this.struts = struts;
this.msg = "请求成功";
}
/**
* 用于查询数据的构造方法
* @param struts
* @param data
*/
public ResponseData(boolean struts,Object data)
{
this.struts = struts;
this.msg = "请求成功";
this.data = data;
}
}
Controller 层重新包装
我们可以把之前的数据,给到ResponseData
进行数据重新包装返回。
/**
* 表现层代码
* 通过使用REST规范对增删改查进行操作
*/
@RestController
@RequestMapping("/books")
public class BookController {
/**
* 取得Services层控制
*/
@Autowired
private BookServiceMPInterface bookService;
/**
* 查询所有数据
* - 使用GET请求
* - 请求参数为空
*/
@GetMapping
public ResponseData getAll() {
return new ResponseData(true, bookService.list());
}
/**
* 查询单个数据
* - 使用GET请求
* - 参数为 id
*/
@GetMapping("{id}")
public ResponseData getById(@PathVariable Integer id) {
return new ResponseData(true, bookService.getById(id));
}
/**
* 增加数据
* - 使用POST请求
*/
@PostMapping
public ResponseData save(@RequestBody Book book) {
return new ResponseData(bookService.save(book));
}
/**
* 修改数据
* - 使用PUT请求
* - 参数为 Book
* 与 增加数据 不同的是,修改数据的Book带有 id 值
*/
@PutMapping
public ResponseData update(@RequestBody Book book) {
return new ResponseData(bookService.updateById(book));
}
/**
* 删除数据
* - 使用 Delete 请求
* - 接收参数 id
*/
@DeleteMapping
public ResponseData delete(@PathVariable Integer id) {
return new ResponseData(bookService.removeById(id));
}
/**
* 分页处理
* - 使用GET请求
* - 使用自定义Mapper方法处虣
* - 接收参数 currentPage 和 pageSize
*/
@GetMapping("{currentPage}/{pageSize}")
public ResponseData getPage(@PathVariable Integer currentPage, @PathVariable Integer pageSize) {
return new ResponseData(true, bookService.getPage(currentPage, pageSize));
}
}
关于异常抛出处理
对于正常请求出数据的情况而且,我们会经常在请求中出现一些异常抛出的情况,对于异常抛出,SpringBoot已帮我们转化为JSON输出,但是它的格式并不符合我们的要求。
通过AOP面向切面编程抓获异常处理,并把异常封装成我们自己的JSON格式
/**
* 本类用AOP对异常处理进行数据格式的重新封装
* 我们知道,系统有可能会出现异常情况,但是系统的默认异常抛出格式与我们定义的并非一样
* 为了保证抛出的异常封装的JDON数据和我们定义的一至
* 我们可以使用AOP(面向切面编程)来对异常抛出做处理
*/
@RestControllerAdvice
public class ProjectExceptionAdvice {
/**
* 带有Exception类的异常抛出都会执行这个方法
* 如果想定义个别异常抛出做其它外理时,可以在注解@ExceptionHandler中定义异常类型
*/
@ExceptionHandler(Exception.class)
public ResponseData doException(Exception ex){
// 通知运维
// 通知开发
// ....
ex.printStackTrace();
return new ResponseData("服务器出现错误,请稍候再试!");
}
}
前端页面开发
前端页面开发的部分代码,可以参照下面的完整源码配合阅读
查询所有数据getAll()
使用Axios查询所有数据,并在页面中渲染出来。
//取出所有列表
async getAll() {
const {data} = await axios.get("/books")
this.dataList = data.data
},
该请求对调用的是GET( /books ) 上
删除数据delete()
// 删除 row 是 elementUI 提供的插糟数据封装,包含表行中所有的字段
handleDelete(row) {
// 弹出提示是否确认删除
this.$confirm("是否确定要删除这一行的数据", "提示", {type: "info"}).then(() => {
console.log("sssss")
// 确定删除
axios.delete("/books/" + row.id).then(res => {
if (res.data.status) {
this.$message.success("删除成功")
} else {
this.$message.error("删除失败")
}
}).finally(() => {
this.getAll()
})
}).catch(() => {
// 取消
this.$message.info("取消删除")
})
},
该请求对调用的是Delete( /books/{id} ) 上
新增数据Add()
//添加
handleAdd() {
axios.post("/books", this.formData).then(res => {
if (res.data.status) {
this.$message.success("数据添加成功");
this.dialogFormVisible = false;
} else {
this.$message.error("数据添加失败");
}
}).finally(() => {
this.getAll();
});
},
该请求对调用的是POST( /books ) 上
编辑数据Edit()
//弹出编辑窗口
handleUpdate(row) {
axios.get("/books/" + row.id).then(res => {
if (res.data.status && res.data.data != null) {
this.dialogFormVisible4Edit = true;
this.formData = res.data.data;
} else {
// 已被删除时点击编辑
this.$message.error("数据已被删除,刷新列表");
}
}).finally(() => {
this.getAll();
})
},
//修改
handleEdit() {
axios.put("/books", this.formData).then(res => {
if (res.data.status) {
this.$message.success("修改成功");
this.dialogFormVisible4Edit = false;
} else {
this.$message.error("修改失败");
}
}).finally(() => {
this.getAll();
})
},
该请求对调用的是PUT( /books ) 上,所发送的参数是book实体例对象
分页显示
于分页和获取所有列表而言,它们的含意是相同的,只是分页所列出的数据是按照数量定义
getAll() {
let param = "?query";
param += `&type=${this.query.type}`;
param += `&name=${this.query.name}`;
param += `&description=${this.query.description}`;
axios.get(`/books/${this.pagination.currentPage}/${this.pagination.pageSize}${param}`).then(res => {
// 获取列表信息
this.dataList = res.data.data.records;
// 获取页码信息
this.pagination.total = res.data.data.total;
this.pagination.currentPage = res.data.data.current;
this.pagination.pageSize = res.data.data.size;
})
},
该请求对调用的是GET( /books/{currectPage}/{pageSize} ) 上
@GetMapping("{currentPage}/{pageSize}")
public ResponseData getPage(@PathVariable Integer currentPage, @PathVariable Integer pageSize) {
IPage page = bookService.getPage(currentPage, pageSize);
return new ResponseData(true, page);
}
自定义查询
对于用户提交的查询,可以使用param参数提交(?query=xxx)的方式,而数据的接收,可以SpringBoot会自动帮我们整合到POJO实体类中
@GetMapping("{currentPage}/{pageSize}")
public ResponseData getPage(@PathVariable Integer currentPage, @PathVariable Integer pageSize, Book book) {
/**
* 判断页码只有一行数据时,当删除后,查询页码大于总页码的问题
*/
IPage page = bookService.getPage(currentPage, pageSize, book);
if (currentPage > page.getPages()) {
// 查询最后页码的数据
page = bookService.getPage((int) page.getPages(), pageSize, book);
}
return new ResponseData(true, page);
}
项目打包
通过Maven工程的 package 功能打包成一个jar包
注意:在打包时,Maven工程会对工程内的测试都执行一遍,这有可能使得测试方法中修改了部分数据库中的数据
我们可以通过禁用【测试】后再进行打包
静态打包
Springboot 项目本身自带了Tomcat服务器,可以在不需要任何容器的情况下自运行。
在打包时是否把Tomcat和依赖打包到到jar包中,取决于 pom.xml 文件中的 打包插件
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
如果 pom.xml 文件中去掉这个插件,那么打包下来的jar包不带有容器依赖和类加载器等数据,不能直接运行。
项目运行
普通运行
对于静态打包的SpringBoot项目,可以通过下面命令行进行运行
java -jar springboot.jar
命令行提交运行参数
打包后的Springboot项目只有一个jar包,但是我们的配置文件也一同打包在jar里了,如果我们此时需要临时修改配置文件中的配置时,可以通过运行命令行提交
java -jar springboot.jar --server.port=8080 --spring.datasource.druid.username=root --spring.datasource.druid.password=root
其它配置如此累推
四级配置文件权重
针对SpringBoot项目的配置问题,如果只使用命令行会非常麻烦,而且不安全。Spring提供了一种外置配置文件的方法,且它们之间有权重顺序,适用于项目中负责不同阶段的人员对系统进行配置。
// 等级1 ==> 最低等级
resources\application.yml
// 等级2
resources\config\application.yml
// 等级3
jar包根目录\application.yml
// 等级4 ==> 最高等级
jar包根目录\config\application.yml
注意:配置文件遵循【相同配置=>高等级覆盖等级】和【不同配置 => 高低等级配置合并】原则。
提供配置等级的作用在于,
3级与4级留做系统打包后设置通用属性,4级常用于运维经理进行线上整体项目部署方案调控
1级与2级用于系统开发阶段设置通用属性,2级常用于项目经理进行整体项目属性调控
自定义配置文件名
对于SpringBoot项目而言,它的配置默认名称是 application ,这一默认名字有可能会使其他人通过配置覆盖的方式对原来的配置进行篡改,为了保证配置文件的安全,可以设置自定义配置文件名
用命令行定义配置文件
java -jar springboot.jar --spring.config.name=configName
也可以指定配置文件的位置
java -jar springboot.jar --spring.config.location=d:\xxxx
// 使用 classpath 定位到 resources 文件夹下
java -jar springboot.jar --spring.config.location=classpath:\config1.yml,classpath:\config1.yml
// 注意:当配置多个配置文件时,越靠后的配置文件优先级越高
多环境配置
针对一个项目,都必须经历三个阶段,分别是【开发】【测试】【生产】
每一个阶段它们的配置会不一样,如数据库连接配置等。
SpringBoot提供了然后环境配置的方式
Yaml文件
在Yml文件下可以通过【---】方式划分配置,配置如下
# 应用环境:这里一般配置三大阶段都需要用到的必要的公共配置
# 配置当前使用那一个阶段的配置 pro
spring:
profiles:
active: pro
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
---
# 生产环境 : 环境一,可用于在生产环境下的私有配置
server:
port: 80
spring:
config:
activate:
on-profile: pro
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/springboot_ssmp?serverTimezone=UTC
username: root
password: 543ikjh69483
---
# 测试环境: 环境一,可用于在测试环境下的私有配置
server:
port: 81
spring:
config:
activate:
on-profile: test
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/springboot_ssmp?serverTimezone=UTC
username: test
password: test
---
# 开发环境: 环境一,可用于在开发环境下的私有配置
server:
port: 82
spring:
config:
activate:
on-profile: dev
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/springboot_ssmp?serverTimezone=UTC
username: root
password: root
多文件Yaml文件
针对单个文件多个环境配置的问题,我们可以把各个不同环境的配置分别拆分出不同的文件
规则:application-xxx.yml
1.生产环境配置 application-pro.yml
# 生产环境 : 环境一,可用于在生产环境下的私有配置
server:
port: 80
spring:
config:
activate:
on-profile: pro
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/springboot_ssmp?serverTimezone=UTC
username: root
password: 543ikjh69483
2.测试环境配置 application-test.yml
# 测试环境: 环境一,可用于在测试环境下的私有配置
server:
port: 81
spring:
config:
activate:
on-profile: test
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/springboot_ssmp?serverTimezone=UTC
username: test
password: test
3.开发环境配置 application-dev.yml
# 开发环境: 环境一,可用于在开发环境下的私有配置
server:
port: 82
spring:
config:
activate:
on-profile: dev
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/springboot_ssmp?serverTimezone=UTC
username: root
password: root
总配置 application.yml
总配置里定义当前使用的环境配置,和包含公共配置参数
# 应用环境:这里一般配置三大阶段都需要用到的必要的公共配置
# 配置当前使用那一个阶段的配置 pro
spring:
profiles:
active: pro
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
多文件properties文件
与yml 文件多文件配置相似。
1.生产环境配置 application-pro.properties
2.测试环境配置 application-test.properties
3.开发环境配置 application-dev.properties
总配置文件 application.properties
spring.profiles.active=pro
多文件配置分组
当我们需要切换到某一环境时,我们可以通过【spring.profiles.active】配置对应的配置文件适应当前环境
但对于配置文件中的配置,也可以细分出来多个文件,如DB配置可以一个配置文件,Redis缓存配置又一个配置文件等等。
application-dev.yml ==> 用于配置 开发环境 下的公共配置
application-devDB.yml ==> 用于专门配置 开发环境下的数据库信息
application-devRedis.yml ==> 用于专门配置 开发环境下的Redis缓存配置
application-pro.yml ==> 用于配置 开发环境 下的公共配置
application-proDB.yml ==> 用于专门配置 开发环境下的数据库信息
application-proRedis.yml ==> 用于专门配置 开发环境下的Redis缓存配置
SpringBoot 2.4.x 以前
使用 include,可以引入多个细分配置文件如
spring:
profiles:
active: pro
include: proDB,proRedis # 指定 pro 配置外,还同时加载 proDB,proRedis 两个配置
# 配置加载顺序 proDB -> proRedis -> pro
缺点是,如果需要更改配置时,include 配置也要同时更改。
注意:后加载的配置会覆盖前加载的配置
SpringBoot 2.4.x 以后(推荐使用)
SpringBoot 提供了【配置组】的概念,即可提前定义使用什么环境下就会 include 什么配置如
spring:
profiles:
active: pro
group:
"dev" : devDB, devRedis, pubConfig
"pro" : proDB, proRedis, pubConfig
"test" : testDB, testRedis, pubConfig
# 配置加载顺序 pro -> proDB -> proRedis -> pubConfig
当active定义的是什么环境时,SpringBoot则选用那个环境下预定义的细分配置。
注意:后加载的配置会覆盖前加载的配置,在group参数下,active 是最先加载的,需要注意细分配置中是否有冲突配置项把active配置覆盖了。
Maven与SpringBoot多环境问题
Maven项目中也包含多环境控制,而SpringBoot也有多环境控制
针对这个问题,首先Maven是启动SpringBoot项目的主要工具,因此在多环境配置问题上,应以Maven为主,SpringBoot可以通过读取Maven配置项来设定SpringBoot环境
pom.xml 文件
<profiles>
<!-- 创建一个配置项 -->
<profile>
<!-- 配置项ID -->
<id>env_pro</id>
<!-- 配置项具体属性(这个属性可自定义,用于在yml文件中引用) -->
<properties>
<profile.active>pro</profile.active>
</properties>
<!-- 设置为当前默认的配置 -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>env_dev</id>
<properties>
<profile.active>dev</profile.active>
</properties>
</profile>
<profile>
<id>env_test</id>
<properties>
<profile.active>test</profile.active>
</properties>
</profile>
</profiles>
yml文件中引用属性,使用【@xxxx@】来引用pom.xml文件中定义的属性
spring:
profiles:
active: @profile.active@
group:
"dev" : devDB, devRedis, pubConfig
"pro" : proDB, proRedis, pubConfig
"test" : testDB, testRedis, pubConfig
SpringBoot 日志系统
基础操作
通过实例化日志对象,输出日志
// 在对应控制器上初始化日志对象
private static final Logger log = LoggerFactory.getLogger(Controller.class);
// 输出debug级别的日志(调试日志)
log.debug("debug...")
// 输出info级别的日志(普通日志)
log.info("info...")
// 输出warn级别的日志(警告日志)
log.warn("warn...")
// 输出error级别的日志(崩溃日志)
log.error("error...")
注意:日志级别往上兼容,最低级是debug级别,设置输出debug级别的日志时,将同时允许输出【info】【warn】【error】,当设置输出级别为error时,【debug】【info】【warn】级别的日志将不会输出。
lombok 快捷创建Logger日志对象
lombok 支持为我们快捷创建Logger日志对象,使用【@Slf4j】注解即可。
@Slf4j
@RestController
@RequestMapper("/")
public class Controller {
// 在方法中就可以使用 log.error() 等方法输出日志了
}
配置开启debug模式
开启debug模式后,会输出debug级别的日志信息,生产系统中建议关闭。
debug: true
配置日志级别
可在配置中定义日志级别,默认为 info 级别
logging:
level:
root: info | debug | warn | error
cn.unsoft.controller: debug
root 指根节点,即整体应用日志级别输出
cn.unsoft.controller: debug 可以自定义包中的类特定的日志输出级别
配置日志级别分组
针对多个包采用相同的日志级别时,会出现如下情况
logging:
level:
root: info | debug | warn | error
cn.unsoft1.controller: debug
cn.unsoft2.controller: debug
cn.unsoft3.controller: debug
....
在日常项目开发中,软件包会非常多,这样配置会显得臃肿
可以把采用相同日志级别的软件包归并到一个组中,并引用即可。
logging:
level:
root: info | debug | warn | error
unsoft: warn # 引用组中定义的包
server: error
group:
unsoft: cn.unsoft.controller1,cn.unsoft.controller2,cn.unsoft.controller3
server: com.alibaba,com.taobao
自定义日志输出格式
SpringBoot 项目日志输出可以自定义格式
logging:
pattern:
console: ...
%d : 日期时间
%p : 日志级别,%5p 指的是为日志级别单词预留5个字节位,用于日志排版
%n : 换行符
%clr() : 显示彩色日志文字,通过 %clr(){color} 来定义颜色如 {red}
%t : 执行日志输出的线程
%c : 执行日志输出的类,%40c 指的是预留40个字符位显示输出类,少于40字符位的类名会被居中显示,%-40c 指的是字符左移40个字符位,%-40.40c 指的是字符居左40个字显示,最多显示40个字符
%m : 输出日志内容
日志文件化
我们需要把日志写到文件中,SpringBoot还支持日志文件分割
# 设置把日志输出到文件中保存
logging:
file:
name: server.log
当我们长期运行系统后,日志文件会越来越大,为了方便日志追朔,SpringBoot 支持分割日志文件
# 设置文件大小限制,当文件大于10MB时,自动新建一个新的日志文件
# 新建的日志文件命名按 file-name-pattern 配置的规则创建
# 文件名将是 server.2023-01-31.0.log,server.2023-01-31.1.log 累推
logging:
logback:
rollingpolicy:
max-file-size: 10MB
file-name-pattern: server.%d{yyyy-MM-dd}.%i.log
%i 指的是循环变量,从0开始
源码参考
Vue源代码参考
<script>
var vue = new Vue({
el: '#app',
data: {
dataList: [],//当前页要展示的列表数据
dialogFormVisible: false,//添加表单是否可见
dialogFormVisible4Edit: false,//编辑表单是否可见
formData: {},//表单数据
rules: {//校验规则
type: [{required: true, message: '图书类别为必填项', trigger: 'blur'}],
name: [{required: true, message: '图书名称为必填项', trigger: 'blur'}]
},
// 用于查询表单
query: {
type: "",
name: "",
description: ""
},
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize: 10,//每页显示的记录数
total: 0//总记录数
}
},
//钩子函数,VUE对象初始化完成后自动执行
created() {
this.getAll();
},
methods: {
//取出所有列表
// async getAll() {
// const {data} = await axios.get("/books")
// this.dataList = data.data
// },
//分页查询
/**
* 对于分页和获取所有列表而言,它们的含意是相同的,只是分页所列出的数据是按照数量定义
*/
getAll() {
let param = "?query";
param += `&type=${this.query.type}`;
param += `&name=${this.query.name}`;
param += `&description=${this.query.description}`;
axios.get(`/books/${this.pagination.currentPage}/${this.pagination.pageSize}${param}`).then(res => {
// 获取列表信息
this.dataList = res.data.data.records;
// 获取页码信息
this.pagination.total = res.data.data.total;
this.pagination.currentPage = res.data.data.current;
this.pagination.pageSize = res.data.data.size;
})
},
//切换页码,当点击页码时的数据切换
handleCurrentChange(currentPage) {
this.pagination.currentPage = currentPage;
this.getAll();
},
//弹出添加窗口
handleCreate() {
// 为了防止上一次添加数据还在,所以在弹出之前先清空表单
this.resetForm();
// 通过变量控制弹出添加窗口
this.dialogFormVisible = true;
},
//重置表单
resetForm() {
this.formData = {};
},
//添加
handleAdd() {
axios.post("/books", this.formData).then(res => {
if (res.data.status) {
this.$message.success("数据添加成功");
this.dialogFormVisible = false;
} else {
this.$message.error("数据添加失败");
}
}).finally(() => {
this.getAll();
});
},
//取消
cancel() {
this.dialogFormVisible = false;
this.dialogFormVisible4Edit = false;
},
// 删除 row 是 elementUI 提供的插糟数据封装,包含表行中所有的字段
handleDelete(row) {
// 弹出提示是否确认删除
this.$confirm("是否确定要删除这一行的数据", "提示", {type: "info"}).then(() => {
console.log("sssss")
// 确定删除
axios.delete("/books/" + row.id).then(res => {
if (res.data.status) {
this.$message.success("删除成功")
} else {
this.$message.error("删除失败")
}
}).finally(() => {
this.getAll()
})
}).catch(() => {
// 取消
this.$message.info("取消删除")
})
},
//弹出编辑窗口
handleUpdate(row) {
axios.get("/books/" + row.id).then(res => {
if (res.data.status && res.data.data != null) {
this.dialogFormVisible4Edit = true;
this.formData = res.data.data;
} else {
// 已被删除时点击编辑
this.$message.error("数据已被删除,刷新列表");
}
}).finally(() => {
this.getAll();
})
},
//修改
handleEdit() {
axios.put("/books", this.formData).then(res => {
if (res.data.status) {
this.$message.success("修改成功");
this.dialogFormVisible4Edit = false;
} else {
this.$message.error("修改失败");
}
}).finally(() => {
this.getAll();
})
},
//条件查询
}
})
</script>
HTML源代码参考
<body class="hold-transition">
<div id="app">
<div class="content-header">
<h1>图书管理</h1>
</div>
<div class="app-container">
<div class="box">
<div class="filter-container">
<el-input placeholder="图书类别" v-model="query.type" style="width: 200px;" class="filter-item"></el-input>
<el-input placeholder="图书名称" v-model="query.name" style="width: 200px;" class="filter-item"></el-input>
<el-input placeholder="图书描述" v-model="query.description" style="width: 200px;" class="filter-item"></el-input>
<el-button @click="getAll()" class="dalfBut">查询</el-button>
<el-button type="primary" class="butT" @click="handleCreate()">新建</el-button>
</div>
<el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
<el-table-column type="index" align="center" label="序号"></el-table-column>
<el-table-column prop="type" label="图书类别" align="center"></el-table-column>
<el-table-column prop="name" label="图书名称" align="center"></el-table-column>
<el-table-column prop="description" label="描述" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<div class="pagination-container">
<el-pagination
class="pagiantion"
@current-change="handleCurrentChange"
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
layout="total, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>
</div>
<!-- 新增标签弹层 -->
<div class="add-form">
<el-dialog title="新增图书" :visible.sync="dialogFormVisible">
<el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right"
label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input v-model="formData.description" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel()">取消</el-button>
<el-button type="primary" @click="handleAdd()">确定</el-button>
</div>
</el-dialog>
</div>
<!-- 编辑标签弹层 -->
<div class="add-form">
<el-dialog title="编辑检查项" :visible.sync="dialogFormVisible4Edit">
<el-form ref="dataEditForm" :model="formData" :rules="rules" label-position="right"
label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input v-model="formData.description" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="cancel()">取消</el-button>
<el-button type="primary" @click="handleEdit()">确定</el-button>
</div>
</el-dialog>
</div>
</div>
</div>
</div>
</body>
共有 0 条评论