JavaWeb 学习笔记

D01 web及HTML#

Web网站的工作流程:路由器 - 前端服务器 - 后端Java程序 - 数据库服务器 = 前后端分离开发

前端Web开发

  • HTML、CSS、JavaScript
  • Vue、Element、Nginx

后端Web开发

  • Maven
  • SpringBoot Web
  • MySQL
  • SpringBoot Mybatis

Web标准:三个组成部分 HTML(网页的结构)、CSS(网页的表现)、javaScript(网页的行为)

HTML#

  • 结构标签:html - head body
  • 特点:不区分大小写、属性值单双引号都可以、语法松散

CSS#

CSS引入方式:行内/内嵌/外联(写在单独的css文件中)

<link rel="stylesheet" href="style.css">

CSS 颜色表示形式:关键字/rgb(0,0,0)/十六进制表示#ff0000

CSS 选择器:元素选择器(元素名称 element)/ID选择器(元素ID #time)/类选择器(元素CLASS .cls)

超链接#

<a href="" target="_self or _blank">

  • _self: 当前页面,默认
  • _blank: 新页面
  • _parent: 现在已经不常用,在父级框架打开
  • _top: 现在已经不常用,在整个窗口中打开链接

text-decoration: none; 指定文本装饰。

视频#

  • src: 视频的url
  • controls: 显示播放控件
  • width
  • height

音频#

  • src: 音频的url
  • controls: 显示播放控件

段落#

<p> 划分段落。

  • text-indent: 首行缩进
  • text-align: 文本对齐设置

文本加粗#

<b> / <strong>

HTML页面的整体布局#

盒子模型:内容区域content、内边距区域padding、边框区域border、外边距区域margin

div标签#

  • 一行只显示一个
  • 宽度默认是父元素的宽度
  • 可以设置宽高

span标签#

  • 一行可以显示多个
  • 宽度和高度默认由内容撑开
  • 不可以设置宽高

边距元素#

顺时针 - 上右下左

表格标签#

  • table: 定义表格整体
  • tr: 表格的行
  • td: 表格单元格,表头则为th

表单标签#

form -> action/method

  • input: 表单项,通过type控制输入形式
  • select: 下拉列表
  • textarea: 文本域
  • input type=submit: 提交按钮

能采集数据必须有内部属性name

label标签,聚焦区域全部元素;单选/多选按钮一组内需要有相同的name;提交的为value值

D02 JS和Vue#

JavaScript是跨平台、面向对象的脚本语言,是用来控制网页行为的,使得网页可交互。

JS的引入方式#

  • 内部脚本 <script></script>
  • 外部脚本 <script src=".."></script> (不能自闭合)

JS的基础语法#

书写语法#

  • 区分大小写
  • 每行结尾的分号可有可无,但建议加上
  • // 注释 /* 注释 */

输出语句

  • window.alert() 写入警告框
  • document.write() 写入HTML输出
  • console.log() 写入浏览器控制台

变量#

  • (const) var/let 声明变量,let作用于块级作用域,var作用于函数作用域 const表示只读
  • js是弱类型语言,变量可以存放不同类型的值
  • var定义的变量可以重复定义(声明),let则不行

数据类型、运算符、流程控制#

  • 原始类型:number、string、boolean、null、undefined
  • typeof [var] 获取数据类型

运算符

  • 算数运算符
  • 赋值运算符
  • 比较运算符: === 全等运算符,不会进行类型转换; == 会进行类型转换
  • 逻辑运算符
  • 三元运算符

类型转换

  • parseInt()字符串转换为数字,若字面值不是数字,则转为NaN
  • 0和NaN转为boolean为false,其它为true

流程控制语句

  • if…else…
  • switch
  • for
  • while
  • do…while

JS的函数#

被用来执行特定任务的代码块。

  • function functionName(参数1, 参数2){ // 代码 }
  • var functionName = function(参数1, 参数2){ // 代码 }

JS的对象#

  • Array String JSON
  • BOM对象: 浏览器封装的对象模型
  • DOM对象: 文档对象模型

Array#

  • var 变量名 = new Array(元素列表)
  • var 变量名 = [元素列表]

相当于java中的集合,长度可变(直接赋值即可)、类型可变

  • 属性:array.length
  • 方法:forEach() 遍历一遍有值的元素、push() 将新元素添加到末尾并返回新的长度、splice() 删除元素

ES6 箭头函数 (e) => {...}

String#

  • var 变量名 = new String("...")

  • var 变量名 = "..."

  • 属性:string.length

  • 方法:charAt() 返回在指定位置的字符、indexOf() 检索字符串、trim() 去除字符串两边的空格、substring() 截取子字符串

JSON#

  • var jsObject = JSON.parse(userStr)
  • var jsonStr = JSON.stringify(jsObject)

BOM#

  • window 浏览器窗口对象
    • history、location、navigator - location.href
    • alert()、confirm()、setInterval()、setTimeout()
  • Navigator 浏览器对象
  • Screen 屏幕对象
  • History 历史记录对象
  • Location 地址栏对象

DOM#

封装对应对象:

  • Document: 整个文档对象
  • Element: 元素对象
  • Attribute: 属性对象
  • Text: 文本对象
  • Comment: 注释对象

DOM

JS的事件监听#

事件:发生在HTML元素上的事情:按钮被点击/鼠标移动到元素上/按下键盘按键。

绑定方式#

  • 通过HTML标签中的事件属性绑定 onclick="on()"
  • 通过DOM元素属性绑定 document.getElementById('btn').onclick = function(){}

常见事件#

常见事件

Vue#

基于MVVM(Model-View-ViewModel)思想,实现数据双向绑定的前端框架。

插值表达式#

  • 形式 {{ 表达式 }}
  • 内容可以是:变量、三元运算符、函数调用、算术运算

常用指令#

指令:HTML标签上带有v-前缀的特殊属性,不同指令具有不同含义

  • v-bind 为HTML标签绑定属性值
  • v-model 在表单元素上创建双向数据绑定
  • v-on 为HTML标签绑定事件
  • v-if 条件性渲染某元素
  • v-else-if
  • v-else
  • v-show 根据条件展示某元素
  • v-for 列表渲染

// 绑定的数据变量必须在数据模型中声明。 eg.

  • <a v-bind:href="url">AAA</a> / <a :href="url">AAA</a>::为属性绑定数据模型url
  • <input type="text" v-model="url">:绑定表单元素,输入的数据会自动绑定到url上
  • <input type="button" value="按钮" v-on:click="handle()" / <input type="button" value="按钮" @click="handle()":为HTML标签绑定事件
  • <span v-show="age <= 35">年轻人</span>:条件判定(只通过display判断是否展示)
  • <div v-for="(addr, index) in addrs">{{ addr }}</div>:遍历数组

生命周期#

生命周期:一个对象从创建到销毁的整个过程。

生命周期的八个阶段

mounted字段对Java后端最重要。

D03 Ajax和Element#

Ajax#

Ajax - 异步的JavaScript和XML。作用:数据交换、异步交互。

原生Ajax#

  1. 准备数据
  2. 创建XMLHttpRequest对象
  3. 向服务器发送请求
    4,获取服务器响应数据

Axios#

  1. 引入Axios的js文件
  2. 使用Axios发送请求,并获取响应结果
1
2
3
4
5
6
7
8
9
axios({
method: "get",
url: "xxx",
data: "id=1"
}).then(result => {
console.log(result.data);
}) // 手动构建

axios.get(url[, config]) // 精简方法

前后端分离开发#

需求分析 - 接口定义(API接口文档) - 前后端并行开发 - 测试 - 前后端联调测试

前端工程化#

前端开发:模块化(JS\CSS)、组件化(UI结构\样式\行为)、规范化(目录结构\编码\接口)、自动化(构建\部署\测试)

Vue的目录结构#

  • node_modules 整个项目的依赖结构
  • public 存放项目的静态文件
  • src 存放项目源代码
  • src/assets 静态资源
  • src/components 可复用的组件
  • src/router/ 路由配置
  • src/views/ 视图组件(页面)
  • src/App.vue 入口页面(根组件)
  • src/main.js 入口js文件
  • package.json 存放项目基本信息及依赖资源
  • vue.config.js 存放vue配置信息

Element#

参考

案例#

  • 创建页面,完成页面的整体布局规划。
  • 布局中各个部分的组件实现。
  • 列表数据的异步加载,并渲染展示。

Vue路由#

前端路由:URL中的hash与组件之间的对应关系。

Vue Router 是Vue的官方路由。由VueRouter(路由器类,维护路由表)、<router-link>(请求链接组件)、<router-view>(动态视图组件,用来渲染)

打包部署#

打包:执行脚本build,最终文件会在dist目录下生成。将dist目录内容使用nginx部署即可。

D04 Maven及Web入门#

Maven是一款用于管理和构建Java项目的工具。

  • 依赖管理,管理项目依赖的资源、避免版本冲突
  • 统一项目结构
  • 标准化的项目构建流程

需要使用的架包只需要在pom.xml(Project Object Model)中声明依赖即可。

Maven的介绍#

仓库:用于存储资源,管理各种jar包。本地仓库/中央仓库/远程仓库。

  • bin\ 存放可执行文件
  • conf\ 存放maven的配置文件
  • lib\ maven本身的依赖

IDEA集成Maven#

  • 配置Maven环境
  • 创建Maven项目
  • 导入Maven项目

Maven坐标#

:资源的唯一标识,通过坐标可以唯一定位资源位置。可以使用坐标来定义项目或引入项目中需要的依赖。

坐标组成

  • groupId: 项目隶属组织名称,通常为域名反写
  • artifactId: 当前项目名称/模块名称
  • version: 项目版本号

导入Maven项目#

  • 打开IDEA,选择右侧Maven面板,点击+号,选中对应项目的pom.xml文件,打开即可。
  • File - Project Structure - + - Import Module - + - pom.xml。

依赖管理#

依赖配置#

<dependencies> 标签中的若干个 <dependency> 子标签。

依赖传递#

依赖具有传递性。

  • 直接依赖:在当前项目中通过依赖配置建立的依赖关系。
  • 间接依赖:当前项目间接依赖其它资源。

排除依赖:在项目中主动断开依赖的资源。目的:减少版本冲突、排除不需要的实现、减少安全漏洞、减少包体积

<exclusions> 标签,放置于 <dependency> 内部即可,引用方法与依赖同理,但无需定义版本号。

依赖范围#

依赖的jar包在默认情况下全局可用,可通过<scope>...</scope>设置作用范围。取值:compile/test(仅测试)/provided(主程序和测试程序)/runtime(测试程序和打包)

maven的生命周期#

Maven的生命周期就是为了对所有的maven项目构建过程进行抽象和统一。

  • clean: 清理工作。(- clean)
  • default: 核心工作。(- compile test package install)
  • site: 生成报告、发布站点等。

主要需要注意的阶段

  • clean 移除上一次构建生成的文件
  • compile 编译项目源代码
  • test 使用合适的单元测试框架进行测试
  • package 将编译后文件打包
  • install 安装项目到本地仓库

在同一套生命周期中,运行后面的阶段时,前面的阶段都会运行。

  • IDEA运行:直接在idea中双击对应阶段运行
  • CMD运行:mvn xxx

在idea中可手动跳过某个阶段。

SpringBootWeb 入门#

SpringBoot可帮助我们快速的构建应用程序,简化开发,提高效率。

略。

HTTP协议#

计算机网络课程中已有,此处快速略过。

概述 - 请求协议 - 响应协议 - 协议解析

特点:

  • 基于TCP协议,面向连接、安全
  • 基于请求-响应模型,一次请求对应一次响应
  • 无状态协议,每次请求-响应都是独立的

HTTP状态码

  • 1xx 响应中,临时状态码,ws中用得多
  • 2xx 成功
  • 3xx 重定向
  • 4xx 客户端问题
  • 5xx 服务器问题

HTTP状态码

Tomcat#

一个轻量级的web服务器,支持servlet、jsp等少量JavaEE规范。也被称为web容器/servlet容器。

起步依赖:把开发某功能常见的依赖聚合在了一起。例如springboot的父依赖。

D05 请求响应和分层解耦#

  • HttpServletRequest 请求对象、HttpServletResponse 响应对象
  • B-S架构 浏览器/服务器架构(维护方便 体验一般)、C-S架构 客户端/服务器架构模式(开发和维护麻烦 体验不错)

请求#

1
2
3
4
5
6
7
8
9
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(@RequestParam(name = "name") String username) {
// String name = request.getParameter("name");
System.out.println("hello " + username);
return "hello " + username;
}
}
  • @RequestRaram中的required属性默认为true,表示该参数必须传递,否则将报错400。若为可选参数,可将required属性设置为false。

实体参数#

可将所有参数封装到实体类中。实体类中可再封装实体类,使用.区别。

1
2
3
4
5
6
7
8
public class User {
private String name;
private Integer age;

// get方法
// set方法
// toString方法
}

数组集合参数#

  • 前端请求方式:使用两个相同的key。
  • 后端接收方式:使用数组 String[] hobby / 集合 @RequestParam List<String> hobby (@RequestParam 这样才能将多个请求参数值封装到集合)

日期集合参数#

形参类似: @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime 服务端接收时需要指定前端传递的日期参数的格式。

JSON参数#

JSON数据键名与形参对象属性名相同,定义形参即可接收参数,需要使用 @RequestBody 标识。

路径参数#

参数作为URL的一部分。使用{...}来标识该路径参数,需要使用@PathVariable来获取路径参数。

1
2
3
4
@RequestMapping("/path/{id}")
public String pathParam(@PathVariable Integer id){
return "OK";
}

响应#

@ResponseBody

  • 类型:方法注解、类注解
  • 位置:Controller方法上/类上
  • 作用:将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转换为JSON格式响应
  • @RestController = @Controller + @ResponseBody

可封装一个Result类统一响应的格式,便于项目的维护。

分层解耦#

单一职责原则:一个方法尽量只起到一个功能

三层架构#

  • controller: 控制层,接收前端发送的请求,对数据进行处理,并响应数据
  • service: 业务逻辑层,处理具体的业务逻辑
  • dao: 数据访问层 Data Access Object,负责数据访问操作,包括数据的crud。

创建接口处理,将web接口分为三层。

_分层解耦#

  • 内聚:软件中各个功能模块内部的功能联系。
  • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
  • 软件设计原则:高内聚低耦合

使用容器类来封装特定实现类实现低耦合。

  • 控制反转:IOC,对象的创建控制权由程序自身转移到外部(容器)。
  • 依赖注入:DI,容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
  • Bean对象:IOC容器中创建、管理的对象

IOC & DI 入门#

  1. 将Service层与Dao层的实现类交给IOC容器管理,在类上新增注解@Component
  2. 为Controller及Service注入运行时,依赖的对象,在成员变量上加上注解@Autowired - 自动装配、简化开发、实现解耦
  3. 运行测试
  4. 需要修改调用的实现类时,更换@Component的位置即可。

IOC详解#

将对象的控制权交给IOC容器,由容器来创建和管理这些对象。

bean的声明

  • @RestController = @Controller + @ResponseBody

每一个Bean都有一个标识,在声明bean的时候可以通过注解当中的value属性来指定bean的名字。默认名字为类名(首字母小写)。指定方式 @Repository(value = "DaoA") / @Repository("DaoA"),一般不用指定。

使用以上四个注解都可以声明bean,但在springboot集成web开发中,只能使用@Controller。


声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描。在创建时已经在启动类声明注解@SpringBootApplication中包含,默认扫描的范围是启动类所在包及其子包。

  1. 手动在新包中设置包扫描 @ComponentScan({“包名”, “原包名”}) // 不推荐。
  2. 按照SpringBoot项目规范,将包全部放在启动类所在包下。

DI详解#

@Autowired默认按照类型进行,如果存在多个相同类型的bean则会报错

解决方案:

  • @Primary: 在@Service之前设置bean的优先级
  • @Qualifier("A"):在@Autowired上一行声明需要注入的是哪个bean
  • @Resource(name = "empB"):在声明的上一行声明需要注入的是哪个bean(该注解是jdk提供的,是按照名称注入)

D06 MySQL、数据库设计、数据库操作#

本部分学习过,仅作补充记录。

  • 关系型数据库:建立在关系模型基础上,由多张相互连接的二维表组成的数据库。
    • 使用表存储数据,格式统一,便于维护
    • 使用SQL语言操作,标准统一,使用方便,可用于复杂查询
  • SQL:一门操作关系型数据库的编程语言,定义操作所有关系型数据库的统一标准。

部分标签#

  • signed: 有符号范围(默认)
  • unsigned: 无符号范围,只能存储正数,范围翻倍
  • double(5,2) 长度位是5,小数位数是2,例如123.45
  • char节省性能,varchar节省空间

D07 MySQL 查询、多表操作#

  • 起始索引=(页码-1)*每页记录数
  • ECharts
  • case ... [when ... then ...] else ... end

物理外键#

  • 影响增删改的效率
  • 仅适用于单节点数据库
  • 容易引发数据库的死锁问题

逻辑外键#

在业务中保证数据的完整性和一致性,而不在数据库中设置外键。

D08 MySQL 多表查询、事务、索引、Mybatis 入门#

多表查询#

  • 标量子查询 </>/=
  • 列子查询 in/not in
  • 行子查询 =/<>/in/not in - 使用(a,b)=(select a,b from xxx)实现
  • 表子查询 from - 作为临时表使用

如果可以用连接查询替代子查询,尽量使用连接查询(效率更高)。

事务#

事务是一组操作的集合,是一个不可分割的工作单位,会把所有操作作为一个整体一起向系统提交/撤销操作请求,这些操作要么同时成功,要么同时失败。

  • 在MySQL中,默认事务是自动提交的。

事务控制#

  • 开启事务:start transaction; / begin;
  • 提交事务:commit;
  • 回滚事务:rollback;

四大特性#

  • 原子性 A:事务是不可分割的最小操作单元
  • 一致性 C:事务完成时,必须使所有数据都保持一致
  • 隔离性 I:事务在不受外部并发操作影响的独立环境下运行
  • 持久性 D:事务一旦提交或回滚,对数据库中的数据的改变是永久的

索引#

索引是帮助数据库高效获取数据的数据结构。

  • 优点:提升查询效率,降低IO成本。/通过索引降低数据排序的成本,降低CPU消耗
  • 缺点:占用存储空间,降低insert、update、delete的效率

使用的树#

MySQL的索引默认为B+树。

  • 一个叶可以存储多个指针。每一个节点,可以存储多个key。
  • 所有数据都在叶子节点保存,非叶子节点只用于存储数据。
  • 叶子节点按从小到大顺序排序,形成双向链表。

语法#

  • 创建索引 create [unique] index 索引名 on 表名(字段名);
  • 查看索引 show index from 表名
  • 删除索引 drop index 索引名 on 表名

主键字段在建表时会自动创建,性能最高;添加唯一约束时,实则是添加了唯一索引。

Mybatis#

一款优秀的持久层框架,用于简化JDBC的开发。

Mybatis 入门#

步骤:准备工作、引入Mybatis依赖及配置(配置在application.properties中)、编写SQL语句

框架底层会自动生成实现类对象,并将该对象交给IOC容器管理。

数据库连接池:容器,负责分配、管理数据库连接。可以做到资源重用、提升系统响应速度、避免数据库连接遗漏。实现接口为DataSource。

不同连接池的区别:

  • HikariCP为速度而生。它是一个追求极致性能、轻量、简洁的连接池,目标是做到最快、最高效、最低开销。
  • Druid为监控和扩展而生。它不仅仅是一个连接池,更像一个数据库中间件,提供了强大、详尽的监控和丰富的扩展功能。

Lombok是实用的Java类库,能通过注解的形式自动生成各种方法,并可以自动化生成日志变量,简化Java开发、提高开发效率。

Lombok

Mybatis 增删改查#

  • 准备数据库表emp
  • 创建一个新的springboot工程,选择引入起步依赖
  • 在application.properties中引入数据库连接信息
  • 创建对应实体类Emp
  • 准备Mapper接口 EmpMapper

增删改操作可以为int返回值,返回本次操作影响的记录数。

1
2
@Delete("delete from emp where id = #{id}") // 预编译SQL,性能更高且防止SQL注入
public void delete(Integer id);

对表名、字段名进行动态设置时使用${id},但需要注意可能存在SQL注入。

增(接口方法)

1
2
3
@Insert("insert into emp(username, name)") + 
"values(#{username}, #{name})" // 实体类中的属性名
public void insert(Emp emp); // 使用实体类封装多个参数

主键返回,需要在接口上添加注解@Options(keyProperty = "id", useGeneratedKeys = true)

1
2
@Update("update emp set username = #{username}, name = #{name}") // 实体类中的属性名
public void update(Emp emp);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id); // 只会封装字段名称相同的值

// 封装处理
// 1. 给字段起别名
@Select("select id, username, dept_id deptId from emp where id = #{id}")
public Emp getById(Integer id);
// 2. 起注解进行手动映射封装
@Results({
@Result(column = "dept_id", property = "deptId")
})
@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);
// 3. 开启自动映射的开关
// 配置application.properties: mybatis.configuration.map-underscore-to-camel-case=true

条件查询

1
2
3
@Select("select * from emp where name like '%${name}%' and gender = #{gender} and ") + 
"entrydate between #{begin} and #{end} order by update_time desc"
public List<map> list(String name, Short gender, LocalDate begin, LocalDate end);

可以使用concat字符串拼接函数,防止sql注入

1
2
3
@Select("select * from emp where name like concat('%', #{name}, '%') and gender = #{gender} and ") + 
"entrydate between #{begin} and #{end} order by update_time desc"
public List<map> list(String name, Short gender, LocalDate begin, LocalDate end);

XML映射文件方式,规范:

  • XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)
  • XML映射文件的namespace属性为Mapper接口全限定名一致
  • XML映射文件中sql语句的id与Mapper接口中的方法名一致,并保持返回类型一致

XML映射

MyBatisX插件 复杂语句建议使用XML,简单语句使用注解即可。

Mybatis 动态SQL#

随着用户的输入或外部条件的变化而变化的SQL语句,我们称为动态SQL。

<if> 标签用于判断条件是否成立。使用test属性进行条件判断,若条件为true,则添加SQL。

动态SQL

<where> 标签会自动判断里面的条件是否成立,并进行拼接/去除多余的and/or。

where条件-动态SQL

需求:动态更新员工信息,传递时有值才更新、没有传值不更新。

<set> 标签会自动在行首插入set关键字,并会删掉额外的逗号(在update语句中)
set条件-动态SQL

<foreach> 标签,循环遍历 SQL:delete from emp where id in (18,19,20)

public void deleteByIds(List<Integer> ids);

foreach条件-动态SQL

<sql> <include> 标签:配置在后期会存在代码复用性差的问题

1
2
3
4
5
6
<sql id="xxx">
select id, username
</sql>
<!-- 定义sql片段 -->
<include refid="xxx">
<!-- 引入sql片段 -->

D10 综合案例(P74-85)#

Restful 表述性状态转换,通过请求方式的区分来定义接口功能的不同。

  • REST是风格,不是规定,可以打破
  • 描述功能模块通常使用复数形式,表示此类资源

一个完整的请求路径,应该为类上的 @RequestMapping 的value属性 + 方法上的 @RequestMapping 的value属性

日志技术#

  • JUL:JavaSE平台提供的官方日志框架,配置相对简单、不够灵活、性能较差
  • Log4j:流行的日志框架,提供了灵活的配置选项,支持多种输出目标
  • Logback:基于Log4j升级而来,提供了更多的功能和配置选项,性能更佳
  • (规范)Slf4j:简单日志门面,提供了一套日志操作的标准接口及抽象类,允许应用程序使用不同的底层日志框架

日志级别#

日志级别

D11 综合案例-多表关系(重复) & 员工管理(P86-100)#

PageHelper是第三方提供的在Mybatis框架中用来实现分页的插件,用来简化分页操作、提高开发效率。

  • PageHelper语句结尾不能加分号
  • PageHelper仅对设置分页操作的下一次查询生效

在插入数据后获取主键 @Options(useGeneratedKeys = true, keyProperty = "id")

D12 事务管理 & 文件上传(P101-109)#

注解:@Transactional 使用于业务层的方法、类、接口上 - 将当前方法交给spring进行事务管理。推荐加在一个多次进行增删改的接口上。

rollbackFor - 出现何种异常要进行事务的回滚 eg. @Transactional(rollbackFor = {Exception.class})。默认出现运行时异常才会回滚。s

事务传播行为:当一个事务被另一个事务方法调用时,应当如何进行事务控制。

事务传播

参数配置化,通过 @Value("${aliyun.oss.endpoint}$") 注解引入。也可批量注入(需要注入的属性较多时更方便),封装到实体类中:@Data @Component @ConfigurationProperties(prefix = "aliyun.oss")

D13 删除员工 & 修改员工 & 异常处理(P110-118)#

Mybatis中封装查询结果时,若查询返回的字段名与实体的属性名可以对应,用resultType,否则用resultMap手动封装。

定义全局异常处理器: @RestControllerAdvice = @ControllerAdvice + @ResponseBody@ExceptionHandler

D14 实战问题记录(P119)#

  • Q:无法自动装配。找不到 ‘StudentService’ 类型的 Bean。
  • A:实现类没有继承 StudentService 导致找不到实现的Service。
  • Q:创建mapper时错误声明了其为类,需要重新声明为接口?
  • A:删除重建 / 将class修改为interface,同时修正方法体。
  • Q:部门下是否存在员工的判定?
  • A:Autowired导入一个EmpMapper类,对类单独写一个实现方法 select count(*)。

  1. Mapper层从数据库表映射到实体类Dept,最终结果放置在List<Dept>中。
  2. Service层创建一个专门用于数据传输的类DTO(Data Transfer Object),接收到对象并将Dept的信息映射到DTO_Dept中。
  3. Controller层在@RestController注解的情况下会字段进行序列化,形成json。

ECharts处理#

Mapper:查询出包含班级名、人数的列表。#

Mapper 接口

1
List<Map<String, Object>> getClassCounts();

Mapper XML

1
2
3
4
5
6
7
8
<select id="getClassCounts" resultType="java.util.Map">
SELECT
c.name,
COUNT(s.id) AS cnt
FROM clazz c
LEFT JOIN student s ON c.id = s.clazz_id
GROUP BY c.name
</select>

从而获得类似于

1
2
3
4
5
6
[
{ "name": "Java一班", "cnt": 22 },
{ "name": "前端二班", "cnt": 33 },
{ "name": "Python三班", "cnt": 44 },
{ "name": "Go四班", "cnt": 55 }
]

的数据结构。

Service:接收原始列表,根据业务需求重组数据,将其拆分成两个列表并放在某个容器中。#

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override
public Map<String, Object> getClassReport() {
// 1. 从Mapper获取原始数据列表
List<Map<String, Object>> rawData = yourMapper.getClassCounts();

// 2. 准备两个空的列表,一个放名字,一个放数量
List<String> nameList = new ArrayList<>();
List<Long> cntList = new ArrayList<>(); // count(*)返回的是Long,用Long接收最安全

// 3. 遍历原始数据列表,进行拆分
for (Map<String, Object> item : rawData) {
// 从当前项中取出name,添加到nameList
nameList.add((String) item.get("name"));

// 从当前项中取出cnt,添加到cntList
cntList.add((Long) item.get("cnt"));
}

// 4. 创建一个最终的Map,作为容器
Map<String, Object> finalReport = new HashMap<>();

// 5. 把两个列表分别以"name"和"cnt"为键,放入最终的Map中
finalReport.put("name", nameList);
finalReport.put("cnt", cntList);

// 6. 返回这个加工好的Map
return finalReport;
}

Controller:Spring自动序列化。#

1
2
3
4
5
@GetMapping("/classReport")
public Result getClassReport() {
Map<String, Object> reportData = yourService.getClassReport();
return Result.success(reportData);
}

最终的映射结果:

1
2
3
4
5
6
7
8
{
"code": 1,
"msg": "success",
"data": {
"name": ["Java一班", "前端二班", "Python三班", "Go四班"],
"cnt": [22, 33, 44, 55]
}
}

D15 登录(P120-132)#

登录接口查数据库表 where username = xxx and password = xxx

  • 登录标记:用户登录成功后,在后续每次请求中都可以获取到该标记(会话技术)
  • 统一拦截:过滤器Filter、拦截器Interceptor

会话技术#

  • 会话:用户打开浏览器,访问web服务器的资源,会话建立 - 有一方断开连接,会话结束。
  • 会话跟踪:服务器需要识别多次请求是否来自同一浏览器,以便在同一会话的多次请求之间共享数据。
  • 客户端会话跟踪技术:Cookie / 服务端会话跟踪技术:Session / 令牌技术:JWT

Cookie#

Cookie是HTTP协议所支持的技术。响应头 Set-Cookie;请求头 Cookie

  • 移动端APP无法直接使用。
  • 不安全,用户可以自己禁用cookie。
  • Cookie不能跨域。

Session(底层基于Cookie)#

Cookie - JSESSIONID

声明HttpSession session,存值session.setAttribute(),取值session.getAttribute()

  • 数据存储在服务端,安全。
  • 服务器集群环境下无法直接使用Session
  • 有Cookie的所有缺点。

令牌#

  • 支持PC端、移动端
  • 解决集群环境下的认证问题
  • 减轻服务器存储压力
  • 但需要自己实现

JWT令牌#

登录成功后生成令牌,统一拦截校验。

  • 第一部分:Header,记录令牌类型、签名算法
  • 第二部分:Payload,携带一些自定义信息、默认信息
  • 第三部分:Signature,防止Token被篡改、确保安全性

使用

  • 引入jjwt的依赖。
  • 调用官方提供的工具类Jwts来生成或解析jwt令牌。

生成令牌

1
2
3
4
5
6
7
8
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", 1);
dataMap.put("username", "admin");
String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "secretKey")
.addClaims(dataMap)
.setExpiration(new Date(System.currentTimeMillis() + 3600*1000))
.compact();
System.out.println(jwt);

解析令牌

1
2
3
4
Claims claims = Jwts.parser().setSigninKey("secretKey")
.parseClaimsJws("token_string")
.getBody();
System.out.println(claims);

令牌失效或被篡改,在解析时会报错。

过滤器Filter#

过滤器可以把对资源的请求拦截下来,从而实现特殊的功能。可以完成一些通用的操作,包括登录校验、统一编码处理、敏感字符处理等。

定义一个类实现Filter接口(init doFilter destroy)、@WebFilter(urlPatterns="/*")@ServletComponentScan

在Filter过程中验证是否为登录请求、验证是否携带token、验证token是否有效。

过滤器链:在一个web应用中可以配置多个过滤器。优先级默认按照类名(字符串)的自然排序。

拦截器Interceptor(Spring)#

拦截器是一种动态拦截方法调用的机制,类似于过滤器,主要用来动态拦截控制器方法的执行。

  • 定义:实现HandlerInterceptor接口的preHandle、postHandle、afterCompletion方法;
  • 配置:定义一个配置类实现WebMvcConfigurer接口,注册拦截器(/**

区别:拦截路径需要用.addPathPatterns("/**") 增加拦截路径、.excludePathPatterns("/login") 排除拦截路径。/* 拦截一级路径、 /** 任意级路径

过滤器和拦截器的区别#

  • 接口规范不同:过滤器需要实现Filter接口,拦截器需要实现HandlerInterceptor接口
  • 拦截范围不同:过滤器会拦截所有的资源(所有的HTTP请求)、Web容器层面,拦截器只会拦截Spring环境中的资源(访问Controller的请求)、依赖于Spring容器

D16 SpringAOP(P133-140)#

AOP - 面向切面编程

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect // 表明这个类是切面类
@Component
public class RecordTimeAspect throws Throwable { // 切面类,切面所在的类
@Around("execution(* com.itheima.service.*.*(..))") // 切入点表达式,表达的就是切入点
public Object recordTime(ProceedingJoinPoint pjp) { // 通知
long beginTime = System.currentTimeMillis();
Object result = pjp.proceed();
long endTime = System.currentTimeMillis();
log.info("方法 {} 执行耗时: {}ms", pjp.getSignature(), endTime - beginTime);
return result;
}
}
  • 优点:减少重复代码、代码无侵入、提高开发效率、维护方便。
  • 开发步骤:引入AOP依赖、编写AOP程序
  • 应用场景:记录系统操作日志、事务管理、权限控制…

核心概念#

  • 连接点:JoinPoint,可以被AOP控制的方法
  • 通知:Advice,指共性功能(最终体现为一个方法)
  • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用 - 实际被AOP控制的方法
  • 切面:Aspect,描述通知与切入点的对应关系
  • 目标对象:Target,通知所应用的对象

执行流程#

创建代理对象,为目标对象实现同一个接口,使用通知方法中的逻辑。

注释的类型#

  • @Around:在目标方法前后均执行 -> 需要自己调用方法让原始方法执行,返回值必须指定为Object
  • @Before:在目标方法前执行
  • @After:在目标方法后执行,是否有异常都执行
  • @AfterReturning:在目标方法后执行,有异常时不会执行
  • @AfterThrowing:在目标方法发生异常后执行

@Pointcut:公共的切入点表达式

1
2
3
4
5
6
7
8
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pt() {} // 在外部的切面类中也可以引用该表达式
// private void pt() {} 仅能在当前切面类中引用该表达式

@Around("pt()")
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
// anything.
}

通知顺序#

当有多个切面的切入点都匹配到了目标方法,目标方法运行时多个通知方法都会被执行。

顺序:不同切面类中,默认按照切面类的类名字母排序。或者可以用@Order(数字)控制顺序,数字越小越先执行。

切入点表达式#

execution(...) 基于方法的签名来匹配#

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)

  1. 访问修饰符:可省略(public、protected)
  2. 包名.类名:可省略 – 不建议省略
  3. throws 异常:可省略(是方法上声明抛出的异常,不是实际抛出的异常)
  4. * 可以通配任意一个参数或一部分
  5. .. 多个连续的任意符号,可以通配任意层级的包、或任意类型任意个数的参数
  6. 支持逻辑运算符,与/或多个execution

书写建议

  • 方法名在命名时尽量规范,方便切入点表达式快速匹配
  • 描述切入点方法通常基于接口描述,增强扩展性
  • 尽量缩小切入点的匹配范围,防止效率降低

@annotation(...) 根据注解匹配#

  1. 定义注解anno,在注解方法上新增注解@Target(Element.METHOD)@Retention(RetentionPolicy.RUNTIME)

连接点#

ProceedingJoinPoint(仅对于@Around通知) 或 JoinPoint 类型,可以获得方法时的执行信息。

  • getTarget() 获取目标对象
  • getTarget().getClass().getName() 获取目标类
  • getSignature().getName() 获取目标方法
  • getArgs() 获取目标方法参数

ThreadLocal#

ThreadLocal实为线程的局部变量,为每个线程提供一份单独的存储空间,具有线程隔离的效果、不同的线程之间不会相互干扰。

  • public void set(T value) 设置当前线程的线程局部变量的值
  • public T get() 返回当前线程所对应的线程局部变量的值
  • public void remove() 移除当前线程的线程局部变量

操作步骤

  • 定义ThreadLocal操作的工具类
  • 在TokenFilter中解析当前登录的ID,存入ThreadLocal CurrentHolader.setCurrentId(empId)
  • 从线程中获取ID CurrentHolder.getCurrentId()
  • 在AOP程序中获取当前员工ID,删除 CurrentHolder.remove()

D17 SpringBoot原理&Maven高级(P141-151)#

配置文件的优先级#

以下配置文件优先级由大到小,但推荐统一使用一种。

  • 命令行参数
  • Java系统属性
  • application.properties
  • application.yml(主流)
  • application.yaml

启动时还可以通过Java系统属性或命令行参数进行属性配置,优先级更高。

Bean作用域#

bean作用域共有以下五种,后三种在web环境才生效。

  • singleton 容器内同名称的bean只有一个实例
  • prototype 每次使用该bean时创建新的实例
  • request 请求范围内创建新的实例
  • session 会话范围内创建新的实例
  • application 应用范围内创建新的实例

可以添加 @Scope("xxx") 设置bean的作用域。在单例中 @Lazy 表示延迟初始化,延迟到第一次使用时再创建这个bean。

实际开发中,绝大部分的bean都会是单例的。可以节约资源、提升性能。无状态的bean即是线程安全的,有状态的bean在单例时 多线程同时操作时为线程不安全。

第三方Bean#

  • 如果要管理的bean对象来自第三坊,需要在启动类中使用 @Bean 注解。通过bean注解的name或value属性可以声明bean的名称,若不声明、默认为方法名。
  • 若要管理的第三方bean对象,建议对这些bean进行集中分类配置,可以通过@Configuration注解声明一个配置类。

自动配置#

自动配置:Spring项目启动后,一些配置类、bean对象自动存入到IOC容器中,不需要手动去声明,从而简化开发、省略配置。

通过@ComponentScan注解扫描指定的包。 /

在启动类中用@Import 注解导入的类(可以直接导入普通类,无需在类上添加@Component等注解)会被Spring加载到IOC容器中。

导入ImportSelector接口的实现类可以实现批量导入类。

还可将import注解再封装到一个注解当中,这样就能通过注解@Enablexxx批量导入。(更方便)

自动配置-@Conditional#

自动配置 @Conditional 按照一定的条件进行判断,在满足给定条件后才会注册对于的bean对象到Spring IOC容器中。

  • @ConditionalOnClass :判断环境中是否有对应的字节码文件,才注册
  • @ConditionalOnMissingBean :判断环境中没有对应的bean,才注册
  • @ConditionalOnProperty :判断配置文件中有对应属性和值,才注册

自定义starter#

在实际开发中经常会定义一些公共组件提供给各个项目团队使用,可以将这些组件封装为SpringBoot的start。

Maven高级#

分模块设计:将一个大项目拆分为若干个子模块,方便项目的管理、维护、扩展。

拆分策略

  • 按照功能模块拆分
  • 按层拆分
  • 按照功能模块 + 层拆分

先针对模块功能进行设计,再进行编码。

继承#

子工程可以继承父工程中的配置信息,常见于依赖关系的继承。可以简化依赖配置、统一管理依赖。 使用标签:<parent>。不支持多继承,但支持多重继承。

  • jar:普通模块打包,springboot项目基本都是jar包
  • war:普通web程序打包,需要在外部的tomcat服务器运行
  • pom:父工程或聚合工程,不写代码,仅进行依赖管理

  • 在子工程中配置继承关系后,坐标中的groupId可以省略、会自动继承父工程
  • relativaPath指定父工程的pom文件的相对位置,若不指定,会自动从本地仓库/远程仓库查找

版本锁定#

如果不是所有项目都需要用的依赖,不太建议在父工程引入。在maven中,可以在父工程的pom文件中通过 <dependencyManagement> 来统一管理依赖版本。

自定义属性可以在 <properties> 标签中定义<lombok.version>1.18.34</lombok.version>,在版本中用 ${lombok.version} 引用即可。

聚合#

将多个模块组织成一个整体,同时进行项目的构建。

  • 首先需要有一个聚合工程,有且仅有一个pom文件(可以与父工程同一个)。
  • maven中可以通过<modules>设置当前聚合工程所包含的子模块名称。

继承和聚合#

  • 继承与聚合都属于设计型模块,打包方式都为pom,常设计到同一个pom文件中。
  • 继承用于简化依赖配置、统一管理依赖版本
  • 聚合用于快速构建项目,在父工程中配置聚合的模块

私服#

私服是一种特殊的远程仓库,架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部的资源共享与资源同步问题。

项目版本:RELEASE/SNAPSHOT

  1. 设置私服的访问用户名/密码 - settings.xml中的servers中配置
  2. IDEA的maven工程的pom文件中配置上传地址
    上传地址配置
  3. 设置私服依赖下载的仓库组地址(快照默认不可使用)
    设置仓库组地址

后端总结(P152)#

Controller(接收请求) - Service(逻辑处理) - Dao(数据访问、操作数据库) - MySQL

过滤器、拦截器 - 拦截前端发起的请求

IOC/DI/AOP/事务管理/全局异常处理/Cookie.Session/JWT/阿里云OSS/Mybatis

D18 Vue + ElementPlus(P152-159)#

  • <script setup> 预处理
  • <style scoped> 设置样式的作用域范围
1
2
3
4
5
6
import { ref } from 'vue';
const count = ref(0);

function increment(){
count.value++;
}

Element 引入中文语言#

1
2
import zhCn from 'element-plus/es/locale/lang/zh-cn'
createApp(App).use(ElementPlus, {locale: zhCn}).mount('#app')

D20 Tlias案例(P160-166)#

可以在vite.config.js中配置前端请求服务器信息。

配置规则

D21 Tlias案例(P167-174)#

watch函数:侦听一个或多个响应式数据源,并在数据源变化时调用传入的回调函数。

  • 导入watch函数
  • 执行watch函数,传入要侦听的响应式数据源喝回调函数
  • {deep: true} 深度侦听,侦听对象的全部属性
  • {immediate: true} 在 代码创建时立即侦听
  • 亦可以侦听对象的单个属性 user -> () => user.value.age

动态绑定:<el-option v-for="j in jobs" :key="j.value" :label="j.name" :value="j.value"></el-option>

在import时可以为函数取别名 import {xxx as aaa} from 'xxx'

D21 Tlias案例(P175-181)#

  • Ajax请求头中携带令牌 - 通过request拦截器完成
  • 收到401响应跳转到登录页面 - 通过response拦截器完成
  • 使用nginx反向代理进行路径重写(rewrite / proxy_pass)

D22 项目部署-Linux(P182-190)#

  • ls -l <dir> = ll <dir>:显示指定目录下内容
  • head [-n] fileName:输出文件开头的n行内容
  • tail [-n] fileName 输出文件末尾的n行内容