目录
1.删除部门
1.1需求分析
删除部门数据。在点击 "删除" 按钮,会根据ID删除部门数据。

需求分析之后我们要看接口文档中关于删除部门接口的描述,然后根据接口文档进行服务端接口的开发

1.2 思路分析
1.2.1 思路说明
明确了删除部门的需求之后,再来梳理一下实现该功能时,三层架构每一层的职责:

- Mapper主要负责写SQL语句
- Service调用Mapper接口方法
- Controller需要接收请求参数(如删除部门时id = 1),再调用Service层,最后响应结果
这里的难点在于Controller如何从前端接收请求参数?
1.2.1 简单参数接收
1.通过原始的 HttpServletRequest 对象获取请求参数
/**
* 根据ID删除部门 - 简单参数接收: 方式一 (HttpServletRequest)
*/
@DeleteMapping("/depts")
public Result delete(HttpServletRequest request){
String id = request.getParameter("id");
int id = Integer.parseInt(id); //需要手动进行强转
System.out.println("根据ID删除部门: " + id);
return Result.success();
}
2.通过Spring提供的 @RequestParam 注解,将请求参数绑定给方法形参
@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer deptId){
System.out.println("根据ID删除部门: " + deptId);
return Result.success();
}
@RequestParam 注解的value属性,需要与前端传递的参数名保持一致 。
@RequestParam注解required属性默认为true,代表该参数必须传递,如果不传递将报错。 如果参数可选,可以将属性设置为false。
3.(更推荐)如果请求参数名与形参变量名相同,直接定义方法形参即可接收。(省略@RequestParam)
@DeleteMapping("/depts")
public Result delete(Integer id){
System.out.println("根据ID删除部门: " + deptId);
return Result.success();
}
1.3 代码实现
1). Mapper层
@Mapper
public interface DeptMapper {
/**
* 查询所有部门信息
*
* @return
*/
@Select("select * from dept")
public List<Dept> list();
/**
* 根据id删除部门
*/
@Delete("delete from dept where id = #{id}")
void delete(Integer id);
}
如果mapper接口方法形参只有一个普通类型的参数,可以用#{…}, 里面的属性名可以随便写,如:#{id}、#{value}。
对于 DML 语句来说,执行完毕,也是有返回值的,返回值代表的是增删改操作,影响的记录数,所以可以将执行 DML 语句的方法返回值设置为 Integer。 但是一般开发时,是不需要这个返回值的,所以也可以设置为void。
2). Service层
/**
* 业务逻辑处理层
*/
@Service
public class DeptServiceImpl implements DeptService {
@Autowired //从IOC容器中,自动寻找bean对象,为该变量赋值--依赖注入DI的实现
private DeptMapper deptMapper;
//查询所有部门
public List<Dept> list(){
//1.调用mapper的方法,获取列表数据并返回
return deptMapper.list();
}
//根据id删除部门
public void delete(Integer id){
//调用mapper的删除方法
deptMapper.delete(id);
}
}
3). Controller层
/*
* 请求处理类
*/
@RestController //等价于@Controller + @ResponseBody
public class DeptController {
@Autowired
private DeptService deptService;
/**
* 查询所有部门信息
* @return
*/
@GetMapping("/depts")
public Result list() {
//1.调用service获取数据
List<Dept> depts = deptService.list();
//3.响应数据(json格式)
return Result.success(depts);
}
public Result delete(Integer id){
System.out.println("deptId = " + id);
//调用Service的删除方法
deptService.delete(id);//不需要返回值
return Result.success();
}
}
1.4 Mybatis中的#与$
在mybatis的mapper接口中,我们定义SQL语句,参数占位符可以使用#{...}与${...}
| 符号 | 说明 | 场景 | 优缺点 |
|---|---|---|---|
| #{…} | 执行时,会将#{…}替换为?,生成预编译SQL,并自动设置参数值 | 参数值传递 | 安全、性能高 (推荐) |
| ${…} | 拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题 | 表名、字段名动态设置时使用 | 不安全、性能低 |
1). 如果我们在定义SQL语句的时候,使用的是 #{...}

最终生成的SQL语句是预编译的SQL语句。
2). 如果我们在定义SQL语句的时候,使用的是 ${...}

2.新增部门
2.1 需求分析
点击 "新增部门" 的按钮之后,弹出新增部门表单,填写部门名称之后,点击确定之后,保存部门数据。

了解了需求之后,我们再看看接口文档中,关于新增部门的接口的描述,然后根据接口文档进行服务端接口的开发 。

2.2 思路分析
2.2.1 思路说明

注意在Controller接收到的json格式请求参数只有name,但是添加部门还需要id、create_time、update_time,id我们在建表时设置了自增,所以可以不传入数据就可自增,我们需要在Service层中补全其余的基础属性,然后再调用Mapper层中的SQL语句对数据库进行操作。
2.2.2 json参数接收
在Controller中需要接受前端传递的json格式请求数据
- JSON格式的参数,通常会使用一个实体对象进行接收(创建对象来接收数据)
- 规则:JSON数据的键名与方法形参对象的属性名相同(如JSON数据中{"name" : "教研部"},与要创建的Dept类对象dept中的属性name名称一致),并需要使用@RequestBody注解标识

2.3 代码实现
1). Controller层
/**
* 新增部门
* @param dept
* @return
*/
// @RequestBody 作用:用来接收json格式的请求参数
@PostMapping("/depts")
public Result add(@RequestBody Dept dept){
System.out.println("dept = " + dept);
//调用Service
deptService.add(dept);
return Result.success();
}
2). Service层
/**
* 新增部门
* @param dept
*/
@Override
public void add(Dept dept) {
//1.补充基础属性
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
//2.调用mapper的新增方法
deptMapper.insert(dept);
}
3). Mapper层
/**
* 新增部门
*/
@Insert("insert into dept(name, create_time, update_time) values (#{name}, #{createTime}, #{updateTime})")
void insert(Dept dept);
在mapper接口中,如果需要传递多个参数,可以把多个参数封装到一个对象中。在SQL语句中获取参数的时候,#{...}里面写的是对象的属性名【注意:是对象的属性名,不是表的字段名】


3.修改部门
对于任何业务的修改功能来说,一般都会分为两步进行:查询回显、修改数据。

3.1 查询回显
3.1.1 需求分析
当我们点击“编辑”的是时候,需要根据ID查询部门数据,然后用于页面回显展示(在框中显示当前部门的名称)

3.1.2 思路分析
3.1.2.1 思路说明

了解需求之后我们还需要看接口文档中关于ID查询部门的接口的描述,然后根据接口文档进行服务端接口的开发

3.1.2.2 路径参数接收
/depts/1、/depts/2这种url中传递的参数,我们称之为路径参数。那么如何接收这样的路径参数?
路径参数:通过请求URL直接传递参数,使用{...}来标识该路径参数,需要使用@PathVariable获取路径参数。


3.1.3 代码实现
1). Controller层
/**
* 回显
* @param id
* @return
*/
// @PathVariable 作用:用于接收路径参数值
@GetMapping("/depts/{id}")
public Result getById(@PathVariable Integer id){
System.out.println("id = " + id);
//调用Service方法
Dept dept = deptService.getById(id);
return Result.success(dept);
}
2). Service层
/**
* 数据回显
* @param id
* @return
*/
@Override
public Dept getById(Integer id) {
//调用mapper的查询方法
return deptMapper.selectById(id);
}
3). Mapper层
/**
* 根据id查询部门数据
*/
@Select("select * from dept where id = #{id}")
Dept selectById(Integer id);
编写完毕之后我们可以先用apifox进行调试,发现点击运行后会出现id所对应的数据:

然后可以在网页中调试:

3.2 修改数据
3.2.1 需求分析
查询回显之后,就可以对部门的信息进行修改了,修改完毕之后,点击确定,此时,就需要根据ID修改部门的数据。

了解了需求之后,我们再看看接口文档中,关于修改部门的接口的描述,然后根据接口文档进行服务端接口的开发 。

3.2.2 思路分析

通过接口文档我们看到,前端传递的请求参数是json格式的请求参数,在Controller的方法中,我们可以通过@RequestBody注解来接收,并将其封装到一个对象中。
3.2.3 代码实现
1). Controller层
在 DeptController 中增加 update 方法,具体代码如下:
@PutMapping("/depts")
public Result update(@RequestBody Dept dept){
System.out.println("dept = " + dept);
deptService.update(dept);
return Result.success();
}
2). Service层
/**
* 修改部门
* @param dept
*/
@Override
public void update(Dept dept) {
//1.补充基础属性
dept.setUpdateTime(LocalDateTime.now());
//2.调用mapper的修改方法
deptMapper.update(dept);
}
3). Mapper层
在 DeptMapper 中增加 update 方法,具体代码如下:
/**
* 更新部门数据
*/
@Update("update dept set name = #{name}, update_time = #{updateTime} where id = #{id}")
void update(Dept dept);
代码编写完毕之后,我们就可以启动服务,进行测试了。

修改完成之后,我们可以看到最新的数据,如下:

3.2.4 @RequestMapping
我们会发现,我们在 DeptController 中所定义的方法,所有的请求路径,都是 /depts 开头的,只要操作的是部门数据,请求路径都是 /depts 开头。
那么这个时候,我们其实是可以把这个公共的路径 /depts 抽取到类上的,那在各个方法上,就可以省略了这个 /depts 路径。 代码如下:

一个完整的请求路径,应该是类上的 @RequestMapping 的value属性 + 方法上的 @RequestMapping的value属性。
3.3.2 功能优化
随着用户的输入或者外部条件变化而变化的SQL语句,我们称为动态SQL
在上面的案例中,我们要更新部门的数据,我们所用的SQL语句是:
/**
* 更新部门数据
*/
@Update("update dept set name = #{name}, update_time = #{updateTime} where id = #{id}")
void update(Dept dept);
但是如果我们只想更新部门的name,而保持update_time不变,该怎么操作呢?
我们先将Service层中补全基础属性的功能 dept.setUpdateTime(LocalDateTime.now()) 注释掉
接着我们通过apifox发送请求,发现请求参数中不包含updateTime时,虽然数据能够传递成功,但是在idea中刷新数据库表格,我们会发现当前被修改部门的update_time是null,而我们要的是保持原来不变,显然简单的注释掉更新时间语句无法满足我们的需求,并且我们要的是动态SQL语句,在有updateTime数据时也能自动添加进去,那么该如何实现呢?
我们可以将之前DeptMapper中的update方法上的SQL语句换成动态SQL,此时就需要用到mtbatis的另一种方式:配置XML文件
1). 在 `src/main/resource` 目录下创建文件夹 `com/itheima/mapper`,并在其中定义配置文件,名为 `DeptMapper.xml`(在DeptMapper类名上点击alt + enter 即可实现)

2). 在DeptMapper.xml 中基于Mybatis中提供的动态SQL标签 <if> <set> 将SQL语句改造为动态的。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.mapper.DeptMapper">
<update id="update">
update dept
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="updateTime != null">
update_time = #{updateTime}
</if>
</set>
where id = #{id}
</update>
</mapper>
在上述我们使用了,两个动态SQL的标签,具体含义如下:
-
<if>:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。
-
<set>:动态地在行首插入 SET 关键字,并会删掉额外的逗号。(用在update语句中)
3.3 总结接收参数的方法
controller层接收请求参数
接收普通参数:
方式一:通过HttpServletRequest对象获取(原始方式)
方式二:方法形参使用@RequestParam("请求参数名")修饰

方式三:形参变量名与请求参数名一致,即可自动封装
接收json参数:
方法形参必须是实体对象,对象中的属性名必须与json格式的请求参数对应(实体类Dept中有属性name,而json格式请求参数也有name)
方法形参必须由@RequestBody修饰

接收路径参数:/xx/{id}
方法形参必须由@PathVariable修饰,形参变量名与请求参数名一致。

4.日志技术
4.1 概述
什么是日志?
日志就好比生活中的日记,可以随时随地记录你生活中的点点滴滴。
程序中的日志,是用来记录应用程序的运行信息、状态信息、错误信息的。
为什么要在程序中记录日志呢?
-
便于追踪应用程序中的数据信息、程序的执行过程。
-
便于对应用程序的性能进行优化。
-
便于应用程序出现问题之后,排查问题,解决问题。
-
便于监控系统的运行状态。
之前我们编写程序时,也可以通过 System.out.println(...)来输出日志,为什么我们还要学习单独的日志技术呢?
这是因为,如果通过 System.out.println(...) 来记录日志,会存在以下几点问题:
- 硬编码。所有的记录日志的代码都是硬编码,没有办法做到灵活控制,要想不输出这个日志,只能删除记录日志的代码。
- 只能输出日志到控制台
- 不便于程序的扩展、维护
4.2 日志框架

JUL:这是JavaSE平台提供的官方日志框架,也被称为JUL。配置相对简单,但不够灵活,性能较差。
Log4j:一个流行的日志框架,提供了灵活的配置选项,支持多种输出目标。
Logback:基于Log4j升级而来,提供了更多的功能和配置选项,性能由于Log4j。
Slf4j:(Simple Logging Facade for Java)简单日志门面,提供了一套日志操作的标准接口及抽象类,允许应用程序使用不同的底层日志框架。
4.3 Logback快速入门
1). 准备工作:引入logback的依赖(springboot中无需引入,在springboot中已经传递了此依赖)
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
2). 引入配置文件 logback.xml (资料中已经提供,拷贝进来,放在 src/main/resources 目录下)
<?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>
3). 记录日志:定义日志记录对象Logger,记录日志
public class LogTest {
//定义日志记录对象
private static final Logger log = LoggerFactory.getLogger(LogTest.class);
@Test
public void testLog(){
log.debug("开始计算...");
int sum = 0;
try {
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];
}
} catch (Exception e) {
log.error("程序运行出错", e);
}
log.info("计算结果为: "+sum);
log.debug("结束计算...");
}
}
运行单元测试,可以在控制台中看到输出的日志,如下所示:

我们可以看到在输出的日志信息中,不仅输出了日志的信息,还包括:日志的输出时间、线程名、具体在那个类中输出的。
4.4 Logback配置文件
Logback日志框架的配置文件叫 logback.xml 。
该配置文件是对Logback日志框架输出的日志进行控制的,可以来配置输出的格式、位置及日志开关等。
常用的两种输出日志的位置:控制台、系统文件。
1). 如果需要输出日志到控制台。添加如下配置:
<!-- 控制台输出 -->
<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>
2). 如果需要输出日志到文件。添加如下配置:
<!-- 按照每天生成日志文件 -->
<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>
3). 日志开关配置 (开启日志(ALL),取消日志(OFF))
<!-- 日志输出级别 -->
<root level="ALL">
<!--输出到控制台,如果不想要的话注释掉即可-->
<appender-ref ref="STDOUT" />
<!--输出到文件-->
<appender-ref ref="FILE" />
</root>
4.5 Logback日志级别
日志级别指的是日志信息的类型,日志都会分级别,常见的日志级别如下(优先级由低到高):
| 日志级别 | 说明 | 记录方式 |
|---|---|---|
| trace | 追踪,记录程序运行轨迹 【使用很少】 | log.trace("...") |
| debug | 调试,记录程序调试过程中的信息,实际应用中一般将其视为最低级别 【使用较多】 | log.debug("...") |
| info | 记录程序运行的重要信息,如:数据库连接、网络连接、io操作 【使用较多】 | log.info("...") |
| warn | 警告信息,可能会发生问题 【使用较多】 | log.warn("...") |
| error | 错误信息 【使用较多】 | log.error("...") |
可以在配置文件logback.xml中,灵活的控制输出那些类型的日志。(大于等于配置的日志级别的日志才会输出)
<!-- 日志输出级别 -->
<root level="info">
<!--输出到控制台-->
<appender-ref ref="STDOUT" />
<!--输出到文件-->
<appender-ref ref="FILE" />
</root>
1388

被折叠的 条评论
为什么被折叠?



