终于放寒假了,继续后端进度
文章目录
一.日志
1)概述
日志:用来记录应用程序的运行信息、状态信息、错误信息
为什么用sout直接输出不行?
因为:
- 硬编码。所有的记录日志的代码,都是硬编码,没有办法做到灵活控制,要想不输出这个日志了,只能删除掉记录日志的代码。
- 只能输出日志到控制台。
- 不便于程序的扩展、维护
2)日志框架

- JUL:javase官方提供的日志框架,配置简单,性能较差
- Log4j:较为流行的日志框架,提供了灵活的配置选项,支持多种输出目标
- **Logback:**基于Log4j升级而来,提供了更多的功能和配置选项,性能由于Log4j
- Slf4j:一套日志的规范,提供了日志操作的标准接口及抽象类
3)Logback教程
-
准备工作:引入logback依赖(springboot中无需引入,在springboot中已经传递了此依赖)
xml<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.11</version> </dependency> -
引入配置文件
logback.xml,放在src/main/resources包下xml<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符 --> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern> </encoder> </appender> <!-- 日志输出级别 --> <root level="ALL"> <appender-ref ref="STDOUT" /> </root> </configuration> -
定义日志记录对象Logger,记录日志
eg:
javapublic class LogTest { //定义日志记录对象 private static final Logger log = LoggerFactory.getLogger(LogTest.class); @Test public void testLog(){ log.debug("开始计算..."); int sum = 0; int[] nums = {1, 5, 3, 2, 1, 4, 5, 4, 6, 7, 4, 34, 2, 23}; for (int i = 0; i < nums.length; i++) { sum += nums[i]; } log.info("计算结果为: "+sum); log.debug("结束计算..."); } }
4)Logback配置文件
对Logback日志框架输出的日志进行控制,可以来配置输出的格式、位置及日志开关等
常用的两种输出日志的位置:控制台、系统文件
-
输出到控制台
xml<!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符 --> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern> </encoder> </appender> -
输出到文件
xml<!-- 按照每天生成日志文件 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- 日志文件输出的文件名, %i表示序号 --> <FileNamePattern>D:/tlias-%d{yyyy-MM-dd}-%i.log</FileNamePattern> <!-- 最多保留的历史日志文件数量 --> <MaxHistory>30</MaxHistory> <!-- 最大文件大小,超过这个大小会触发滚动到新文件,默认为 10MB --> <maxFileSize>10MB</maxFileSize> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符 --> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern> </encoder> </appender> -
日志的开关配置(开启ALL、取消OFF)
xml<!-- 日志输出级别 --> <root level="ALL"> <!--输出到控制台--> <appender-ref ref="STDOUT" /> <!--输出到文件--> <appender-ref ref="FILE" /> </root>
可以在配置文件logback.xml中,灵活的控制输出那些类型的日志。(大于等于配置的日志级别的日志才会输出)
XML
<!-- 日志输出级别 -->
<root level="info">
<!--输出到控制台-->
<appender-ref ref="STDOUT" />
<!--输出到文件-->
<appender-ref ref="FILE" />
</root>
在日常开发中建议把日志输出级别设置为
info或者debug如果级别太低,会将源码的日志也输出,影响日志查看
6)案例
回到tlias案例中,把先前用sout输出的方式全部改为Logback日志
java
/**
* 部门管理控制器
*/
@Slf4j
@RequestMapping("/depts")
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
/**
* 查询部门列表
*/
//@RequestMapping(value = "/depts", method = RequestMethod.GET)
@GetMapping
public Result list(){
//System.out.println("查询部门列表");
log.info("查询部门列表");
List<Dept> deptList = deptService.findAll();
return Result.success(deptList);
}
/**
* 根据id删除部门 - delete http://localhost:8080/depts?id=1
*/
@DeleteMapping
public Result delete(Integer id){
//System.out.println("根据id删除部门, id=" + id);
log.info("根据id删除部门, id: {}" , id);
deptService.deleteById(id);
return Result.success();
}
/**
* 新增部门 - POST http://localhost:8080/depts 请求参数:{"name":"研发部"}
*/
@PostMapping
public Result save(@RequestBody Dept dept){
//System.out.println("新增部门, dept=" + dept);
log.info("新增部门, dept: {}" , dept);
deptService.save(dept);
return Result.success();
}
/**
* 根据ID查询 - GET http://localhost:8080/depts/1
*/
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id){
//System.out.println("根据ID查询, id=" + id);
log.info("根据ID查询, id: {}" , id);
Dept dept = deptService.getById(id);
return Result.success(dept);
}
/**
* 修改部门 - PUT http://localhost:8080/depts 请求参数:{"id":1,"name":"研发部"}
*/
@PutMapping
public Result update(@RequestBody Dept dept){
//System.out.println("修改部门, dept=" + dept);
log.info("修改部门, dept: {}" , dept);
deptService.update(dept);
return Result.success();
}
}
5)Logback日志级别
日志级别:日志信息的类型
由低到高依次是:
| 日志级别 | 说明 | 记录方式 |
|---|---|---|
| trace | 追踪,记录程序运行轨迹 【使用很少】 | log.trace("...") |
| debug | 调试,记录程序调试过程中的信息,实际应用中一般将其视为最低级别 【使用较多】 | log.debug("...") |
| info | 记录一般信息,描述程序运行的关键事件,如:网络连接、io操作 【使用较多】 | log.info("...") |
| warn | 警告信息,记录潜在有害的情况 【使用较多】 | log.warn("...") |
| error | 错误信息 【使用较多】 | log.error("...") |
==注:==可以在类上注释
@Slf4j,就可以不用在类内定义日志记录对象,便于直接使用添加了该注解,就相当于在类中定义了日志记录器:
javaprivate static Logger log = LoggerFactory. getLogger(Xxx. class);
二.多表查询
细节部分可见MySQL部分笔记
这里重点说物理外键和逻辑外键
-
物理外键
-
使用
foreign key定义外键关联另一张表 -
缺点:
1.影响增、删、改的效率(需要检查外键关系)。
2.仅用于单节点数据库,不适用于分布式、集群场景。
3.容易引发数据库的死锁问题,消耗性能。
-
-
逻辑外键
- 在业务层中解决外键关联
现实开发中,大多使用逻辑外键而紧张使用物理外键
三.分页查询
在tlias案例中,若要实现分页查询,我们需要向前端传递:查询的总条数、查询结果列表;前端在请求服务器时,传递当前页码、每页条数

使用LIMIT进行分页查询
1). 查询第1页数据的SQL语句是:
Java
select * from emp limit 0,10;
2). 查询第2页数据的SQL语句是:
Java
select * from emp limit 10,10;
3). 查询第3页的数据的SQL语句是:
Java
select * from emp limit 20,10;
所以,开始索引的计算公式: 开始索引 = (当前页码 - 1) * 每页显示条数
1)原始方式
1). EmpController
java
@Slf4j
@RequestMapping("/emps")
@RestController
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping
public Result page(@RequestParam(defaultValue = "1") Integer page ,
@RequestParam(defaultValue = "10") Integer pageSize){
log.info("查询员工信息, page={}, pageSize={}", page, pageSize);
PageResult pageResult = empService.page(page, pageSize);
return Result.success(pageBean);
}
}
@RequestParam(defaultValue="默认值") //设置请求参数默认值
2). EmpService
java
public interface EmpService {
/**
* 分页查询
* @param page 页码
* @param pageSize 每页记录数
*/
PageResult page(Integer page, Integer pageSize);
}
3). EmpServiceImpl
java
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
public PageResult page(Integer page, Integer pageSize) {
//1. 获取总记录数
Long total = empMapper.count();
//2. 获取结果列表
Integer start = (page - 1) * pageSize;
List<Emp> empList = empMapper.list(start, pageSize);
//3. 封装结果
return new PageResult(total, empList);
}
}
4). EmpMapper
java
@Mapper
public interface EmpMapper {
/**
* 查询总记录数
*/
@Select("select count(*) from emp e left join dept d on e.dept_id = d.id ")
public Long count();
/**
* 查询所有的员工及其对应的部门名称
*/
@Select("select e.*, d.name deptName from emp as e left join dept as d on e.dept_id = d.id limit #{start}, #{pageSize}")
public List<Emp> list(Integer start , Integer pageSize);
}
2)PageHelper分页插件
使用原始方法较为繁琐,Mybatis提供了一个分页查询的插件PageHelper
使用前后对比:
- Mapper接口层:
- 原始的分页查询功能中,我们需要在Mapper接口中定义两条SQL语句。
- PageHelper实现分页查询之后,只需要编写一条SQL语句,而且不需要考虑分页操作,就是一条正常的查询语句。
- Service层:
- 需要根据页码、每页展示记录数,手动的计算起始索引。
- 无需手动计算起始索引,直接告诉PageHelper需要查询那一页的数据,每页展示多少条记录即可。
使用了PageHelper,就无需再Mapper中进行手动分页了。
在Mapper中我们只需要进行正常的列表查询即可。
在Service层中,调用Mapper的方法之前设置分页参数,在调用Mapper方法执行查询之后,解析分页结果,并将结果封装到PageResult对象中返回。
1). 在pom.xml引入依赖
xml
<!--分页插件PageHelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
2). EmpMapper
java
/**
* 查询所有的员工及其对应的部门名称
*/
@Select("select e.*, d.name deptName from emp as e left join dept as d on e.dept_id = d.id")
public List<Emp> list();
3). EmpServiceImpl
java
@Override
public PageResult page(Integer page, Integer pageSize) {
//1. 设置分页参数
PageHelper.startPage(page,pageSize);
//2. 执行查询
List<Emp> empList = empMapper.list();
Page<Emp> p = (Page<Emp>) empList;
//3. 封装结果
return new PageResult(p.getTotal(), p.getResult());
}
附:解决页码为负数的问题:
在PageHelper中,我们可以通过合理化参数配置,来解决这个问题。直接在application.yml中,引入如下配置即可:
javapagehelper: reasonable: true helper-dialect: mysqlreasonable:分页合理化参数,默认值为false。
当该参数设置为true时,pageNum<=0时会查询第一页,pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
PageHelper底层逻辑为啥empList可以强转为Page
PageHelper 做了以下事情:
- 拦截 MyBatis 的查询(通过插件机制)。
- 先执行一次
COUNT查询 ,获取总记录数(比如SELECT COUNT(*) FROM user)。 - 再执行你的原 SQL,并加上分页语句 (如
LIMIT 0,10)。 - 将查询结果(List 数据) + 总记录数等信息,封装到一个
Page对象中。 - 把这个
Page对象作为查询结果返回 (虽然方法签名是List<User>,但实际返回的是Page<User>实例)。
所以你拿到的 list 虽然声明为 List,但运行时是 Page,你可以安全地强转
3)实现机制
PageHelper在进行分页查询时,会执行上述两条SQL语句
- select count(*) from ... 查询总条数
- select ... from ... 查询结果列表
并将查询到的总记录数,与数据列表封装到了 Page<Emp> 对象中,我们再获取查询结果时,只需要调用Page对象的方法就可以获取
注意事项:
- PageHelper实现分页查询时,SQL语句的结尾一定一定一定不要加分号(😉
- PageHelper只会对紧跟在其后的第一条SQL语句进行分页处理
四.条件分页查询
1)普通实现
1). 在EmpController方法中通过多个方法形参,依次接收这几个参数
java
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping
public Result page(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
String name, Integer gender,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end) {
log.info("查询请求参数: {}, {}, {}, {}, {}, {}", page, pageSize, name, gender, begin, end);
PageResult pageResult = empService.page(page, pageSize);
return Result.success(pageResult);
}
}
2). 修改EmpService及EmpServiceImpl中的代码逻辑
EmpService:
java
public interface EmpService {
/**
* 分页查询
*/
PageResult page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end);
}
EmpServiceImpl:
java
/**
* 员工管理
*/
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
public PageResult page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end) {
//1. 设置PageHelper分页参数
PageHelper.startPage(page, pageSize);
//2. 执行查询
List<Emp> empList = empMapper.list(name, gender, begin, end);
//3. 封装分页结果
Page<Emp> p = (Page<Emp>) empList;
return new PageResult(p.getTotal(), p.getResult());
}
}
3). 调整EmpMapper接口方法
java
@Mapper
public interface EmpMapper {
/**
* 查询所有的员工及其对应的部门名称
*/
public List<Emp> list(String name, Integer gender, LocalDate begin, LocalDate end);
}
由于SQL语句比较复杂,建议将SQL语句配置在XML映射文件中
4). 新增Mapper映射文件EmpMapper.xml
xml
<!--定义Mapper映射文件的约束和基本结构-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
<select id="list" resultType="com.itheima.pojo.Emp">
select e.*, d.name deptName from emp as e left join dept as d on e.dept_id = d.id
where e.name like concat('%',#{name},'%')
and e.gender = #{gender}
and e.entry_date between #{begin} and #{end}
</select>
</mapper>
2)优化1:传递参数封装为对象
优化思路:定义一个实体类,来封装这几个请求参数。
【需要保证,前端传递的请求参数和实体类的属性名是一样的】
1). 定义实体类:EmpQueryParam
java
package com.itheima.pojo;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
public class EmpQueryParam {
private Integer page = 1; //页码
private Integer pageSize = 10; //每页展示记录数
private String name; //姓名
private Integer gender; //性别
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate begin; //入职开始时间
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate end; //入职结束时间
}
2). EmpController接收请求参数
java
@GetMapping
public Result page(EmpQueryParam empQueryParam) {
log.info("查询请求参数: {}", empQueryParam);
PageResult pageResult = empService.page(empQueryParam);
return Result.success(pageResult);
}
3). 修改EmpService接口方法
java
public interface EmpService {
/**
* 分页查询
*/
//PageResult page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end);
PageResult page(EmpQueryParam empQueryParam);
}
4). 修改EmpServiceImpl中的page方法
java
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
/*@Override
public PageResult page(Integer page, Integer pageSize, String name, Integer gender, LocalDate begin, LocalDate end) {
//1. 设置PageHelper分页参数
PageHelper.startPage(page, pageSize);
//2. 执行查询
List<Emp> empList = empMapper.list(name, gender, begin, end);
//3. 封装分页结果
Page<Emp> p = (Page<Emp>) empList;
return new PageResult(p.getTotal(), p.getResult());
}*/
public PageResult page(EmpQueryParam empQueryParam) {
//1. 设置PageHelper分页参数
PageHelper.startPage(empQueryParam.getPage(), empQueryParam.getPageSize());
//2. 执行查询
List<Emp> empList = empMapper.list(empQueryParam);
//3. 封装分页结果
Page<Emp> p = (Page<Emp>)empList;
return new PageResult(p.getTotal(), p.getResult());
}
}
5). 修改EmpMapper接口方法
java
@Mapper
public interface EmpMapper {
/**
* 查询所有的员工及其对应的部门名称
*/
// @Select("select e.*, d.name as deptName from emp e left join dept d on e.dept_id = d.id")
// public List<Emp> list(String name, Integer gender, LocalDate begin, LocalDate end);
/**
* 根据查询条件查询员工
*/
List<Emp> list(EmpQueryParam empQueryParam);
}
EmpMapper.xml 中的配置无需修改
3)优化2:动态SQL解决参数个数多变
在查询时,Mapper映射配置文件中的SQL语句中,查询条件是写死的
而在员工管理中,根据条件查询员工信息时,查询条件是可选的,可以输入也可以不输入
所谓动态SQL,指的就是随着用户的输入或外部的条件的变化而变化的SQL语句
具体实现
xml
<!--定义Mapper映射文件的约束和基本结构-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
<select id="list" resultType="com.itheima.pojo.Emp">
select e.*, d.name deptName from emp as e left join dept as d on e.dept_id = d.id
<where>
<if test="name != null and name != ''">
e.name like concat('%',#{name},'%')
</if>
<if test="gender != null">
and e.gender = #{gender}
</if>
<if test="begin != null and end != null">
and e.entry_date between #{begin} and #{end}
</if>
</where>
</select>
</mapper>
<if>:判断条件是否成立,如果条件为true,则拼接SQL<where>:根据查询条件,来生成where关键字,并会自动去除条件前面多余的and或or