一、springboot项目配置文件



练习:

代码示例:

二、Web后端实战------Tlias案例

2.1 部门管理---目录

2.2 准备工作
2.2.1 前后端分离开发

后端开发的java程序部署在 TomCat服务器中,前端开发的HTML、JS、CSS部署在NGiNX服务器中,为了防止前端发起的请求username,而后端接收请求是name,这样不相符,所以就引入了接口文档,前端开发工程师和后端开发工程师一起阅读这个文档,根据这个文档的规范和要求进行开发。
接口文档如图所示:

前后端开发顺序:


2.2.2 Restful风格:
------前端发起请求的 URL风格 和 请求方式风格。



2.2.3 Apifox
通过浏览器的网址进行请求后端程序(为了测试后端程序返回的数据)都是通过Get的请求方式,就无法使用Restful风格的 Delete/Post/Put 的请求方式。

解决方法:Apifox

Apifox 最大的功能就是:对后端所开发的功能接口进行测试,可以get、delete、put、post。
比如,输入网址和参数,会得到返回信息。


2.2.4 工程搭建
前端请求后端,后端的响应结果都不一样,这样不规范而且难以维护,所以就要在一开始就准备统一的响应结果封装类 Result。

项目搭建正式开始......
1)创建一个空项目,每个创建的空项目都要检查JDK是不是17

2)检查Maven的安装目录是否正确

3)检查字符编码,改为UTF-8,有两处需要修改

4)创建模块Tlias-web-management,并勾选需要的依赖:如图右下角

5)把目录中没有用的包都删掉,static是静态资源目录,是用来存放HTML、CSS、JS等前端静态页面的,现在开发是前后端分离模式,所以,不存放前端代码,就可以删去static和templates包。


6)准备数据库schema 和 表table

7)准备application.yml配置文件的信息

8)实体类要放到pojo包中。注意 表字段名 和 类成员变量名 的命名规范。

result实体类:统一规范响应

9)准备基础代码结构
实体类pojo----Result类、Dept类
业务逻辑层service ---Deptservice(接口)、impl包----DeptserviceImpl实现类
控制层controller-----DeptController类
持久层mapper---DeptMapper(接口)
2.3 查询部门
2.3.1 接口开发
现根据页面原型和接口文档进行分析。


与之前的项目不同点是:1.result响应的返回数据类型
2.采用mybatis框架 ( 注解SQL语句 ) 使用SQL语句------->MySQL数据库
控制层代码:

业务逻辑层代码:

持久层代码:

三层都完成后,就运行启动类,启动程序
之后在Apifox中发起请求,查看返回的数据。
要指定请求方式就这样写:

后来又改为这样写:

小结:

用Apifox 输入http://localhost:8080/dept 即可获得请求数据库的数据。但是却没有createTime和updateTime的值。如图:

null的具体原因:

如何解决?
方法一:

方法二:

方式三:


注意:过程中一致遇到请求返回的响应是406的问题,但是数据库可以正常返回数据,就是一直无法http请求到数据,就去看是否是lombok的问题,把lombok的相关注解删去,采用最原始的Ptg,以后只要有问题就先去看是不是这方面的问题。
2.3.2 前后端联调测试

通过前端点击部门管理,会出现响应的后端TomCat服务器的数据,但是查看网络抓包会发现:

请求的网址并不是Tomcat8080端口的后端服务器,而是90端口的Nginx服务器。通过Nginx服务器的反向代理(反向:说明代理的是对面服务器,也就是TomCat服务器)再把请求发送给后端Tomcat服务器进行处理,处理之后返回给代理,代理再返回给Nginx服务器。


proxy_pass:路径重写之后,要将请求交给哪台服务器处理
rewrite:把api/dept 重写为 /dept
所以要把 /dept 交给 localhost:8080 来处理,所以是: http://localhost:8080/dept

2.4 删除部门
接口开发
1)打开 页面原型 和 接口文档 进行需求分析
接口文档:这次要传递参数id



2)三层架构思路分析

3)controller接收参数的方式


拿到id的值,并将值赋给deptId。

推荐方法三。前端请求参数名称要与方法形参名称一致。

把@RequestParam这个注解中的required 改为 false,参数如果不上传的话就不会报错了,适合参数可选的时候。
4)三层结构实现:


2.5 新增部门
接口开发
1)根据页面原型和接口文档进行需求分析



2)三层架构思路分析

3)接收json格式的请求参数 方法------使用一个对象,但对象的成员变量必须包含请求参数

**注解@RequestBody:**将json格式的请求参数 直接封装到 一个对象当中,这个对象就是方法的形参。

**封装的前提是:**这个对象一定要有 json格式的请求参数 的成员变量,成员变量名和请求参数名称一定要一 样。
4)三层结构代码实现


2.6 修改部门
需求:查询回显+修改数据

2.6.1 查询回显



2)三层架构思路分析

这次使用的是路径参数,不写 ?id=xx,直接写id值 ,采用的是rustful风格,删除的时候也能用。
3)路径参数如何接收

右边是简化:当{ XX } 和方法的形参名称一样时,就可以不用添加注解的变量名。
4)三层架构实现代码


路径参数是 /XX,是路径的一部分,而之前的 ?name=XX ,只是传递参数。
2.6.2 修改数据
1)根据页面原型和接口文档分析 请求参数 和 返回值


页面原型: 对某个ID的记录后面点击 "编辑",并修改部门名称。所以传递的参数是 name值 +ID号
2)三层架构思路分析
接收的是json格式的数据,所以接收请求参数要用对象,注解用 @RequestBody

3)三层架构代码实现

4)以后请求路径地址要抽取公共部分@RequestMapping("/depts")放到controller类的头上。


*、总结
1)传递参数:json格式的请求参数,如插入数据、修改数据,用对象接收参数。
参数少,如查询数据or删除数据,就用路径参数或请求参数来接收,所以三层架构调用方法就要一直 传递参数or对象。
**2)响应数据:**增删改:不返回数据。查询,返回一个,用对象返回;返回多个,用对象列表返回。所以三层架构调用方法就要一直用对象or对象列表作为返回值。
三、日志技术


Slf4j是一套接口,一套规范,底层的实现还是由Log4j或者Logback实现类来实现。
3.1 Logback快速入门


3.2 Logback配置文件详解
用来配置输出的格式、位置及日志开关。

1)<root>具体要输出哪些日志,以及要往哪里输出?(控制台?/文件?)

所有日志都要输出,既要往控制台输出,也要往文件输出。
2)<appender>就是一个输出配置,有FILE:输出到文件,还有STDOUT:输出到控制台。
<pattern>是在控制台和文件中输出日志的格式。


3.3 Logback日志级别

Tlias案例中代码实现

或者采用 注解@slf4j,这个注解是lombok的。

注意配置文件的 level 项目中一般设置为info,因为设置级别太低的话,就会出现很多日志,就淹没关键信息的日志。

四、员工管理
4.1 表关系:一对多
只需要在多的一方把 一的一方 的主键 添加为其字段即可。

SQL语句:一对多

多个表的字段展示:


注:在数据库层面实现一对多------foreign key


还有图形化界面的方法

实际写代码的时候不用foreign key,因为这种物理外键有上面三个缺点,所以现在都用逻辑外键,只写业务代码来实现逻辑外键。

4.2 表关系:一对一

1) 对user_id 的约束:Unique
2)对 user_id 设置 foreign key 外键,关联 另一张表的 id。


4.3 表关系:多对多


之前的 一对多 和 一对一 是通过在其中一个表添加字段、设外键来实现的
现在的多对多,是通过创建一个新的中间表来建立两个表的关系。


4.4 表关系------案例

一般,两个表有关系,就会存储一个外键约束的字段,这个字段就会和另一张表的主键id关联,但是在项目案例中,不能直接显示该字段的值为id值,而要显示 把关联表的id对应的名称。
所以这一小节就是要学会分析:在设计某个模块时,所需要的表,和这些表之间的关系。+各个表中具体的字段和约束。

项目都是用逻辑外键,所以只需要在 多的一方 的表中添加外键字段,这个字段与关联表的主键id对应,所以 外键字段 和 主键字段id 的类型要一致。
4.5 多表查询


在多表查询时,需要消除无效的笛卡尔积 where

4.5.1 内查询 & 外查询


一般用隐式,简单容易

内连接:只会把所有都非空的记录展示出来,如果 某个员工的 dept_id 为 null,那就不会出现这一条数据。如图,有两个 员工的dept_id 为null,那就不会出现这两条数据。

原本有30条数据,现在只有28条。

而外连接,会先把主表拿出来,按照主表id的顺序 与另一张表通过联查条件进行拼凑,如果是dept_id 为null 的员工就不会出现在查询范围内,只有主表和相交部分才可以 被查询。因为dept_id 为null 的员工不在相交范围内,所以不会被查询到。

上图就没有29 和30 id的员工。dept是主表,所以dept的id是按顺序的

上图 是 emp为主表,所以emp的id是按顺序的。但是没有 dept_id为4和的信息。
外连接练习:

查询内容哪个是所有的,就把哪个设为主表,再判断左连接的左右应该放哪个表,左连接的左边的表是主表,右连接右边的表是主表。
4.5.2 子查询

练习:
标量子查询:


列子查询:

行子查询:

表子查询:

4.5.3 多表查询案例



4.6 员工管理---准备工作

注意:
- 所属部门是部门名称,所以需要 dept表+emp表
- 数据库中的每个表,在java程序中都要有一个 JavaBean类与之对应。表的字段 和 类的成员变量是一一对应的。

准备 数据库的表

JavaBean类也要根据数据库的表设计

设计springboot的三层架构+注解

查询所有的员工信息SQL语句(包含所有员工,所以要用外连接)

一个页面是一个功能模块,比如部门管理depts,再比如员工管理emps
4.7 员工管理---分页查询
分析
传递过去的是 1.每页展示记录数 2.页码号
返回到前端的是 1.总记录数 2.当前页的数据列表 ------>>>这两个可以组成一个类


-
分页查询的SQL语句,是在最后面 加一个: limit 起始页号,页的大小。
-
返回的数据列表的emp类,是有deptName的,所以要在emp类的成员变量加一个deptName变量。


注意:

代码示例:
1. EmpController

2. EmpService

3. Mapper

4.7.1 借助PageHelper来完成分页查询


原本的Mapper接口,分页查询需要count(*)和 limit #{start} #{pageHelper}。

现在改为利用PageHelper插件来完成分页功能。

代码示例:

PageHelper类的方法startPage方法执行后,会完成分页操作,分页的结果会存放在page类的对象中,其实就在empList这个对象中,所以要对其进行一次强转。

PageHelper会拦截住所执行的第一个SQL语句,对其进行改进,比如后面加 limit page,pageSize,或者换成select count(0),从而得到总记录数和分页查询的结果,之后把这两个封装在Page这个对象中,之后再从page这个对象调用方法来获取总记录数和分页查询的数据列表。

4.8 员工管理----条件分页查询

接口文档

三层架构

此时的SQL语句要加上 where关键字,所以前端给后端传递的参数就不止 page 和 pageSize 这两个参数。要有 where 关键字的几个参数。
1.EmpController层
日期要注意格式,一定要指定接收的格式。

2.EmpService层
添加几个参数在后面

3.EmpMapper层
这次改用xml映射文件,所以不用@select来写sql语句

4.EmpMapper.xml 映射文件



4.8.1 程序优化

EmpMapper映射文件不用变化,因为对象的属性值也是用 #{ }。

优化2

解决方案:采用动态SQL语句


五、员工管理-----新增员工

传递过来的请求数据:

响应数据 json格式:

新增员工 以及 新增员工工作经历的SQL语句

新增员工三层架构

Emp类与请求参数对齐:

最终代码实现:

批量增加员工经历

把一个对象组成的集合,添加到数据表中,在SQL语句中采用 <foreach>语句。---也是动态SQL
代码示例:
SQL语句------员工经历的

SQL语句------员工的
利用主键返回 注解 @Options。
因为数据表的主键id一般都是自动增加,所以需要返回id值给对象,这个对象是接收请求参数的对象,一切的目标都是为了把员工数据添加到数据表上。

逻辑层


事物管理






这是标注不同日志的插件。

这次有@Transactional注解后,就能实现回滚了,避免不一致的操作。

但是,默认情况下@Transactional注解只会对运行时异常起到回滚操作,如果是直接抛出异常就不能回滚。所以,用到一个属性 rollbackfor="Exception.class",就可以对所有异常进行回滚了。

虽然@Transactional可以做到要么都做,要么都做不了------回滚这种一致性状态,但是并不是说一个方法中有些语句都是要全部执行或者全部不执行的,比如 日志操作,不管是执行成功还是回滚,都要进行操作的日志记录,所以这就需要------事物的传播属性了。
而且执行过程中可能会出现异常,这时就要用到try---finally,来保证不管是否遇到异常,日志操作都能执行,但是,由于异常,一些语句没有执行,所以一荣俱荣,一损俱损的@Transaction特性,导致回滚rollback。所以要使用事物的传播属性------propogation。


让insertlog这个方法每次都在一个全新的事物当中运行,并不是并入最开始的save这个方法的事物中,防止回滚。
事物开始的顺序是:save的事物开始------>>insertLog的事物也开始------>>insertLog事物关闭提交(此时已经写入日志数据表中)--------->>save事物因为异常而回滚。
try---finally 和 事物propagation传播属性 结合,来完成这一操作:不论增加员工成功还是失败,都要执行日志操作。


事物的ACID四大特性

文件上传


表单里的每一项输入input后面的name后面的值,都是上传的变量名,后端用UploadController控制层的方法里的形参来接收参数,所以变量名要一样。
当前端的form一行的后面 没有设置enctype这个属性时,就不会把图片的任何值上传。如下图:

如果有encType这个属性,提交请求的结果应是如下图:会把文件的名字和内容都上传。

换成火狐浏览器:如下图:

如果提交的是文本文件:

服务器接收这些参数并在返回响应之前,都要把参数先存放在服务器的某个临时文件中,但是响应后,这些临时文件又空了,所以在响应之前要保存。

文件上传------本地下载
控制层代码

但是,当上传的文件过大时,会无法上传,这时就要去application.yml配置文件中更改配置。

apifox请求:


文件上传------存储到阿里云OSS
本地存储文件的缺点三个:





第一步:准备工作:

具体操作见javaweb接口文档 09-后端Web实战(员工管理) - 飞书云文档

AccessKey千万不能泄露,如果泄露了,立即点击"禁用"。

第二步:参照官方SDK编写入门程序

参考阿里云OSS中的SDK来编写入门程序
第三步:案例集成OSS

接口文档------请求参数 
接口文档------响应数据


先把阿里云OSS文件上传工具类放到utils软件包中


该工具类最后返回的是上传的文件在网上公开的url。

该工具类的逻辑是:本身就定义的有bucket,还有阿里云OSS的链接,方法是将传过来的content放到bucket中,也将传过来的原本文件名改为在bucket中的新文件名。最后返回上传的文件公开的url。
upload方法接口。当点击上传文件的那一刻,调用的是该upload接口,当显示上传的图片时,接口就成功了。当点击"新增员工"的时候,调用的是save接口,期间会把姓名、年龄、文件的公开路径都上传给相关数据库。

结果展示:

文件上传------阿里云OSS程序优化


但是,当需要配置的参数过多时,就会出现新的问题:无法复用,还是要一个一个地标注@Value





把配置的值都放在application.yml都放在配置文件里,想用的话,一用@Value,二用@ConfigurationProperties。
六、员工管理------删除员工
6.1 接口文档
请求参数

响应参数

6.2 三层架构

6.3 如何接受请求的多个值?

1. 用数组接收必须请求参数名和变量名称要一致。
2. 用集合接受必须请求参数名和变量名称要一致,并且变量名称前面要加上@RequestParam。
逻辑层service层

因为在逻辑层的实现类中的delete方法中调用了两个mapper方法,执行两次SQL语句操作,所以要加上事务控制注解@Transactional(rollback={Exception.class})。
EmpMapper和xml映射文件


<foreach>的作用是拼接SQL语句,一直把集合中的所有元素拼接完,才执行SQL语句。foreach中的collection属性是mapper.java文件中的方法的形参的数组。

七、员工管理------修改员工
7.1 查询回显
接口文档------请求参数

采用的是路径参数 用注解@PathVariable。
该查询回显和上面的分页查询不一样,一个路径是/emps,另一个路径是/emps/1,有id的值,会根据id值查询
接口文档------响应数据
响应数据是json格式

三层架构

控制层

逻辑层

持久层

xml映射文件

但是这样写的话,查询出来有两个记录,因为该员工有两条工作记录。
所以,我们改进了mybatis的封装过程。

要在 Mapper.xml 文件中定义 ResultMap。 type属性是我们要把这些属性封装到哪个类---Emp。
id属性字段要用<id>标签;普通属性用<result>标签;集合属性用<collection>标签。根据类其中的集合对象的属性来写<id>和<result>标签。
xml文件改为:
XML
<!--
建立一个resultMap,便于查询结果更好的封装到Emp类中。
-->
<resultMap id="EmpResultMap" type="com.itheima.pojo.Emp">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="name" property="name"/>
<result column="gender" property="gender"/>
<result column="phone" property="phone"/>
<result column="job" property="job"/>
<result column="salary" property="salary"/>
<result column="image" property="image"/>
<result column="entry_date" property="entryDate"/>
<result column="dept_id" property="deptId"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<collection property="exprList" ofType="com.itheima.pojo.EmpExpr">
<id property="id" column="ee_id"/>
<result property="empId" column="ee_empid"/>
<result property="begin" column="ee_begin"/>
<result property="end" column="ee_end"/>
<result property="company" column="ee_id"/>
<result property="job" column="ee_job"/>
</collection>
</resultMap>
<select id="getById" resultMap="EmpResultMap">
select e.*,
ee.id ee_id,
ee.emp_id ee_empid,
ee.begin ee_begin,
ee.end ee_end,
ee.company ee_cpmpany,
ee.job ee_job
from emp e left join emp_expr ee on e.id=ee.emp_id where e.id=#{id}
</select>

查询回显注意:一般都是根据请求的id来返回emp类,但是要看这个类里面有没有复杂对象,比如集合,如果有,就要采用resultMap。
7.2 修改数据
接口文档------请求参数


接口文档------响应数据

三层架构

而修改数据是 请求参数是json格式的emp对象,emp对象的属性值都是修改后的内容,emp对象里面还包括工作经历exprList集合,都要考虑到。
只要是涉及到两个数据表的操作,比如新增员工时,要新增员工基本信息 还要新增该员工的工作经历,那么就要在两个Mapper里写代码;再比如删除员工时,还有这次的修改员工时,所以一个service逻辑层要修改两个mapper。
代码:
控制层:

逻辑层:

持久层:


修改员工程序优化
如果以后在前端设置了一个按钮,用来修改员工信息,但是不包含所有的员工信息,比如只包含员工的姓名username,那么执行后端后,emp对象只接收了username,其他成员变量比如gender、name、job等都变成null,此时就会修改数据库表中 的数据。所以现在要在mapper.xml文件中修改SQL语句,使用<if>标签来完成。 如下图:

请求链接如图:

执行结果如图:

总结:
-
先看接口文档的请求参数,如果是json格式且多个参数,就用对象接收@RequestBody,这种情况有:新增员工,会上传员工的基本信息和员工经历,还有修改员工,也会上传员工的基本信息和员工经历。这种的用emp对象来接收参数。
-
还有上传的参数是?page=10&pageSize=5&name=阮&gender=2这些的时候,也可以用对象来接受,但是此时不能有@RequestBody,这时的对象是empParams,根据查询条件写的类。
-
删除员工、查询员工、都是上传的id或者id的集合。
-
一个数据库的表table对应一个java中的类,如果一个接口涉及两个表两个类,那么就要在持续层操控两个表,在逻辑层也操控两个表。逻辑层的方法还要有@Transactional注解来事务控制。
-
前端后端的一个接口其实就是对一个数据库增删改查,完成增删改查这些功能。其他接口是对其他数据库的增删改查功能。
-
把一个对象组成的集合,添加到数据表中,在SQL语句中采用 <foreach>语句。可以在数据库的表中动态地增加多个记录,也可以动态地删除一个集合ids中的id。
-
一定要好好观察接口文档,两个点:(1)请求路径的第一个参数,能区别是否一个控制层的名字。(2)最后操纵的是哪个数据库的表,判断用哪个Mapper接口。逻辑层跟随控制层。
八、异常处理
当出现异常时,前端并没有出现任何提示信息。无法正确处理异常。

起初是想修改代码,用try-catch语句来捕获异常,执行result.error代码的异常处理。

设计一个异常处理器,当出现异常时,会被异常处理器捕捉,前端呈现异常结果

如图所示:

但是这种异常提示并不能让人知道到底是哪方面出错,所以又设计了一个异常处理器,当有多个异常处理器时,会根据出现的异常的类型从下往上找,最先在下面的就是该异常。



九、员工信息统计

9.1 职位统计

三层架构

请求路径变了,那就要重新创建service层、controller层、mapper层,例如reportController、reportService、reportMapper。


代码实现:




前端效果

9.2 性别统计
前端是根据这个e-chart来写的,所以后端返回的数据是 value 和 name。

接口文档------请求参数

接口文档------响应数据

这种响应数据,就是一个集合,集合的每个对象包含两个属性name和value,就像之前的查询员工基本信息一样,响应数据也是集合,集合的每个对象是Emp类,包含username、password、name等各种属性。
现在可以用对象集合,也可以用map集合,List<Map<string,object>>这种的,键key都是string字符串类型,而值value有各种类型,所以用object。直接用该方法的类型去接收,mybatis直接把查询结果封装到List<Map>中,就跟封装到List<Emp>一样,自动封装。
List<Map<string,object>>这种的封装一般是,字段名为键,属性值为值。如下图:

代码实现:


前端展示结果:
