Java IO流

Lu Lv3

JavaWeb学习

1.初识web前端

Web标准
Web标准也称为网页标准,由一系列的标准组成,大部分由W3C(World Wide Web Consortium,万维网联盟)组织制定。

  • 三个组成部分:
    • HTML:负责网页的结构(页面元素和内容)
    • CSS:负责页面的表现(页面元素的外观,位置等页面样式,如:颜色、大小等)
    • JavaScript:负责页面的行为(页面元素的交互,如:鼠标点击、滑动等) ^f1dcc5

1.1 HTML

HTML(HyperText Markup Language):超文本标记语言。

  • 超文本:超越了文本的限制,比普通文本更强大。除了文字信息,还可以定义图片、音频、视频等内容。
  • 标记语言:由标签构成的语言。
    • HTML标签都是预定义好的。例如:使用<a>展示超链接,使用<img>展示图片、<video>展示视频等。
    • HTML代码直接在浏览器中运行,HTML标签由浏览器解析。

HTML结构标签

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 这里设置了HTML文档的基本结构,包括文档类型、语言、字符编码和视口设置 -->
</head>
<body>
<!-- 这里是网页的主体部分,内容将显示在这里 -->
</body>
</html>

语言特点

  • HTML标签不区分大小写
  • HTML标签属性值单双引号都可以
  • HTML语法松散
  • 在HTML中无论输入多少个空格,只会显示一个,可以使用空格占位符:&nbsp;

1.1.1 标题标签

  • 标签:<h1>…<h1> (h1 -> h6 重要程度依次降低)
  • 示例
    1
    2
    3
    <h1>标题1</h1>
    <h2>标题2</h2>
    <h3>标题3</h3>

1.1.2 水平线标签

  • 标签:<hr>

1.1.3 换行标签

  • 标签:<br>

1.1.4 段落标签

  • 标签:<p>…<p>

1.1.5 文本加粗标签

  • 标签:<b>…</b> 或者 <strong>…</strong>

1.1.6 图片标签

  • 标签:<img src=”图片路径” width=”宽度” height=”高度” alt=”替代文本”>
    • src:指定图像的路径url(绝对路径/相对路径)
    • width:指定图像的宽度(像素/相对于父元素的百分比)
    • height:指定图像的高度(像素/相对于父元素的百分比)
    • alt:指定图像的替代文本

1.1.7 视频标签

  • 标签:<video src=”视频路径” controls width=”宽度” height=”高度”>
    • src:指定视频文件的路径url(绝对路径/相对路径)
    • controls:指定是否显示播放控件
    • width:指定视频的宽度(像素/相对于父元素的百分比)
    • height:指定视频的高度(像素/相对于父元素的百分比)

1.1.8 音频标签

  • 标签:<audio src=”音频路径” controls>
    • src:指定音频文件的路径url(绝对路径/相对路径)
    • controls:指定是否显示播放控件

1.1.9 超链接标签

  • 标签:<a href=”链接地址” target=”…”>链接文本</a>
    • href:指定链接地址url(绝对路径/相对路径)
    • target:指定链接打开方式(_self:默认值,当前窗口打开;_blank:新窗口打开)

1.1.10 布局标签

实际开发网页中,会大量频繁的使用div和span这两个没有语义的布局标签。

  • 标签:<div>…</div> 或者 <span>…</span>
  • 特点
    • div标签:
      • 一行只显示一个(独占一行)
      • 宽度默认是父元素的宽度,高度默认由内容撑开
      • 可以设置宽高(width、height)
    • span标签
      • 一行可以显示多个(不独占一行)
      • 宽度和高度默认由内容撑开
      • 可以设置宽高(width、height)

1.1.11 表格标签

  • 标签
    • <table>…</table>:定义表格
    • <tr>…</tr>:定义表格中的行,一个<tr>表示一行
    • <th>…</th>:表示表头单元格,具有加粗居中效果
    • <td>…</td>:表示普通单元格
  • 场景:在网页中以表格(行、列)形式整齐展示数据,如:班级表。

1.1.12 表单标签

  • 标签:<form action=”提交地址” method=”提交方式”>
    • action:指定表单提交的地址url(绝对路径/相对路径)
    • method:指定表单提交的方式(get/post)
      • get:表单数据拼接在url后面,大小有限制。eg:?username=java
      • post:表单数据放在请求体中,大小无限制。eg:username=java
  • 表单项
    • <input type="…">:定义表单项,通过type属性控制输入形式
    • <select>…</select>:定义下拉列表,<option>定义列表项
    • <textarea>…</textarea>:定义多行文本输入框

      注意:表单项必须有name属性才可以提交。

1.2 CSS

CSS(Cascading Style Sheets):层叠样式表,用于控制页面的样式(表现)。

1.2.1 盒子模型

  • 盒子:页面中的所有元素(标签),都可以看做是一个盒子,由盒子将页面中的元素包含在一个矩形区域内,通过盒子的视角更方便的进行页面布局
  • 盒子模型:盒子由内容区域(content)内边距区域(padding)边框区域(border)外边距区域(margin)组成。
  • 属性
    • width:设置盒子的宽度
    • height:设置盒子的高度
    • border:设置盒子的边框属性,如:border: 1px solid red/#000/……;
    • padding:设置盒子的内边距
    • margin:设置盒子的外边距

      注意:如果只需要设置某一个方位的边框、内边距、外边距,可以在属性名后加上 -位置,如:border-top、padding-left、margin-right。

1.2.2 CSS引入方式

  • 行内样式:写在标签的style属性中(不推荐)
    1
    <p style="color: red;"></p>
  • 内嵌模式:写在style标签中(可以写在页面任何位置,但通常约定写在head标签中)
    1
    2
    3
    4
    5
    6
    7
    <style>
    h1 {
    <--!属性名:属性值;-->
    xxx: xxx;
    xxx: xxx;
    }
    </style>
  • 外联模式:写在外部一个单独的.css文件中(需要通过 link 标签在网页中引入)
    1
    <link rel="stylesheet" href="css/style.css">

1.2.3 CSS选择器

  • 元素选择器:标签名 {…}
  • id选择器:#id属性值 {…}
  • 类选择器:.class属性值 {…}
    优先级:id选择器 > 类选择器 > 元素选择器

1.2.4 CSS属性

  • 颜色和背景

    • color:设置文本颜色。
    • background-color:设置背景颜色。
    • background-image:设置背景图片。
    • background-repeat:设置背景图片的重复方式。
    • background-size:设置背景图片的大小。
  • 字体和文本

    • font-family:设置字体系列。
    • font-size:设置字体大小(记得加px)
    • font-weight:设置字体粗细。
    • font-style:设置字体样式(例如:italic)。
    • text-transform:设置文本的大小写(uppercase, lowercase, capitalize)。
    • text-decoration:设置文本的装饰(underline, overline, line-through, none)。
    • letter-spacing:设置字母间距。
    • word-spacing:设置单词间距。
  • 边距和填充

    • margin:设置外边距。
    • padding:设置内边距。
  • 边框

    • border:设置边框。
    • border-width:设置边框宽度。
    • border-style:设置边框样式(solid, dashed, dotted)。
    • border-color:设置边框颜色。
    • border-radius:设置圆角边框。
  • 布局

    • display:设置元素的显示类型(block, inline, flex, grid, none)。
    • position:设置元素的定位类型(static, relative, absolute, fixed, sticky)。
    • top, right, bottom, left:设置定位偏移。
    • z-index:设置元素的堆叠顺序。
    • overflow:设置溢出内容的处理方式(visible, hidden, scroll, auto)。
  • 尺寸

    • width:设置元素的宽度。
    • height:设置元素的高度。
    • max-width:设置元素的最大宽度。
    • max-height:设置元素的最大高度。
    • min-width:设置元素的最小宽度。
    • min-height:设置元素的最小高度。
  • 浮动和清除

    • float:设置元素的浮动(left, right, none)。
    • clear:设置浮动的清除(left, right, both, none)。
  • 表格

    • border-collapse:设置是否合并表格边框。
    • border-spacing:设置表格单元之间的边框间距。
    • caption-side:设置表格标题的位置(top, bottom)。
    • empty-cells:设置是否显示空单元格(show, hide)。
  • 其它

    • line-height:设置行高
    • text-indent:设置首行的缩进
    • text-align:设置元素中文本的水平对齐方式(left/center/right/justify)

1.3 JavaScript

  • JavaScript(简称:JS)是一门跨平台、面向对象的脚本语言。是用来控制网页行为的语言,它能使网页可交互。
  • JavaScript和Java是完全不同的语言,不论是概念还是设计,但是基础语法类似。
  • JavaScript在1995年由Brendan Eich发明,并于1997年成为ECMA标准。
  • ECMAScript 6(ES6)是最新的JavaScript版本。

ECMA:ECMA国际(前身为欧洲计算机制造商协会),制定了标准化的脚本程序设计语言 ECMAScript,这种语言得到广泛应用。而JavaScript是遵守ECMAScript标准的脚本语言。

1.3.1 JS引入方式

  • 内部脚本:将JS代码定义在HTML页面中
    • JavaScript代码必须位于<script>……</script>标签之间
    • 在HTML文档中,可以在任意地方,放置任意数量的<script>标签
    • 一般会把脚本置于<body>标签中,这样,脚本会在页面加载后执行,改善显示速度。
    1
    2
    3
    <script>
    alert("Hello JavaScript!");
    </script>
  • 外部脚本:将JS代码定义在外部单独的JS文件中,然后引入到HTML页面中
    • 外部JS文件中,只包含JS代码,不包含<script>标签,也不包含HTML代码
    • <script>标签不能自闭合
    1
    <script src="js/demo.js"></script>
    1
    2
    // demo.js文件
    alert("Hello JavaScript!");

1.3.2 基础语法

1.书写语法:

  • 区分大小写:JavaScript区分大小写,变量名、函数名、关键字等必须使用正确的大小写。
  • 每行结尾的分号可有可无
  • 注释
    • 单行注释:// 注释内容
    • 多行注释:/* 注释内容 */
  • 大括号表示代码块
    1
    2
    3
    4
    // 判断
    if (condition) {
    alter(count);
    }
    2.输出语句
  • 使用 window.alert() 函数输出警告框【window可以省略】
  • 使用document.write() 函数输出到页面
  • 使用 console.log() 函数输出到浏览器控制台
1
2
3
4
5
window.alert("Hello JavaScript!"); // 浏览器弹出警告框

document.write("Hello JavaScript!"); // 写入HTML,在浏览器中显示

console.log("Hello JavaScript!"); // 写入浏览器控制台

3.变量

  • JavaScript中用 var 关键字(variable的缩写)声明变量。
    • var 声明的变量在其所在的函数或全局范围内有效,具有函数作用域,而不是块级作用域。这意味着即使 var 声明在块(如 if 或 for 循环)内部,变量仍然在函数范围内或全局范围内有效。
    • var声明的变量可以被重复声明,这可能导致意外的行为,因此在现代JavaScript中通常不推荐使用。
    1
    2
    3
    4
    5
    {
    var a = 10;
    var a = "A"; // 允许重复声明
    }
    console.log(a); // 输出 "A"
  • JavaScript是一门弱类型语言,变量可以存放不同类型的值
    1
    2
    var a = 10;
    a = "张三";// 变量 `a` 的类型从数字变成了字符串
  • 变量名需要遵循如下规则:
    • 组成字符可以是任何字母、下划线、$符号
    • 不能以数字开头
    • 建议使用驼峰命名

注意

  • ECMAScript 6(ES6)中,新增了let关键字来定义变量。它的作用类似于var,但是所声明的变量具有块级作用域,只在let关键字所在的代码块中有效,且不能重复声明。
1
2
3
4
{
let b = 10;
// let b = 20; // 会抛出错误,因为 `b` 已经在同一块级作用域中声明过
}
  • ECMAScript 6(ES6)中,新增了const关键字,用来声明一个只读的常量,常量的值一旦声明就不能改变。【但是,const 并不意味着所引用的对象是不可变的。如果 const 引用的是一个对象(包括数组),对象的内容是可以改变的】
1
2
3
4
5
const c = 10;
// c = 20; // 会抛出错误,因为 `c` 是常量

const obj = { name: "张三" };
obj.name = "李四"; // 允许改变对象内部属性的值

4.数据类型
JavaScript中的数据类型分为:原始类型引用类型

  • 原始类型
    • number:表示数字类型,可以是整数、浮点数和 NaN(Not a Number)。
    • string:表示字符串类型,单引号和双引号都可以。
    • boolean:表示布尔类型,只能是 true 或 false。
    • null:表示空值,表示一个空对象。
    • undefined:表示未定义的值,表示一个未初始化的变量。
  • 引用类型
    • object:用于存储键值对的集合。对象的键(属性)可以是字符串或符号,值可以是任何类型的数据,包括其它对象。
    • array:用于存储有序列表的集合。数组的元素可以是任何类型的数据,包括其它数组。可以通过索引(从0开始)访问数组的元素。
    • function:表示函数类型。函数在JavaScript中是”一等公民”,意味着函数可以作为变量的值,作为参数传递给其他函数,也可以作为返回值。
    • ……

使用typeof关键字可以获取变量的类型。

1
2
var a = 10;
alter(typeof a);

5.运算符

  • 算术运算符:+,-,*,/,%,++,–
  • 赋值运算符:=,+=,-=,*=,/=,%=
  • 比较运算符,!=,</mark>=!==,>,<,>=,<=
  • 逻辑运算符:&&,||,!
  • 三元运算符:条件表达式? true_value: false_value,

<mark></mark>=的区别:

  • ==:比较两个值是否相等,忽略类型。【会进行类型转换】
  • ===:比较两个值是否相等,同时比较类型。【不会进行类型转换】
1
2
3
4
var a = 10;
alter(a == "10"); // true
alter(a === "10"); // false
alter(a == 10); // true

类型转换

  • 字符串类型转为数字
    • 将字符串面值转为数字。如果字面值不是数字,则返回NaN。
  • 其它类型转为boolean
    • Number:0和NAN为false,其它为true。
    • String:空字符串为false,其它为true。
    • Null和Undefined为false。

parseInt():将字符串转为整数的函数。它的语法如下:

1
parseInt(string, radix)
  • string: 你想要解析的字符串。这个字符串的开头可以包含空格,但在遇到第一个无法转换为数字的字符时,parseInt 会停止解析并返回已经解析的部分。字符串中的非数字字符会导致解析停止。

  • radix: 可选参数,表示进制数。它的值可以在 2 到 36 之间。如果不指定 radixparseInt 会根据字符串的内容自动确定进制:

    • 如果字符串以 “0x” 或 “0X” 开头,parseInt 会将其视为 16 进制。
    • 如果字符串以 “0” 开头,parseInt 会将其视为 8 进制(在老版本的 JavaScript 中,现代浏览器已经不再这么处理)。
    • 否则,parseInt 会将其视为 10 进制。
1
2
3
4
5
6
parseInt("42");           // 返回 42
parseInt("101", 2); // 返回 5 (二进制的 101 为十进制的 5)
parseInt("0xF", 16); // 返回 15 (16 进制的 F 为十进制的 15)
parseInt("10", 8); // 返回 8 (8 进制的 10 为十进制的 8)
parseInt("10", 10); // 返回 10 (十进制的 10 为十进制的 10)
parseInt("abc123"); // 返回 NaN (因为 "abc" 不是数字)

注意事项

  • 如果 string 的第一个字符无法转换为数字,那么 parseInt 将返回 NaN
  • NaN (Not a Number) 是 JavaScript 中表示一个非数字的特殊值。
  • 你可以使用 isNaN() 函数来检查 parseInt 的返回值是否是 NaN

6.流程控制语句

  • if…else:用于条件判断。
  • switch:用于多分支判断。
  • for:用于循环。
  • while:用于循环。
  • do…while:用于循环。

参考官方文档

1.3.3 JS函数

  • 介绍:函数(方法)是被设计为执行特定任务的代码块。
  • 定义:JavaScript 函数通过 function 关键字来定义。语法为:
    1
    2
    3
    4
    5
    6
    7
    8
    // 方式一:
    function functionName(parameter1, parameter2, ...) {
    // 要执行的代码
    }
    // 方式二:
    var functionName = function(parameter1, parameter2, ...) {
    // 要执行的代码
    }
  • 注意
    • 形式参数不需要类型。因为JavaScript 是弱类型语言,参数类型由调用者决定。
    • 函数可以返回值。如果函数没有返回值 或者 return 后没有跟随任何值,则返回 undefined
    • 返回值也不需要定义类型,可以在函数内部直接使用return返回值即可
  • 调用:函数名称(实际参数列表)
1
2
3
4
5
6
7
8
9
10
11
12
13
// 方式一:
function add(a, b) {
return a + b;
}
var result = add(1, 2);
console.log(result);

// 方式二:
var add = function(a, b) {
return a + b;
}
var result = add(1, 2);
console.log(result);

注意事项
JS中,函数调用可以传递任意个数的参数。但只会截取需要的前几个

1.3.4 JS对象

1.Array

  • 介绍:JavaScript 数组是一种特殊的对象,用于存储多个值。
  • 定义
    1
    2
    3
    4
    5
    6
    // 方式一:
    var 变量名 = new Array(元素列表);
    var arr = new Array(1, 2, 3);
    // 方式二:
    var 变量名 = [元素列表];
    var arr = [1, 2, 3];
  • 赋值/访问
    1
    2
    3
    arr[索引] = 值;
    arr[10] = "hello";
    alter(arr[10]);// 警告框输出hello

注意事项
JavaScript 中的数组相当于 Java 中的集合,数组的长度是可变的,而JavaScript是弱类型,所以可以存储任意的类型的数据。

1
2
3
4
5
6
7
8
9
10
var arr = [1, 2, 3];
arr[10] = "hello";

console.log(arr[9]); // undefined
console.log(arr[10]); // hello

arr[9] = "A";
arr[8] = true;

console.log(arr);
  • 属性
    • length:表示数组中元素的数量。你可以读取这个属性来获取数组的长度,也可以设置它来改变数组的长度。
      • 设置 length:如果设置 length 为比当前长度小的值,数组将被截断。如果设置为更大的值,数组会用 undefined 填充新的位置。
  • 方法
    • toString():将数组转换为字符串,元素之间用逗号分隔。
    • join(separator):将数组转换为字符串,元素之间用指定的 separator 分隔。如果不提供 separator,则使用逗号作为默认分隔符。
    • forEach(callback):对数组中的每个元素执行一次提供的函数 callbackcallback 接受三个参数:当前元素值、当前元素的索引、整个数组。forEach 不会改变原数组。
    • push(element1, ..., elementN):将一个或多个新元素添加到数组的末尾,并返回数组的新长度。
    • splice(start, deleteCount, item1, ..., itemN):用于在数组中添加或删除元素。start 是开始位置的索引,deleteCount 指定要删除的元素数量,item1, ..., itemN 是要添加的新元素。这个方法会直接修改原数组,并返回被删除的元素。

代码示例:

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
29
30
31
32
33
// 创建一个数组
let arr = [1, 2, 3];

// length 属性
console.log(arr.length); // 输出: 3
arr.length = 5;
console.log(arr); // 输出: [1, 2, 3, undefined, undefined]

// toString() 方法
console.log(arr.toString()); // 输出: "1,2,3,,"

// join() 方法
console.log(arr.join('-')); // 输出: "1-2-3--"

// forEach() 方法
arr.forEach((element, index) => {
console.log(`Index ${index}: ${element}`); // 或者 console.log("Index " + index + ": " + element);
// ${} 是模板字符串(template literals)中的一种语法,用于在字符串中插入变量或表达式的值。模板字符串使用反引号(`)括起来,并允许在字符串中嵌入表达式。
});
// 输出:
// Index 0: 1
// Index 1: 2
// Index 2: 3
// Index 3: undefined
// Index 4: undefined

// push() 方法
arr.push(6);
console.log(arr); // 输出: [1, 2, 3, undefined, undefined, 6]

// splice() 方法
arr.splice(2, 2, "a", "b");
console.log(arr); // 输出: [1, 2, "a", "b", undefined, 6]

箭头函数(ES6):是用来简化函数定义语法的,具体形式为:(……) => {…},如果需要给箭头函数起名字:var func = (……) => {…}

2.String

  • 介绍:JavaScript 字符串是一种特殊类型的对象,用于存储和操作文本。

  • 定义

    1
    2
    3
    4
    5
    6
    // 方式一:
    var 变量名 = new String("……");
    var str = new String("Hello, World!");
    // 方式二:
    var 变量名 = "……";
    var str = "Hello, World!";
  • 属性

    • length:返回字符串的长度,表示字符串中字符的个数。
  • 方法

    • charAt(index):返回指定位置的字符。index 从 0 开始计算。
    • indexOf(substring):返回子字符串在字符串中第一次出现的位置,如果没有找到,则返回 -1
    • trim():去除字符串两边的空格(包括换行符和制表符)。
    • substring(startIndex, endIndex):提取字符串中从 startIndexendIndex 之间的字符,endIndex 处的字符不包括在内。【包左不包右】

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// length 属性
let str = "Hello, world!";
console.log(str.length); // 输出: 13

// charAt() 方法
console.log(str.charAt(7)); // 输出: "w"

// indexOf() 方法
console.log(str.indexOf("world")); // 输出: 7
console.log(str.indexOf("foo")); // 输出: -1

// trim() 方法
let strWithSpaces = " Hello, world! ";
console.log(strWithSpaces.trim()); // 输出: "Hello, world!"

// substring() 方法
console.log(str.substring(0, 5)); // 输出: "Hello"
console.log(str.substring(7, 12)); // 输出: "world"

3.自定义对象

  • 介绍:JavaScript 中的对象是一种复杂的数据类型,可以包含多个属性和方法。
  • 定义
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /* var 对象名 = {
    属性名1: 值1,
    属性名2: 值2,
    属性名3: 值3,
    函数名称:function(参数列表) {} ,可以省略":function"
    }; */
    var person = {
    name: "John",
    age: 30,
    gender: "Male",
    eat: function() { eat(){
    alert("Eating..."); == alert("Eating...");
    } }
    };
  • 调用
    1
    2
    3
    4
    /* 对象名.属性名;
    对象名.方法名(); */
    console.log(person.name);
    person.eat();

4.JSON

  • 介绍
    • JSON是通过JavaScript对象标记法书写的文本。
    • JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,主要用于在网络传输数据。
    1
    2
    3
    4
    5
    {
    name: "John",
    age: 30,
    gender: "Male",
    }

  • 定义
    1
    2
    var 变量名 = '{"属性名1":"值1","属性名2":"值2","属性名3":"值3"}';
    var person = '{"name":"John","age":30,"gender":"Male","addr":["北京","上海","广州"]}';
    值的数据类型为:
    • 数字(整数或浮点数)
    • 字符串(在双引号中)
    • 逻辑值(true 或 false)
    • 数组(在方括号中)
    • 对象(在花括号中)
    • null
  • 方法
    • JSON.parse(json):将 JSON 字符串转换为 JavaScript 对象。
    • JSON.stringify(obj):将 JavaScript 对象转换为 JSON 字符串。
    1
    2
    var jsObject = JSON.parse(person);
    var jsonStr = JSON.stringify(jsObject);

5.BOM

  • 介绍:BOM(Browser Object Model)是浏览器对象模型的缩写,它提供了与浏览器窗口交互的接口。JavaScript 将浏览器的各个组成部分封装为对象。
  • 组成
    • Window:浏览器窗口对象
    • Navigator:浏览器信息对象
    • Screen:屏幕信息对象
    • History:历史记录对象
    • Location:地址栏对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 获取
window.alert("Hello BOM");
alert("Hello BOM Window");

// 方法
// confirm - 对话框 -- 确认: true,取消: false
var flag = confirm("您确认删除该记录吗?");
alert(flag);

// 定时器 - setInterval -- 周期性地执行某个函数
var i = 0;
setInterval(function() {
i++;
console.log("定时器执行了" + i + "次");
}, 2000);

// 定时器 - setTimeout -- 延迟指定时间执行一次
setTimeout(function() {
alert("JS");
}, 3000);

1
2
3
// location
alter(location.href);
location.href = "https://www.baidu.com";

6.DOM

  • HTML中的Element对象可以通过Document对象获取,而Document对象可以通过window对象获取。
  • Document对象中提供了以下获取Element元素对象的函数:
    • getElementById(id):根据元素的id属性值获取元素对象,返回单个Element对象。
    • getElementsByTagName(tagName):根据元素的标签名获取元素对象,返回一个Element对象的数组
    • getElementsByName(name):根据元素的name属性值获取元素对象,返回一个Element对象的数组
    • getElementsByClassName(className):根据元素的class属性值获取元素对象,返回一个Element对象的数组
    1
    2
    3
    4
    5
    6
    7
    8
    // 根据元素的id属性值
    var h1 = document.getElementById('h1');
    // 根据元素的标签名
    var divs = document.getElementsByTagName('div');
    // 根据元素的name属性值
    var hobbys = document.getElementsByName('hobby');
    // 根据元素的class属性值
    var clss = document.getElementsByClassName('cls');

7.事件监听

  • 介绍:JavaScript 事件是发生在 HTML 元素上的交互行为,如鼠标点击、键盘按键等。
  • 事件监听:JavaScript可以在事件被侦测到时执行代码
  • 事件绑定
    • 方式一:通过HTML标签中的事件属性进行绑定
    1
    2
    3
    4
    5
    6
    7
    <input type="button" value="按钮" onclick="on()">

    <script>
    function on() {
    alert("按钮被点击了");
    }
    </script>
    • 方式二:通过DOM元素属性绑定
    1
    2
    3
    4
    5
    6
    7
    <input type="button" value="按钮" id="btn">

    <script>
    document.getElementById('btn').onclick = function() {
    alert("按钮被点击了");
    }
    </script>

1.4 Vue

  • Vue是一套前端框架,,免除原生JavaScript中的DOM操作,简化书写。
  • 基于MVVM(Model-View-ViewModel)思想,实现数据的双向绑定,将编程的关注点放在数据上。
  • 官网:https://cn.vuejs.org/

    框架:是一个半成品软件,是一套可重用的、通用的、软件基础代码模型。基于框架进行开发,更加快捷,高效。

1.4.1 快速入门

  • 新建HTML文件,引入Vue.js文件:
    1
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  • 在JS代码区域,创建Vue核心对象,定义数据类型
    1
    2
    3
    4
    5
    6
    7
    8
    <script>
    new Vue({
    el: '#app',
    data: {
    message: 'Hello Vue!'
    }
    })
    </script>
  • 编写视图
    1
    2
    3
    4
    <div id="app">
    <input type="text" v-model="message">
    <p>{{ message }}</p>
    </div>

    插值表达式:

    • 形式{{ 表达式 }}
    • 作用:将数据绑定到视图中
    • 内容:变量、三元运算符、函数调用 或 算术运算

1.4.2 常用指令

  • 指令:HTML标签上带有v-前缀的特殊属性,不同指令具有不同含义。
  • 常用指令
指令用途语法
v-bind动态地绑定一个或多个特性(attributes)到一个元素上,如设置href、css样式等v-bind:attribute="expression" (简写::
v-model创建一个双向数据绑定,通常用于表单元素v-model="dataProperty"
v-if条件渲染,根据表达式的真假条件有选择地渲染元素v-if="condition"
v-else-if条件渲染,用于在 v-if 之外添加条件v-else-if="condition"
v-else条件渲染,当所有 v-ifv-else-if 的条件都不满足时渲染元素v-else
v-show基于条件展示元素,元素总是被渲染,只是简单地控制元素的显示和隐藏(display: nonev-show="condition"
v-for基于一个数组或对象的迭代渲染一个列表。对于数组,可以获取每个元素的值和下标;对于对象,可以获取每个键值对的键和值。v-for="item in items"
v-on监听DOM事件,并在事件发生时执行指定的JavaScript表达式或方法v-on:event="handler"(简写:@
v-cloak避免Vue实例接管模板前闪烁的内容(通常配合CSS使用)无表达式
v-pre跳过这个元素和它的所有子元素的编译过程,以加快初次渲染速度无表达式
v-once只渲染元素和组件一次,之后就不会再更新无表达式
v-html输出原始的HTML,不进行HTML转义(小心XSS攻击)v-html="htmlContent"

代码示例

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!-- v-bind 示例 -->
<a v-bind:href="url">Link</a>
<!-- 简写 -->
<a :href="url">Link</a>

<!-- v-model 示例 -->
<input v-model="message" placeholder="Enter something">

<!-- v-if / v-else-if / v-else 示例 -->
<span v-if="age <= 35">年轻人</span>
<span v-else-if="age > 35 && age <= 60">中年人</span>
<span v-else>老年人</span>

<!-- v-show 示例 -->
<p v-show="age <= 35">年轻人</p>

<!-- v-for 示例 -->
<ul>
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
<li v-for="(item,index) in items" >{{ index+1 }} : {{item}}</li>
</ul>

<!-- v-on 示例 -->
<button id="app" v-on:click="doSomething">Click me</button>
<!-- 简写 -->
<button id="app" @click="doSomething">Click me</button>

<script>
new Vue({
el: '#app',
methods: {
doSomething() {
alert('Button clicked!');
}
},
})
</script>
<!-- v-cloak 示例 -->
<div v-cloak>
{{ message }}
</div>
<style>
[v-cloak] { display: none; }
</style>

<!-- v-pre 示例 -->
<span v-pre>{{ raw }}</span>

<!-- v-once 示例 -->
<span v-once>{{ message }}</span>

<!-- v-html 示例 -->
<div v-html="rawHtml"></div>

注意事项

  • 通过v-bind或者v-model绑定的变量,必须在数据模型中声明

1.4.3 生命周期

  • 生命周期:指一个对象从创建到销毁的整个过程。
  • 八个阶段:每触发一个生命周期事件,会自动执行一个生命周期方法(钩子)。
  • mounted:挂载完成,Vue初始化成功,HTML页面渲染成功。(发送请求到服务端,加载数据)

1.5 Ajax

  • 概念AjaxAsynchronous JavaScript and XML 的缩写,表示异步的 JavaScript 和 XML。它是一种用于在不重新加载整个网页的情况下,进行数据交换并更新网页部分内容的技术。Ajax 通常利用 XMLHttpRequest 对象来发送和接收数据,但它并不仅限于 XML,现代应用中也常用 JSON 格式进行数据交换。

  • 作用

    • 数据交换:通过 Ajax 可以与服务器进行数据交互,发送请求并接收响应数据,而不需要刷新整个页面。
    • 异步交互:Ajax 允许网页在后台与服务器进行数据交换,从而在不刷新整个页面的情况下动态更新页面的部分内容。常见的应用场景包括:搜索联想、表单验证(如用户名是否可用)、自动加载更多内容等。

进一步说明

  • 虽然 Ajax 中的 XML 表示可使用 XML 进行数据交换,但现代开发中更多使用 JSON 作为数据交换格式,因为 JSON 更轻量且更易于 JavaScript 解析。
  • Ajax 的核心是 XMLHttpRequest 对象,但在现代开发中,fetch API 也经常用来实现类似的异步数据交互功能。
  • 异步VS同步

  • Ajax请求流程

  • XMLHttpRequest对象属性

1.6 Axios

  • 概念:Axios对原生的Ajax进行了封装,简化书写,快速开发。

  • 官网https://www.axios-http.cn/

  • 请求方式别名

    • axios.get(url [,config])
    • axios.delete(url [,config])
    • axios.post(url [,data[,config]])
    • axios.put(url [,data[,config]])
  • 入门示例

    • 1.引入Axios的js文件
    1
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    • 2.使用Axios发送请求,并获取响应结果
    1
    2
    3
    4
    5
    6
    7
    axios.get("https://api.example.com/data").then((result) => {
    console.log(result.data);
    })// 处理响应结果

    axios.post("https://api.example.com/data","id=1").then((result) => {
    console.log(result.data);
    })// 处理响应结果

1.7 Vue项目


1.8 Element组件

  • 概念:是饿了么团队开发的,一套为开发者、设计师和产品经理准备的基于Vue2.0的桌面端组件库。
  • 组件:组成网页的部件,例如 超链接、按钮、图片、表格、表单、分页条等。
  • 官网https://element.eleme.cn/

快速入门

  • 安装ElementUI组件库(在当前工程的目录下),在命令行执行指令:
    1
    npm install element-ui@2.15.6 
  • 引入ElementUI组件库
    1
    2
    3
    4
    5
    // main.js文件中引入
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';

    Vue.use(ElementUI);
  • 访问官网,复制组件代码并调整。

2.Maven

2.1 介绍

  • 概念:Apache Maven 是一个项目管理和构建工具,用于管理项目的构建、报告和文档。它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建。
  • 作用
    • 方便的依赖管理
    • 统一的项目结构
    • 标准的项目构建流程
  • 官网https://maven.apache.org/

2.2 Maven坐标

  • 概念
    • Maven中的坐标是资源的唯一标识,通过该坐标可以唯一定位资源位置
    • 使用坐标来定义项目或者引入项目中需要的依赖。
  • 主要组成
    • groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:ink.lusy)
    • artifactId:定义当前Maven项目名称(通常是模块名,例如:order-service、goods-service)
    • version:定义当前Maven项目版本号(例如:1.0.0)
1
2
3
4
5
6
7
8
9
10
11
<groupId>ink.lusy</groupId>
<artifactId>maven-project01</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>

2.3 依赖配置

  • 依赖:指当前项目运行所需要的jar包,一个项目中可以引入多个依赖。
  • 配置
    • 在pom.xml文件中编写<dependencies>标签
    • <dependencies>标签中使用<dependency>引入坐标
    • 定义坐标的groupIdartifactIdversion
    • 点击刷新按钮,引入最新加入的坐标

注意事项

  • 如果引入的依赖,在本地仓库不存在,将会连接远程仓库/中央仓库,然后下载依赖。
  • 如果不知道依赖的坐标信息,可以到https://mvnrepository.com/中搜索。

2.4 依赖传递

  • 依赖具有传递性
    • 直接依赖:在当前项目中通过依赖配置建立的依赖关系
    • 间接依赖:被依赖的资源如果依赖其它资源,当前项目间接依赖了被依赖的资源
  • 依赖冲突:当依赖传递时,如果存在多个依赖,且依赖的版本号不同,则存在依赖冲突。
    当直接引用或者间接引用出现了相同的jar包! 这时呢,一个项目就会出现相同的重复jar包,这就算作冲突!依赖冲突避免出现重复依赖,并且终止依赖传递!

    maven自动解决依赖冲突问题能力,会按照自己的原则,进行重复依赖选择。同时也提供了手动解决的冲突的方式,不过不推荐!
    解决依赖冲突(如何选择重复依赖)方式:
    • 短路优先原则(第一原则)
      A—>B—>C—>D—>E—>X(version 0.0.1)
      A—>F—>X(version 0.0.2)
      则A依赖于X(version 0.0.2)。
    • 依赖路径长度相同情况下,则“先声明优先”(第二原则)
      A—>E—>X(version 0.0.1)
      A—>F—>X(version 0.0.2)
      在<depencies>\……中,先声明的,路径相同,会优先选择!

小测试

1
2
3
4
5
6
7
8
9
前提:
A 1.1 -> B 1.1 -> C 1.1
F 2.2 -> B 2.2

pom声明:
F 2.2
A 1.1
B 2.2
C不会被引入
  • 排除依赖:当依赖传递时,如果存在依赖冲突,可以通过<exclusions>标签,排除依赖。
  • 依赖范围

2.5 生命周期

Maven的生命周期就是为了对所有的maven项目构建过程进行抽象和统一。
Maven中有3套相互独立的生命周期:

  • clean:清理项目
  • default:核心工作,包含编译、测试、打包、部署等操作
  • site:生成报告、发布站点等。


    执行指定生命周期的两种方式

2.6 项目结构

Maven 是一个强大的构建工具,它提供一种标准化的项目结构,可以帮助开发者更容易地管理项目的依赖、构建、测试和发布等任务。以下是 Maven Web 程序的文件结构及每个文件的作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|-- pom.xml                               # Maven 项目管理文件 
|-- src
|-- main # 项目主要代码
| |-- java # Java 源代码目录
| | `-- com/example/myapp # 开发者代码主目录
| | |-- controller # 存放 Controller 层代码的目录
| | |-- service # 存放 Service 层代码的目录
| | |-- dao # 存放 DAO 层代码的目录
| | `-- model # 存放数据模型的目录
| |-- resources # 资源目录,存放配置文件、静态资源等
| | |-- log4j.properties # 日志配置文件
| | |-- spring-mybatis.xml # Spring Mybatis 配置文件
| | `-- static # 存放静态资源的目录
| | |-- css # 存放 CSS 文件的目录
| | |-- js # 存放 JavaScript 文件的目录
| | `-- images # 存放图片资源的目录
| `-- webapp # 存放 WEB 相关配置和资源
| |-- WEB-INF # 存放 WEB 应用配置文件
| | |-- web.xml # Web 应用的部署描述文件
| | `-- classes # 存放编译后的 class 文件
| `-- index.html # Web 应用入口页面
`-- test # 项目测试代码
|-- java # 单元测试目录
`-- resources # 测试资源目录
  • pom.xml:Maven 项目管理文件,用于描述项目的依赖和构建配置等信息。
  • src/main/java:存放项目的 Java 源代码。
  • src/main/resources:存放项目的资源文件,如配置文件、静态资源等。
  • src/main/webapp/WEB-INF:存放 Web 应用的配置文件。
  • src/main/webapp/index.html:Web 应用的入口页面。
  • src/test/java:存放项目的测试代码。
  • src/test/resources:存放测试相关的资源文件,如测试配置文件等。

3.HTTP

3.1 概念

HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于传输超文本信息的协议。规定了浏览器和服务器之间数据传输的规则。

3.2特点

  • 基于TCP协议:面向连接,安全。
  • 基于请求-响应模型:一次请求对应一次响应。
  • 无状态:对于事务处理没有记忆能力。每次请求-响应都独立的,没有保存信息。
    • 缺点:多次请求间不能共享数据。
    • 优点:速度快

3.2 请求数据格式


3.3响应数据格式


常见的响应状态码

状态码英文描述解释
200OK客户端请求成功。即处理成功,这是我们最想看到的状态码
302Found指示所请求的资源已移动到由Location响应头给定的 URL,浏览器会自动重新访问到这个页面
304Not Modified告诉客户端,你请求的资源至上次取得后,服务器并未改。你直接用你本地缓存吧。隐式重定向
400Bad Request客户端请求有语法错误,不能被服务器所理解
403Forbidden服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源
404Not Found请求资源不存在。一般是URL输入有误,或者网站资源被删除了
405Method Not Allowed请求方法有误,比如本应用GET请求方式的资源,用了POST
428Precondition Required服务器要求有条件的请求,告诉客户端需要访问该资源,必须携带特定的请求头
429Too Many Requests用户在给定时间内发送了太多请求(“限速”),配合Retry-After(多长时间后可以请求)响应头一起使用
431Request Header Fields Too Large请求头太大。服务器不愿意处理请求,因为它的头字段太大。请求可以在减少请求头域的大小后重新提交。
500Internal Server Error服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧
503Service Unavailable服务器尚未准备好处理请求。服务器刚启动,还未初始化好

3.4 接收请求头数据

可以使用@RequestHeader注解将请求标头绑定到控制器中的方法参数。

请考虑以下带有标头的请求:

1
2
3
4
5
6
Host                    localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300

下面的示例获取 Accept-EncodingKeep-Alive 标头的值:

1
2
3
4
5
6
7
8
@GetMapping("/demo")
public void handle(
// encoding: gzip,deflate
// keepAlive: 300
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}

4.Tomcat

4.1 概念

  • Tomcat是Apache软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP和少量JavaEE规范。
  • Tomcat也称为Web容器、Servlet容器。Servlet程序需要tomcat才能运行。
  • https://tomcat.apache.org/

JavaEE:Java企业级应用环境,指Java企业级开发的技术规范总和。包含13项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF

4.2 基本使用

  • 配置Tomcat端口号 (conf/server.xml)
1
2
3
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

HTTP协议默认端口号为80,如果将Tomcat端口号改为80,则将来访问Tomcat时,将不用输入端口号。

  • Tomcat部署项目:
    将项目放置到webapps目录下,即部署完成

基于SpringBoot开发的web应用程序,内置了Tomcat服务器,当启动类运行时,会自动启动内嵌的Tomcat服务器,无需手动配置。

5.请求响应

请求响应:

  • 请求(HttpServletRequest):获取请求数据
  • 响应(HttpServletResponse):设置响应数据
    BS架构:Browser-Server架构,浏览器向服务器发送请求,服务器处理请求,返回响应,浏览器接收响应,解析响应数据。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。(维护方便 ,体验一般)
    CS架构:Client-Server架构,客户端向服务器发送请求,服务器处理请求,返回响应,客户端接收响应,解析响应数据。客户端需要浏览器和应用程序,应用程序的逻辑和数据都存储在客户端。(维护难,体验好)

5.1 请求

5.1.1 简单参数

  • 原始方式
    在原始的web程序中,获取请求参数,需要通过HttpServletRequest对象手动获取。

    通过HttpServletRequest对象获取请求参数,需要通过getParameter()方法获取,且获取到的请求参数都是字符串类型,需要手动转换为其他类型。

  • SpringBoot方式
    • 参数名与形参变量名相同,定义形参即可接收参数,且会自动进行类型转换。【可以不传递,不会报错】
    • 如果方法形参名称与请求参数名称不匹配,可以使用@RequestParam注解,指定请求参数的名称。【默认情况下必须传递,否则会报错】

      注意事项
      @RequestParam中的required属性默认值为true,代表该请求参数必须传递,如果不传递将会报错。如果该参数是可选的,可以将required属性设置为false。
      @RequestParam中有三个属性:namerequireddefaultValue,分别代表请求参数的名称、是否必须传递、默认值。

5.1.2 实体参数

  • 简单实体参数
    请求参数名与形参对象属性名相同,定义POJO即可
  • 复杂实体参数
    请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数。

注意事项
可以在实体类中直接给定默认值,如果请求参数没有传递,则使用默认值。

5.1.3 数组集合参数

  • 数组参数
    请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收数组参数。
  • 集合参数
    请求参数名与形参集合名称相同且请求参数为多个,@RequestParam绑定参数关系

数组:请求参数名与形参中数组变量名相同,可以直接使用数组封装
集合:请求参数名与形参中集合变量名相同,可以使用@RequestParam绑定参数关系

5.1.4 日期参数

  • 日期参数
    使用@DateTimeFormat注解,指定日期格式。

5.1.5 JSON参数

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

5.1.6 路径参数

  • 路径参数
    通过请求URL直接传递参数,使用{……}来标识该路径参数,需要使用@PathVariable注解获取路径参数。

5.1.7 小结

5.2 响应

5.2.1 响应数据

@ResponseBody

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

如果不添加@ResponseBody注解,Spring会认为返回值是一个视图名称。

5.2.2 统一响应结果

6.分层解耦

6.1 三层架构

  • controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
  • service:业务逻辑层,处理具体的业务逻辑,调用DAO层进行数据访问
  • dao:数据访问层(持久层),负责数据访问操作,包括数据的增删改查

6.2 分层解耦

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

6.3 IOC & DI 入门

6.3.1 Bean的声明

要把某个对象交给IOC容器管理,需要先在对应的类上加上如下注解之一:

注解说明位置
@Component声明Bean的基础注解不属于以下三类时,使用此注解
@Controller@Component的衍生注解标注在控制器类上
@Service@Component的衍生注解标注在业务类上
@Repository@Component的衍生注解标注在数据访问类上 (由于与mybatis整合,用的少)

注意事项

  • 声明Bean的时候,可以通过value属性指定Bean的名称,如果没有指定,默认为类名首字母小写。
  • 使用以上四个注解都可以声明Bean,但在SpringBoot集成web开发中,声明控制器Bean只能使用@Controller注解。

6.3.2 Bean组件扫描

  • 前面声明Bean的四大注解,要想生效,还需要被组件扫描@ComponentScan扫描。
  • @ComponentScan注解虽然没有显示配置,但是实际上已经包含在了启动类声明注解SpringBootApplication中。
  • 默认扫描的范围是启动类所在包及其子包。

6.3.3 Bean的依赖注入

  1. 依赖注入的注解
    • @Autowired:默认按照类型自动装配。
    • 如果同类型的Bean存在多个:
      • @Primary:指定Bean的优先级。
      • @Autowired + @Qualifier("Bean的名称")
      • @Resource(name = "Bean的名称")
  2. @Resource@Autowired区别:
    • @Autowired是Spring框架提供的注解,而@Resource是JDK提供的注解
    • @Autowired默认是按照类型注入,而@Resource默认是按照名称注入

7.Lombok

  • Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashCode、toString等方法,并可以自动生成日志变量,简化Java开发、提高效率。

注意事项
Lombok会在编译时,自动生成对应的Java代码。我们使用Lombok时,还需要安装一个lombok插件(idea自带)。

8.MyBatis

  • MyBatis是一款优秀的持久层(dao)框架,用于简化JDBC的开发。
  • MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由Apache迁移到了Google Code,并且改名为MyBatis。2013年11月迁移到Github。
  • 官网:https://mybatis.org

8.1 快速入门

8.2 JDBC介绍

  • JDBC
    Java DataBase Connectivity,就是使用Java语言操作关系型数据库的一套API。
  • 本质
    • sun公司官方定义的一套操作所有关系型数据库的规范,即接口。
    • 各个数据库厂商去实现这套接口,提供数据库驱动jar包
    • 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

8.3 数据库连接池

  • 概念
    • 数据库连接池是一个容器,负责分配,管理数据库连接(Connection)
    • 它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
    • 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
  • 优势
    • 资源重用
    • 提升系统响应速度
    • 避免数据库连接遗漏
  • 标准接口DataSource
    • 官方(Sun)提供的数据库连接池接口,由第三方组织实现此类接口。
    • 功能:获取连接 Connection getConnection() throws SQLException;
  • 常见产品
    C3P0、DBCP、Druid、Hikari(springboot默认)
    • Durid(德鲁伊):是阿里巴巴开源的一个数据库连接池实现,它具有监控功能、SQL防火墙、SQL优化功能等。
  • 切换Druid数据库连接池

8.4 基础操作

8.4.1 参数占位符

  • #{……}
    • 在执行SQL时,会将#{……}替换为 ? ,生成预编译SQL,会自动设置参数值。
    • 使用时机:参数传递,都使用#{……}
    • 不能直接在引号内使用,因为如果将 #{……} 放在引号内,MyBatis 会将其处理为一个普通的字符串值,而不会进行参数替换。
  • ${……}
    • 拼接SQL,直接将参数拼接在SQL语句中,存在SQL注入问题。
    • 使用时机:如果对表名、列表进行动态设置时使用。

      总结

      • 动态值 使用 #{key},动态的列名、容器名、关键字等使用 ${……}。
      • ?只能替代值的位置,不能替代标签、列名、表名、关键字等。emp_id = ? ,不能写 ?= ?
      1
      2
      3
      >//注解方式传入参数!!
      >@Select("select * from user where ${column} = #{value}")
      >User findByColumn(@Param("column") String column, @Param("value") String value);

8.4.2 预编译SQL

优势

  • 性能更高
  • 更安全(防止SQL注入)

8.4.3 日志输出

可以在application.yml中,打开mybatis的日志,并指定输出到控制台。

1
2
3
4
5
6
mybatis:
configuration:
# 配置mybatis的日志, 指定输出到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启mybatis的驼峰命名自动映射开关 a_column ------> aCloumn
map-underscore-to-camel-case: true

8.4.4 删除

  • SQL语句:
    1
    delete from emp where id = 17;
  • 接口方法:
    1
    2
    @Delete("delete from emp where id = #{id}")
    public void delete(Integer id);

注意事项
如果mapper接口方法形参只有一个普通类型的参数,#{……}里面的属性名可以随便写,如:#{id}、#{value},但最好和参数名一致。

8.4.5 添加

无需主键返回

  • SQL语句:
    1
    insert into emp(username, gender, email, dept_id) values ('宋江',1,1056985080@qq.com,1);
  • 接口方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Insert("insert into emp(username, gender, email, dept_id) values (#{username},#{gender},#{email},#{deptId})")
    public void insert(Emp emp);

    // emp对象
    public class Emp {
    private Integer id;
    private String username;
    private Short gender;
    private String email;
    private Integer deptId;
    }

主键返回

  • 描述
    在数据添加成功后,需要获取插入数据库数据的主键值。(eg:添加套餐数据后,还需要维护套餐菜品关系表数据)
  • 实现
    在接口方法前添加@Options(keyProperty = "id", useGeneratedKeys = true)注解 => 会自动将生成的主键值,赋值给emp对象的id属性。
    1
    2
    3
    @Options(keyProperty = "id", useGeneratedKeys = true)
    @Insert("insert into emp(username, gender, email, dept_id) values (#{username},#{gender},#{email},#{deptId}")
    public void insert(Emp emp);

8.4.6 更新

  • SQL语句(根据ID更新员工信息):
    1
    update emp set username = '李逵', gender = 1, email = '1056985080@qq.com', dept_id = 1 where id = 17;
  • 接口方法:
    1
    2
    @Update("update emp set username = #{username}, gender = #{gender}, email = #{email}, dept_id = #{deptId} where id = #{id}")
    public void update(Emp emp);

8.4.7 查询

根据ID查询

  • SQL语句:
    1
    select * from emp where id = 17;
  • 接口方法:
    1
    2
    @Select("select * from emp where id = #{id}")
    public Emp getById(Integer id);
    模糊查询
  • SQL语句:
    1
    select * from emp where username like '%张%' and gender = 1 order by id desc;
  • 接口方法:
    1
    2
    3
    4
    5
    6
    7
    @Select("select * from emp where username like '%${name}%' and gender = #{gender} order by id desc")
    public List<Emp> getByNameAndGender(String name, Short gender);
    // 性能低、不安全,存在SQL注入问题

    @Select("select * from emp where username like concat('%',#{name},'%') and gender = #{gender} order by id desc")
    public List<Emp> getByNameAndGender(String name, Short gender);
    // 性能高、安全,不会存在SQL注入问题

concat()函数:连接字符串,eg:concat(‘%’,#{name},’%’),解决#{……}不能直接在引号中使用的问题。

8.4.8 参数名说明

8.4.9 数据封装

  • 实体类属性名 和 数据库表查询返回的字段名一致,mybatis会自动封装。
  • 如果实体类属性名 和 数据库表查询返回的字段名不一致,不能自动封装。

解决方式:

  • 起别名:在SQL语句中,对不一样的列名器别名,别名和实体类中的属性名一致。(as可以省略)

    1
    2
    @Select("select id, username, gender, email, dept_id as deptId from emp where id = #{id}")
    public Emp getById(Integer id);
  • 手动结果映射

    • 通过@Results@Result注解,手动映射。
    1
    2
    3
    4
    5
    6
    7
    @Select("select * from emp where id = #{id}")
    @Results({
    @Result(column = "dept_id", property = "deptId"),
    @Result(column = "create_time", property = "createTime"),
    @Result(column = "update_time", property = "updateTime")
    })
    public Emp getById(Integer id);
    • 通过<resultMap>标签定义对应关系,再在后面的SQL语句中引用这个对应关系
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- 专门声明一个resultMap设定column到property之间的对应关系 -->
    <resultMap id="selectEmployeeByRMResultMap" type="com.atguigu.mybatis.entity.Employee">

    <!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
    <!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
    <id column="emp_id" property="empId"/>

    <!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
    <result column="emp_name" property="empName"/>
    <result column="emp_salary" property="empSalary"/>

    </resultMap>

    <!-- Employee selectEmployeeByRM(Integer empId); -->
    <select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">
    select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}
    </select>
  • 开启驼峰命名:如果字段名与属性名符合驼峰命名法则,mybatis会自动通过驼峰命名规则映射。
    主要用于单层级的字段映射。它不能自动处理更复杂的场景,例如多层级的嵌套对象或深层次的属性名转换。

    1
    2
    3
    4
    5
    6
    7
    # 在application.yml中,开启驼峰命名自动映射开关。即从数据库字段名a_column映射到实体类属性名aCloumn。
    mybatis:
    configuration:
    # 配置mybatis的日志, 指定输出到控制台
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启mybatis的驼峰命名自动映射开关 a_column ------> aCloumn
    map-underscore-to-camel-case: true
  • 开启自动映射autoMappingBehavior 是 MyBatis 中的一个全局配置项,用于控制 MyBatis 如何自动映射查询结果中的列到 Java 实体类的属性上。根据配置的不同,autoMappingBehavior 可以影响映射的自动化程度,从而减少或增加手动配置的需求。它有以下三个配置选项:

    1
    2
    3
    4
    5
    6
    mybatis:
    configuration:
    # 配置MyBatis的日志, 指定输出到控制台
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 设置MyBatis的自动映射行为
    auto-mapping-behavior: NONE # 可选值:NONE, PARTIAL, FULL
    • NONE:MyBatis 不会自动映射任何列到实体类属性。所有的映射关系必须手动配置,通常通过 @Result 注解或 <resultMap> 标签来指定。这种模式适合需要对映射关系进行严格控制的场景,以避免意外的映射错误。
    • PARTIAL(默认值):MyBatis 会自动映射查询结果中的列到实体类属性,但只限于那些没有在 ResultMap 中明确定义过的列。如果在 ResultMap 中已经定义了某列的映射关系,则 MyBatis 不会再次自动映射该列。PARTIAL 是大多数情况下的推荐设置,能够在保持映射控制的同时,自动处理简单的映射任务。
    • FULL:MyBatis 会尝试自动映射所有查询结果中的列到实体类属性,无论这些列是否在 ResultMap 中定义。FULL 设置适合简单对象的映射,能够减少手动配置的工作量。但在复杂映射场景中,可能导致意外的映射错误,因此需要谨慎使用。
    • 应用场景与注意事项autoMappingBehavior 的设置直接影响 MyBatis 的自动映射能力。设置为 FULL 时,MyBatis 可以帮助自动处理简单的嵌套对象映射,但对于复杂的嵌套结构,手动映射依然是必不可少的。即使设置为 FULLPARTIAL,在复杂或关键场景下,建议依然手动配置映射,以确保映射的准确性和可控性。此外,在处理大数据量或复杂对象映射时,FULL 的自动映射可能增加系统负担,因此在性能敏感的应用中应谨慎选择。

8.4.10 多表映射

  • 一对一映射
    在一对一关联中,一个实体对象包含另一个实体对象,通常通过外键关系实现。使用 @One 注解、**@Result注解、<association>**标签均可以帮助 MyBatis 自动处理这种关系的查询和映射。

    示例
    假设我们有两个表:useraddress,它们之间存在一对一关系。

    • user 表:

      • id(用户ID)
      • name(用户名)
      • address_id(外键,指向 address 表的 ID)
    • address 表:

      • id(地址ID)
      • street(街道)
      • city(城市)

    实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class User {
    private Integer id;
    private String name;
    private Address address; // 一对一关系
    // Getters and setters
    }

    public class Address {
    private Integer id;
    private String street;
    private String city;
    // Getters and setters
    }
  1. 注解方式 (@One 注解)
    UserMapper 中,使用 @One 注解来配置一对一关联。在这种方式中,@One 注解用于指定如何从主查询的结果集中加载一个单一的关联对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public interface UserMapper {

    @Select("SELECT u.id, u.name, a.id AS address_id, a.street, a.city " +
    "FROM user u " +
    "LEFT JOIN address a ON u.address_id = a.id " +
    "WHERE u.id = #{id}")
    @Results({
    @Result(column = "id", property = "id"),
    @Result(column = "name", property = "name"),
    @Result(column = "address_id", property = "address",
    one = @One(select = "com.example.mapper.AddressMapper.findById"))
    })
    User findById(Integer id);
    }
    • @Result(column = "address_id", property = "address", one = @One(select = "com.example.mapper.AddressMapper.findById"))
      • column:数据库字段名。
      • property:实体类中的属性名。
      • one:指定一个方法来查询 Address 对象,select 属性指向了 AddressMapper 中的方法 findById
    1
    2
    3
    4
    5
    public interface AddressMapper {

    @Select("SELECT id, street, city FROM address WHERE id = #{id}")
    Address findById(Integer id);
    }
  2. 注解方式 (@Result 注解)
    UserMapper 中,使用 @Result 注解来直接映射数据库字段到实体类的属性。如果关联对象的属性在主查询中被直接包含,则可以通过 @Result 注解来映射。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public interface UserMapper {

    @Select("SELECT u.id, u.name, a.id AS address_id, a.street, a.city " +
    "FROM user u " +
    "LEFT JOIN address a ON u.address_id = a.id " +
    "WHERE u.id = #{id}")
    @Results({
    @Result(column = "id", property = "id"),
    @Result(column = "name", property = "name"),
    @Result(column = "address_id", property = "address.id"), // 自动映射到 Address 对象
    @Result(column = "street", property = "address.street"),
    @Result(column = "city", property = "address.city")
    })
    User findById(Integer id);
    }
    • @Result(column = "address_id", property = "address.id"):自动将 address_id 映射到 Address 对象的 id 属性。
    • @Result(column = "street", property = "address.street"):将 street 映射到 Address 对象的 street 属性。
  3. XML 配置方式 (<association> 标签)
    UserMapper.xml 中,通过 <association> 标签指定如何查询和映射关联对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <select id="findById" resultType="com.example.pojo.User">
    SELECT u.id, u.name, a.id AS address_id, a.street, a.city
    FROM user u
    LEFT JOIN address a ON u.address_id = a.id
    WHERE u.id = #{id}
    <association property="address" javaType="com.example.pojo.Address">
    <id column="address_id" property="id"/>
    <result column="street" property="street"/>
    <result column="city" property="city"/>
    </association>
    </select>
    • <association> 标签:用于指定如何加载和映射关联对象。
      • property:在主对象中用来表示关联对象的属性名。
      • javaType:被关联的实体类的全类名。
      • <id>:映射主键字段。
      • <result>:映射其他字段。

总结

  • @One 注解:用于在注解方式中配置一对一关系,通过指定 select 属性来查询关联对象。
  • @Result 注解:直接映射查询结果到实体对象的属性,包括嵌套对象的属性。
  • <association> 标签:在 XML 配置中定义一对一关系,通过指定 propertyjavaType 来配置如何映射关联对象。
  • 一对多映射
    假设我们有两个表:userorders,其中一个用户 (user) 可以有多个订单 (orders)。我们将展示如何在 User 对象中使用 @Many 注解以及在 XML 配置中使用 <collection> 标签来处理一对多关系。

    实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class User {
    private Integer id;
    private String name;
    private List<Order> orders; // 一对多关系
    // Getters and setters
    }

    public class Order {
    private Integer id;
    private String product;
    private Integer userId; // 外键
    // Getters and setters
    }
  1. 使用 @Many 注解
    在这种方式中,我们在 UserMapper 接口中使用 @Many 注解来定义一对多关系。
    Mapper 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public interface UserMapper {
    @Select("SELECT u.id, u.name, o.id AS order_id, o.product " +
    "FROM user u " +
    "LEFT JOIN orders o ON u.id = o.user_id " +
    "WHERE u.id = #{id}")
    @Results({
    @Result(column = "id", property = "id"),
    @Result(column = "name", property = "name"),
    @Result(column = "id", property = "orders",
    many = @Many(select = "com.example.mapper.OrderMapper.findByUserId"))
    })
    User findById(Integer id);
    }

    OrderMapper 接口

    1
    2
    3
    4
    public interface OrderMapper {
    @Select("SELECT id, product, user_id FROM orders WHERE user_id = #{userId}")
    List<Order> findByUserId(Integer userId);
    }
    • @Many 注解:在 UserMapper 中,@Result 注解的 many 属性指定了如何加载 orders 属性。select 属性指向 OrderMapper 中的 findByUserId 方法,该方法返回
      userId 相关的所有订单。
  2. 使用 XML 配置中的 <collection> 标签
    在这种方式中,我们使用 XML 配置文件来定义一对多关系。

    UserMapper.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <mapper namespace="com.example.mapper.UserMapper">
    <select id="findById" resultType="com.example.pojo.User">
    SELECT u.id, u.name, o.id AS order_id, o.product
    FROM user u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.id = #{id}
    <!-- 使用 `<collection>` 标签定义一对多关系 -->
    <collection property="orders" ofType="com.example.pojo.Order">
    <id column="order_id" property="id"/>
    <result column="product" property="product"/>
    </collection>
    </select>
    </mapper>
    • <collection> 标签:用于定义 User 对象中的 orders 集合属性。property 属性指定集合属性的名称,ofType 属性指定集合中元素的类型。在查询结果中,MyBatis 会自动 将结果集中的多个订单映射到 User 对象的 orders 属性中。

总结

  • @Many 注解:在 UserMapper 中通过 @Many 注解指定如何查询与 User 相关的多个 Order 对象。这种方式简洁明了,并且适用于注解方式的配置。
  • XML 配置中的 <collection> 标签:在 UserMapper.xml 中使用 <collection> 标签来定义一对多关系。通过这种方式,你可以在 XML 文件中清晰地配置如何将多个结果映射到 User 对象的集合属性中。这种方式适用于 XML 配置文件,并允许你在查询中定义更复杂的映射逻辑。
  • 三表查询
    在 MyBatis 中处理三表关联时,通常涉及到多对多或更复杂的多级关联关系。我们可以通过多对一和一对多的组合来实现三表关联。下面我将展示一个处理三表关联的完整示例,包括使用注解和 XML 配置两种方式。

    示例
    假设我们有以下三张表:

  • user:存储用户信息。

    • id(用户ID)
    • name(用户名)
  • orders:存储订单信息,关联到 user 表。

    • id(订单ID)
    • product(产品名)
    • user_id(外键,指向 user 表的 ID)
  • order_items:存储订单项信息,关联到 orders 表。

    • id(订单项ID)
    • order_id(外键,指向 orders 表的 ID)
    • item_name(订单项名)

    实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class User {
    private Integer id;
    private String name;
    private List<Order> orders; // 一对多关系:用户 -> 订单
    // Getters and setters
    }

    public class Order {
    private Integer id;
    private String product;
    private List<OrderItem> orderItems; // 一对多关系:订单 -> 订单项
    // Getters and setters
    }

    public class OrderItem {
    private Integer id;
    private String itemName;
    // Getters and setters
    }

    目标
    我们要查询 User 对象,并且包含用户的订单 (Order),以及每个订单下的订单项 (OrderItem)。

  1. 使用注解的方式
    UserMapper 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public interface UserMapper {

    @Select("SELECT u.id, u.name, o.id AS order_id, o.product " +
    "FROM user u " +
    "LEFT JOIN orders o ON u.id = o.user_id " +
    "WHERE u.id = #{id}")
    @Results({
    @Result(column = "id", property = "id"),
    @Result(column = "name", property = "name"),
    @Result(column = "order_id", property = "orders",
    many = @Many(select = "com.example.mapper.OrderMapper.findByUserId"))
    })
    User findById(Integer id);
    }

    OrderMapper 接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public interface OrderMapper {

    @Select("SELECT id, product FROM orders WHERE user_id = #{userId}")
    @Results({
    @Result(column = "id", property = "id"),
    @Result(column = "product", property = "product"),
    @Result(column = "id", property = "orderItems",
    many = @Many(select = "com.example.mapper.OrderItemMapper.findByOrderId"))
    })
    List<Order> findByUserId(Integer userId);
    }

    OrderItemMapper 接口

    1
    2
    3
    4
    5
    public interface OrderItemMapper {

    @Select("SELECT id, item_name FROM order_items WHERE order_id = #{orderId}")
    List<OrderItem> findByOrderId(Integer orderId);
    }

    在这个配置中:

    • UserMapper:负责查询 User 信息及其关联的 Order 列表。@Many 注解指向 OrderMapper 中的 findByUserId 方法,该方法进一步加载每个订单的详细信息。
    • OrderMapper:负责查询 Order 信息及其关联的 OrderItem 列表。@Many 注解指向 OrderItemMapper 中的 findByOrderId 方法。
    • OrderItemMapper:负责查询 OrderItem 列表。
  2. 使用 XML 配置的方式
    UserMapper.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <mapper namespace="com.example.mapper.UserMapper">
    <select id="findById" resultType="com.example.pojo.User">
    SELECT u.id, u.name
    FROM user u
    WHERE u.id = #{id}
    <collection property="orders" ofType="com.example.pojo.Order">
    SELECT o.id AS order_id, o.product
    FROM orders o
    WHERE o.user_id = #{id}
    <collection property="orderItems" ofType="com.example.pojo.OrderItem">
    SELECT oi.id AS item_id, oi.item_name
    FROM order_items oi
    WHERE oi.order_id = #{order_id}
    </collection>
    </collection>
    </select>
    </mapper>
    • <collection> 标签:用于定义一对多关系,首先映射 UserOrder 之间的关系,然后在 OrderOrderItem 之间再次使用 <collection> 标签。
    • 多级映射:在 UserMapper.xml 中,我们嵌套使用 <collection> 标签来处理多级关联。

总结

  • 注解方式@Many 注解在三表关联中非常有效,可以简化代码,特别是在分布式项目中,但在复杂查询中可能不如 XML 配置灵活。
  • XML 配置方式<collection> 标签在处理复杂关联时更直观,尤其是当查询需要多级嵌套或复杂的结果映射时。

8.5 XML映射文件

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

      使用Mybatis的注解,主要是来完成一些简单的增删改查功能,如果需要实现复杂的SQL功能,建议使用XML来配置映射语句。

8.5.1 if标签

  • <if>
    • 用于判断条件是否成立,如果条件为true,则拼接SQL。
    • 形式:<if test=”条件”>……</if>。
      1
      <if test="gender != null">……</if>

8.5.2 where标签

  • <where>
    • where元素只会在子元素有内容的情况下才能插入where子句,而且会自动去除子句开头的and或or
    • 形式:<where>……</where>。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <select id="findUsers" resultType="User">
      select * from user
      <where>
      <if test="name != null and name != ''">
      name = #{name}
      </if>
      <if test="age != null">
      and age = #{age}
      </if>
      <if test="gender != null and gender != ''">
      and gender = #{gender}
      </if>
      </where>
      </select>

8.5.3 set标签

  • <set>
    • 动态地在行首插入set关键字,并会删除掉额外的逗号。(用在update语句中)
    • 形式:<set>……</set>。

8.5.4 foreach标签

  • SQL语句:
    1
    delete from emp where id in (1,2,3,4,5);
  • 接口方法:
    1
    2
    // 批量删除
    public void deleteByIds(List<Integer> ids);
  • XML映射文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <delete id="deleteByIds">
    <!-- 从 emp 表中删除满足条件的记录 -->
    delete from emp where id in
    <!--
    使用 <foreach> 标签遍历传入的集合 ids
    collection="ids" 表示传入的集合参数名称是 ids
    item="id" 表示遍历集合中的每个元素,并将当前元素赋值给 id
    open="(" 表示在遍历元素之前,SQL 语句中添加一个左括号 (
    separator="," 表示在每个元素之间,用逗号 , 作为分隔符
    close=")" 表示在遍历元素之后,SQL 语句中添加一个右括号 )
    原SQL:delete from emp where id in (1,2,3,4,5)
    -->
    <foreach collection="ids" item="id" open="(" separator="," close=")">
    <!--
    将当前遍历到的 id 以 #{id} 的形式插入到 SQL 语句中
    #{id} 是 MyBatis 的占位符,用于安全地传递参数,防止 SQL 注入
    -->
    #{id}
    </foreach>
    </delete>

  • 属性:
    • collection:集合属性名。
    • item:集合遍历出来的元素/项的名称。
    • open:遍历集合元素前的前缀。【在遍历元素之前需要往SQL语句中添加什么内容】
    • separator:遍历集合元素间的分隔符。【在遍历元素之间需要往SQL语句中添加什么内容】
    • close:遍历集合元素后的后缀。【在遍历元素之后需要往SQL语句中添加什么内容】

8.5.5 sql片段

  • <sql>:定义可重用的SQL片段。
  • <include>:通过属性refid,指定包含的sql片段的id。

8.5.6 trim标签

使用trim标签控制条件部分两端是否包含某些字符

  • prefix属性:指定要动态添加的前缀
  • suffix属性:指定要动态添加的后缀
  • prefixOverrides属性:指定要动态去掉的前缀,使用”|”分隔有可能的多个值
  • suffixOverrides属性:指定要动态去掉的后缀,使用”|”分隔有可能的多个值
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
<!-- List<Employee> selectEmployeeByConditionByTrim(Employee employee) -->
<select id="selectEmployeeByConditionByTrim" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id,emp_name,emp_age,emp_salary,emp_gender
from t_emp

<!-- prefix属性指定要动态添加的前缀 -->
<!-- suffix属性指定要动态添加的后缀 -->
<!-- prefixOverrides属性指定要动态去掉的前缀,使用“|”分隔有可能的多个值 -->
<!-- suffixOverrides属性指定要动态去掉的后缀,使用“|”分隔有可能的多个值 -->
<!-- 当前例子用where标签实现更简洁,但是trim标签更灵活,可以用在任何有需要的地方 -->
<trim prefix="where" suffixOverrides="and|or">
<if test="empName != null">
emp_name=#{empName} and
</if>
<if test="empSalary &gt; 3000">
emp_salary>#{empSalary} and
</if>
<if test="empAge &lt;= 20">
emp_age=#{empAge} or
</if>
<if test="empGender=='male'">
emp_gender=#{empGender}
</if>
</trim>
</select>

8.5.7 choose/when/otherwise标签

在多个分支条件中,仅执行一个。

  • 从上到下依次执行条件判断
  • 遇到的第一个满足条件的分支会被采纳
  • 被采纳分支后面的分支都将不被考虑
  • 如果所有的when分支都不满足,那么就执行otherwise分支
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- List<Employee> selectEmployeeByConditionByChoose(Employee employee) -->
<select id="selectEmployeeByConditionByChoose" resultType="com.atguigu.mybatis.entity.Employee">
select emp_id,emp_name,emp_salary from t_emp
where
<choose>
<when test="empName != null">emp_name=#{empName}</when>
<when test="empSalary &lt; 3000">emp_salary &lt; 3000</when>
<otherwise>1=1</otherwise>
</choose>

<!--
第一种情况:第一个when满足条件 where emp_name=?
第二种情况:第二个when满足条件 where emp_salary < 3000
第三种情况:两个when都不满足 where 1=1 执行了otherwise
-->
</select>

9. 项目搭建

9.1 环境搭建

9.2 开发规范

注意事项

  • REST是风格,是约定方式,约定不是硬性规定,可以打破。
  • 描述模块的功能通常是复数, 也就是加s的格式来描述,表示此类资源,而非单个资源。如:uers、emps、books……

9.3 @RequestMapping

  • 类级别的RequestMapping定义了所有该类中处理方法的共同URL前缀。
  • 方法级别的RequestMapping则在类级别的URL基础上进一步细分,指定具体的处理路径(eg:GET、POST)。可以使用更具体的GetMapping、PostMapping等注解替代。
    如果不指定,默认情况下任何请求方式都可以访问。
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
29
30
31
32
// 使用方法级别的@RequestMapping注解
@RestController
@RequestMapping("/api")
public class MyController {

@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String sayHello() {
return "Hello, World!";
}

@RequestMapping(value = "/hello", method = RequestMethod.POST)
public String postHello(@RequestBody String name) {
return "Hello, " + name;
}
}

// 使用方法级别的@GetMapping、@PostMapping注解
@RestController
@RequestMapping("/api")
public class MyController {

@GetMapping("/hello")
public String sayHello() {
return "Hello, World!";
}

@PostMapping("/hello")
public String postHello(@RequestBody String name) {
return "Hello, " + name;
}
}

注意事项

  • @RequestMapping不用必须使用 / 开头
  • @RequestMapping可以同时指定多个URL路径,用数组表示,如:@RequestMapping(value = {"/hello", "/hi"})
  • @RequestMapping可以同时指定多个请求方式,用数组表示,如:@RequestMapping(value = "/hello", method = {RequestMethod.GET, RequestMethod.POST})
  • @RequestMapping支持模糊匹配,* 表示任意一层字符串,**表示任意层字符串。
1
2
/hello/* -> /hello/world  /hello/world123 [不可以是 /hello/world/123]
/hello/** -> /hello /hello/world /hello/world/123
  • 方法前必须加上@RequestMapping注解,就算类上已经添加了@RequestMapping注解,否则该方法将不会被识别成外部请求。
1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping // 不能省略, url: /hello
public String hello() {
return "hello";
}
}

9.4 PageHelper分页插件

  • 导入依赖
    1
    2
    3
    4
    5
    	<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
    </dependency>
  • 使用方法
    1
    2
    3
    PageHelper.startPage(pageNum, pageSize);
    List<Emp> list = empMapper.list();
    Page<Emp> page = (Page<Emp>)list;

9.5 文件上传

  • 简介
    • 文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其它用户浏览或下载的过程。
    • 文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
  • 前端页面
    • 表单项 type=”file”
    • 表单提交方式 post
    • 表单的enctype属性:multipart/form-data
  • 服务端接收文件
    • MultipartFile接口
      • String getOriginalFilename():获取原始文件名
      • void transferTo(File dest):将文件保存到指定位置
      • long getSize():获取文件大小,单位:字节
      • byte[] getBytes():获取文件内容的字节数组
      • InputStream getInputStream():获取接收到的文件内容的输入流

9.5.1 本地仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class FileController {
@PostMapping("/upload")
public Result upload(MultipartFile file) throws IOException {
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 构建新的文件名
String newFilename = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
// 将文件保存在服务端 本地仓库 E:/images/ 目录下
file.transferTo(new File("E:/images/" + newFilename));
return Result.success();
}
}

在SpringBoot中,文件上传,默认单个文件允许最大大小为 1M,如果需要上传大文件,可以进行如下配置:

1
2
3
4
5
6
7
# 在application.yml中配置
servlet:
multipart:
# 配置单个文件最大上传大小
max-file-size: 10MB
# 配置单个请求最大上传大小(一次请求可以上传多个文件)
max-request-size: 100MB

9.5.2 阿里云OSS

  • 引入阿里云OSS相关依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.17.4</version>
    </dependency>
    <dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
    </dependency>
    <dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
    </dependency>
    <!-- no more than 2.3.3-->
    <dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.3</version>
    </dependency>
  • 引入阿里云OSS上传文件工具类(由官方的示例代码改造而来)
  • 上传图片接口开发
    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
    @Slf4j
    @RestController
    public class UploadController {
    /* 本地存储
    @PostMapping("/upload")
    public Result upload(MultipartFile image) throws IOException {

    // 获取原始文件名
    String originalFilename = image.getOriginalFilename();
    // 构建新的文件名
    String newFileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

    // 将文件保存到本地
    image.transferTo(new File("E:\\Edge下载(定期删除)\\day11-SpringBootWeb案例\\" + newFileName));
    return Result.success();
    }*/
    @Autowired
    private AliOSSUtils aliOSSUtils;

    @PostMapping("/upload")
    public Result upload(@RequestParam MultipartFile image) throws IOException {
    log.info("文件上传,文件名:{}",image.getOriginalFilename());
    String url = aliOSSUtils.upload(image);
    log.info("文件上传完成,文件访问的url:{}",url);
    return Result.success(url);
    }
    }

9.6 配置文件

  • yml文件基本语法
    • 大小写敏感
    • 数值前边必须有空格,作为分隔符
    • 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能使用空格(idea中会自动将Tab键转换为空格)
    • 缩进的空格数目不重要,只要相同层级的元素左对齐即可
    • # 表示注解,从这个字符开始到行尾,都是注释,不会被解析

9.7 外部配置注入

在Spring框架中,@Value@ConfigurationProperties注解用于将外部配置的值注入到Spring管理的Bean中。它们的使用场景和功能有所不同,下面分别讲解它们的作用和使用方法。

  • @Value 注解
    @Value注解用于将配置文件中的单个属性值注入到Spring Bean的字段或方法中。它通常用于注入简单的属性值,比如字符串、数值等。

    1. 基本语法
    1
    2
    @Value("${property.key}")
    private String propertyValue;

    其中${property.key}表示从配置文件(如application.propertiesapplication.yml)中读取指定键的值。

    1. 默认值
      可以在@Value注解中指定一个默认值,当配置文件中找不到相应的键时,使用默认值:
    1
    2
    @Value("${property.key:defaultValue}")
    private String propertyValue;
    1. 复杂表达式
      @Value也可以使用Spring表达式语言(SpEL)来处理复杂的值:
    1
    2
    @Value("#{T(java.lang.Math).random() * 100}")
    private double randomValue;
    1. 使用示例
      假设在application.properties文件中有以下配置:
    1
    2
    app.name=MyApp
    app.version=1.0.0

    可以在Bean中使用@Value注解注入这些值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Component
    public class AppConfig {

    @Value("${app.name}")
    private String appName;

    @Value("${app.version}")
    private String appVersion;

    // getters and setters
    }
  • @ConfigurationProperties 注解
    @ConfigurationProperties用于将一组相关的配置属性绑定到一个Java类的属性上。它可以将配置文件中的属性自动映射到类的字段中,尤其适用于需要处理一组复杂或嵌套配置的场景。

    1. 基本语法
      定义一个类来对应配置文件中的一组属性:
    1
    2
    3
    4
    5
    6
    7
    8
    @ConfigurationProperties(prefix = "app")
    public class AppProperties {

    private String name;
    private String version;

    // getters and setters
    }

    prefix = "app"表示这个类将映射application.propertiesapplication.yml中以app开头的属性。

    1. 启用@ConfigurationProperties
      通常需要使用@EnableConfigurationProperties注解或@Component来让Spring管理这个配置类:
    1
    2
    3
    4
    5
    @Configuration
    @EnableConfigurationProperties(AppProperties.class)
    public class AppConfig {
    // 其他配置
    }

    或者在配置类上直接使用@Component

    1
    2
    3
    4
    5
    @Component // 如果已经在配置类中使用了@EnableConfigurationProperties,则不需要再使用@Component
    @ConfigurationProperties(prefix = "app")
    public class AppProperties {
    // fields, getters, setters
    }

    1. 使用示例
      假设在application.properties文件中有以下配置:
    1
    2
    3
    4
    app.name=MyApp
    app.version=1.0.0
    app.details.description=This is a demo application.
    app.details.author=John Doe

    可以定义一个类来映射这些配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Component
    @ConfigurationProperties(prefix = "app")
    public class AppProperties {

    private String name;
    private String version;
    private Details details = new Details();

    public static class Details {
    private String description;
    private String author;

    // getters and setters
    }

    // getters and setters
    }

    这样,AppProperties类中的字段会自动映射到配置文件中的相应属性值。

主要区别

  • 粒度@Value用于注入单个属性值,而@ConfigurationProperties更适合注入一组相关的属性。
  • 类型安全@ConfigurationProperties支持类型安全的配置绑定,并且可以通过构建复杂对象来映射嵌套属性,而@Value只能注入简单的属性值。
  • 可维护性@ConfigurationProperties有助于将配置集中管理,并且对属性进行结构化处理,这对大型项目的配置管理更为友好。
  • @Value的局限性@Value只能注入单个属性值,不能注入集合、数组等。
    选择建议
  • 当需要注入单个或少量的配置值时,使用@Value
  • 当需要注入大量相关配置或希望将配置分组时,使用@ConfigurationProperties

在Spring框架中,@PropertySource@ConfigurationProperties都涉及配置属性的管理,但它们的作用和使用场景有所不同,不能简单地认为它们是类似的。
@PropertySource的作用

  • 用途@PropertySource主要用于加载自定义的属性文件(如.properties文件)到Spring的Environment中。通过这个注解,Spring能够识别和使用这些属性文件中的键值对。
  • 典型场景
    • 当你的配置文件不是默认的application.propertiesapplication.yml,而是自定义的文件时,需要用@PropertySource来加载这个文件。

    • 例如,如果你有一个名为custom.properties的文件,且希望Spring加载其中的配置项,那么可以使用@PropertySource注解:

      1
      2
      3
      4
      5
      @Configuration
      @PropertySource("classpath:custom.properties")
      public class AppConfig {
      // 配置类内容
      }
  • 配合@Value使用@PropertySource通常与@Value注解配合使用,从加载的属性文件中读取具体的属性值并注入到Bean的字段中。

@ConfigurationProperties的作用

  • 用途@ConfigurationProperties用于将一组相关的配置属性绑定到一个Java类的属性上。它可以将配置文件中的属性自动映射到类的字段中,尤其适用于需要处理一组复杂或嵌套配置的场景。
  • 典型场景
    • 当你有一组相关的配置项,比如与应用程序、数据库、消息队列等相关的多个配置项时,可以使用@ConfigurationProperties将这些配置项整合到一个专门的配置类中。
    • 例如,假设有以下配置:
      1
      2
      3
      4
      app.name=MyApp
      app.version=1.0.0
      app.details.description=This is a description
      app.details.author=John Doe
      可以使用@ConfigurationProperties将这些属性映射到类中:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      @Component
      @ConfigurationProperties(prefix = "app")
      public class AppProperties {
      private String name;
      private String version;
      private Details details = new Details();

      public static class Details {
      private String description;
      private String author;
      // Getters and Setters
      }

      // Getters and Setters
      }

      @PropertySource vs. @ConfigurationProperties

      • @PropertySource:负责告诉Spring从哪里加载配置文件,并与@Value结合使用,允许单独注入具体的配置值。@PropertySource只加载属性文件,不负责属性的绑定和注入。
      • @ConfigurationProperties:负责将一组相关的配置属性映射到一个Java类中,通常用于处理结构化的、较复杂的配置。@ConfigurationProperties不仅加载属性,还绑定它们到类的字段上,使配置的管理更加方便和结构化。

      总结

      • 相似点:两者都与配置属性的处理有关,但@PropertySource更侧重于属性文件的加载,而@ConfigurationProperties侧重于属性值的绑定和管理。
      • 使用场景不同@PropertySource用于从自定义的文件加载属性,而@ConfigurationProperties则用于将这些属性整合到Java类中进行统一管理。 ^223021

9.8 登录校验

9.8.1 会话技术

  • 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
  • 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据
  • 会话跟踪方案
    • 客户端会话跟踪技术:Cookie
    • 服务器会话跟踪技术:Session
    • 令牌技术:Token

Cookie

  1. 什么是Cookie?
    Cookie是由服务器生成并发送到客户端(浏览器)的小型文本文件。客户端会将这些Cookie存储在本地,并在每次访问相同服务器时将它们包含在HTTP请求中,发送回服务器。

  2. Cookie的工作原理

  • 生成和存储:当用户第一次访问网站时,服务器可以生成一个或多个Cookie,并通过HTTP响应头将它们发送到客户端。客户端浏览器接收到这些Cookie后,会根据服务器指示(如ExpiresMax-Age属性)将Cookie存储在本地。

  • 发送Cookie:在用户的后续请求中,浏览器会自动将与该域名匹配的所有Cookie附加到HTTP请求中,发送给服务器。服务器可以读取这些Cookie的值,以便识别用户的身份或保持会话状态。

  • Cookie属性

    • Name=Value:Cookie的名称和值,浏览器会在后续请求中发送这些信息。
    • Domain:指定Cookie属于哪个域名,浏览器只会在访问该域名时发送此Cookie。
    • Path:指定Cookie适用的路径,只有访问此路径或子路径时,Cookie才会被发送。
    • Expires/Max-Age:设置Cookie的过期时间,决定Cookie在客户端存储的时长。
    • HttpOnly:防止客户端脚本访问Cookie,增加安全性。
    • Secure:指示Cookie只能通过HTTPS发送,防止Cookie在传输过程中被截获。
    • SameSite:防止跨站请求伪造攻击(CSRF)。
  1. Cookie的应用场景
  • 会话管理:保存用户登录状态,跟踪用户在网站上的活动。
  • 个性化设置:保存用户偏好,如主题、语言选择等。
  • 跟踪和分析:用于广告跟踪、用户行为分析等。
  1. Cookie的优缺点
  • 优点

    • 易于使用:可以在客户端和服务器之间简单地传递信息。
    • 跨请求保持状态:允许在多个请求之间保持状态(如用户登录状态)。
  • 缺点

    • 安全性问题:存储在客户端,容易被篡改或窃取。
    • 数据量有限:通常一个Cookie的大小限制为4KB,且总数有限。
    • 性能影响:每次请求都会带上Cookie,可能影响带宽和性能。
    • 不能跨域。

跨域问题只是针对浏览器的,后端不同服务器之间的数据传输不会存在跨域问题

  • 样例
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
29
@Slf4j
@RestController
public class CookieController {

// 设置Cookie
@GetMapping("/c1")
public Result cookie1(HttpServletResponse response){
response.addCookie(new Cookie("login_username", "itheima"));// 设置Cookie/响应Cookie
return Result.success();
}

// 获取Cookie 两种方法
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies){
if(cookie.getName().equals("login_username")){
log.info("login_username:{}",cookie.getValue());
return Result.success();
}
}
return Result.error("未找到指定Cookie");
}

@GetMapping("/c3")
public Result cookie3(@CookieValue("login_username") String username){
return Result.success(username);
}
}

Session

  1. 什么是Session?
    Session是服务器端的一种会话机制,用于跟踪和保存用户的状态和数据。与Cookie不同,Session数据存储在服务器端,客户端只保留一个标识符(通常是Session ID)。

  2. Session的工作原理

  • 生成Session:当用户访问服务器时,服务器会创建一个Session,并生成一个唯一的Session ID。这个Session ID会通过Cookie或URL参数传递给客户端。

  • 存储和检索:服务器将与该Session相关的数据存储在服务器端(如内存、数据库或文件系统)。当客户端再次发送请求时,会带上Session ID,服务器根据这个ID检索相应的Session数据。

  • Session的生命周期

    • 创建:Session在用户首次访问时创建,并分配一个唯一的Session ID。
    • 过期:Session有一定的有效期(可配置),当用户长时间不活动,Session会自动过期和销毁。
    • 销毁:用户主动登出或Session过期后,Session数据会从服务器端删除。
  1. Session的应用场景
  • 用户认证:保存用户登录信息,确保用户在会话期间不需要重复登录。
  • 购物车功能:保存用户的购物车内容,在整个会话期间保持一致。
  • 用户跟踪:跟踪用户在网站上的操作,并根据这些操作做出响应。
  1. Session的优缺点
  • 优点

    • 安全性较高:由于Session数据存储在服务器端,不容易被用户篡改或窃取。
    • 支持复杂数据:可以在Session中存储复杂的对象和数据结构。
    • 保持状态:适合需要在多个请求之间保持复杂状态的应用。
  • 缺点

    • 占用服务器资源:每个用户的Session数据都存储在服务器上,用户数量多时可能导致服务器资源消耗过大。
    • 扩展性问题:在分布式系统中,Session管理较为复杂,需要采取措施(如Session共享、Session粘性)来保证用户会话的一致性。
  • 样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Slf4j
@RestController
public class SessionController {

// 往HttpSession中存储值
@GetMapping("/s1")
public Result session1(HttpSession session) {
log.info("HttpSession-s1:{}", session.hashCode());
session.setAttribute("loginUser", "zhangsan"); // 往session中存储数据
return Result.success();
}

// 往HttpSession中获取值
public Result session2(HttpServletRequest request) {
HttpSession session = request.getSession();
log.info("HttpSession-s2:{}", session.hashCode());

Object loginUser = session.getAttribute("loginUser"); // 从session中获取数据
log.info("loginUser:{}", loginUser);
return Result.success(loginUser);
}
}

JWT
JWT(JSON Web Token)是一种常用的认证机制,它允许在网络应用中安全地在客户端和服务器之间传递信息。与Cookie和Session相比,JWT有着独特的工作方式和应用场景。

  1. 什么是JWT?
    JWT是一种开放标准(RFC 7519),用于在各方之间作为JSON对象安全传输信息。信息可以被验证和信任,因为它是数字签名的。JWT通常用于认证和授权。
  2. JWT的工作原理
  • 生成JWT:当用户成功登录后,服务器会生成一个JWT。这个Token由三部分组成:Header(头部)Payload(负载)Signature(签名)

    • Header:指定Token的类型(通常是JWT)和签名算法(如HMAC SHA256或RSA)。例如:{“alg”:”HS256”,”type”:”JWT”}
    • Payload:包含声明(Claims),即需要传输的用户信息、默认信息或其他数据。这些数据是公开的,任何人都可以解码查看。例如:{“id”:1,”username”:”Tom”}
    • Signature:由Header和Payload通过指定的算法加密生成,用于验证Token的真实性,确保安全性。
  • 生成的JWT通常类似这样

  • 测试样例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     // 在pom.xml中添加依赖:
    <!--JWT配置-->
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version> <!-- 请检查最新版本号 -->
    </dependency>
    // 测试样例
    @Test
    public void genjwt(){
    Map<String, Object> claims = new HashMap<>();
    claims.put("id", "1");
    claims.put("username","Tom");
    String jwt = Jwts.builder()
    .setClaims(claims) // 自定义内容(载荷)
    .signWith(SignatureAlgorithm.HS256, "lusy")// 签名算法
    .setExpiration(new Date(System.currentTimeMillis() + 12 * 3600 * 1000)) // 有效期 单位 ms
    .compact();
    System.out.println(jwt);
    }
  • 传递JWT:JWT生成后,服务器会将其返回给客户端。客户端通常将JWT存储在Local StorageSession Storage或Cookie中。

  • 验证JWT:在后续请求中,客户端会将JWT包含在请求头(通常是Authorization: Bearer <token>)中发送给服务器。服务器接收到JWT后,会使用相同的签名算法和密钥验证Token的真实性,并从Payload中提取用户信息。

    1
    2
    3
    4
    5
    6
    7
    8
     @Test
    public void parseJwt(){
    Claims claims = Jwts.parser()
    .setSigningKey("lusy")// 指定签名秘钥
    .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEiLCJleHAiOjE3MjM4NDk2MzIsInVzZXJuYW1lIjoiVG9tIn0.KrQ-8Y4ISZds9DZ_OHaXvrrLM-4By84ngwV8vz9A6bE")// 解析令牌
    .getBody();
    System.out.println(claims);
    }

    注意事项

    • JWT校验时使用的签名秘钥,必须和生成的JWT令牌时使用的秘钥是配套的。
    • 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法。
  1. JWT的应用场景
  • 用户认证:在用户登录成功后,服务器生成JWT并返回给客户端。客户端在后续请求中携带这个JWT,以证明自己的身份。

  • 授权:服务器可以在JWT的Payload中包含用户的权限信息(如角色、权限范围),以决定用户能访问哪些资源。

  • 信息交换:JWT可以安全地传输信息,因为它是经过签名的,接收方可以验证信息的来源和完整性。

  1. JWT的优缺点
  • 优点

    • 无状态:JWT是无状态的,不需要在服务器端存储会话信息,这使得它非常适合分布式系统。
    • 跨域支持:JWT不依赖于Cookie,因此可以在不同域名之间轻松传递。
    • 可扩展性:JWT的Payload部分可以包含自定义的声明,灵活性高。
  • 缺点

    • 安全性风险:JWT的Payload部分是可见的,尽管它是签名的,但不能加密。如果存储敏感信息,可能会有安全隐患。
    • Token大小:JWT通常比传统的Session ID大,因为它包含了更多信息,这可能增加网络传输的开销。
    • Token管理:一旦JWT被签发,服务器无法主动撤销Token的权限,除非采用额外的策略(如Token黑名单)。

会话跟踪方案对比

9.8.2 过滤器Filter

  • 概述

    • 概念:Filter 过滤器,是JavaWeb三大组件(Servlet,Filter,Listener)之一。
    • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
    • 过滤器一般完成一些通用的操作,比如:登录校验,统一编码处理、敏感字符处理等。
  • 快速入门

    1. 创建一个Filter类,实现Filter接口,重写接口中的方法。
    2. 配置Filter:Filter类加上@WebFilter注解,配置拦截资源的路径,引导类上加@ServletComponentScan注解开启Servlet组件支持。
  • 拦截路径
    Filter可以根据需求,配置不同的拦截资源路径:

    拦截路径urlPatterns值含义
    拦截具体路径/login只有访问 /login 路径时,才会被拦截
    目录拦截/emps/*访问 /emps/ 下的所有资源,都会被拦截
    拦截所有/*访问所有资源,都会被拦截
    1
    2
    3
    4
    @WebFilter(urlPatterns = "/*")
    public class MyFilter implements Filter {

    }
  • 执行流程

  • 过滤器链

    注意
    存在多个过滤器时,过滤器的执行顺序是按照过滤器配置的顺序执行的。但如果没有明确指定执行顺序(例如通过@Order注解),那么容器通常会根据类名的字典顺序(字母顺序)来决定过滤器的执行顺序。

9.8.3 拦截器(Interceptor)

  • 概述

    • 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。
    • 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
  • 快速入门


    WebMvcConfigurer: [[WebMvcConfigurer#^mk-20240915210900|WebMvcConfigurer接口的讲解]]

  • 拦截器可以根据需求,配置不同的拦截路径

  • 执行流程

  • 多个拦截器执行顺序

    1. preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
    2. postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
    3. afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。

9.8.4 异常处理


9.9 参数校验

在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。

校验概述
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并过标准的验证接口对Bean进行验证。

注解规则
@Null标注值必须为 null
@NotNull标注值不可为 null
@AssertTrue标注值必须为 true
@AssertFalse标注值必须为 false
@Min(value)标注值必须大于或等于 value
@Max(value)标注值必须小于或等于 value
@DecimalMin(value)标注值必须大于或等于 value
@DecimalMax(value)标注值必须小于或等于 value
@Size(max,min)标注值大小必须在 max 和 min 限定的范围内
@Digits(integer,fratction)标注值值必须是一个数字,且必须在可接受的范围内
@Past标注值只能用于日期型,且必须是过去的日期
@Future标注值只能用于日期型,且必须是将来的日期
@Pattern(value)标注值必须符合指定的正则表达式

JSR 303 只是一套标准,需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:

注解规则
@Email标注值必须是格式正确的 Email 地址
@Length标注值字符串大小必须在指定的范围内
@NotEmpty标注值字符串不能是空字符串
@Range标注值必须在指定的范围内

Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC 中,可直接通过注动 @EnableWebMvc 的方式进行数据校验。Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器定义一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中。Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下。

配置 @EnableWebMvc 后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的作。
操作演示

  • 导入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 校验注解 -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>

<!-- 校验注解实现-->
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.0.Final</version>
</dependency>
  • 应用校验注解
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import org.hibernate.validator.constraints.Length;

/**
* projectName: com.atguigu.pojo
*/
public class User {
//age 1 <= age < = 150
@Min(10)
private int age;

//name 3 <= name.length <= 6
@Length(min = 3,max = 10)
private String name;

//email 邮箱格式
@Email
private String email;

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}

  • handler标记和绑定错误收集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestController
@RequestMapping("user")
public class UserController {

/**
* @Validated 代表应用校验注解! 必须添加!
*/
@PostMapping("save")
public Object save(@Validated @RequestBody User user,
//在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
BindingResult result){
//判断是否有信息绑定错误! 有可以自行处理!
if (result.hasErrors()){
System.out.println("错误");
String errorMsg = result.getFieldError().toString();
return errorMsg;
}
//没有,正常处理业务即可
System.out.println("正常");
return user;
}
}

易混总结
@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。

  1. @NotNull (包装类型不为null)

    @NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解。

  2. @NotEmpty (集合类型长度大于0)

    @NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。

  3. @NotBlank (字符串,不为null,切不为” “字符串)

    @NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。

总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验。

10. 事务管理

10.1 事务回顾

  • 概念:事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败
  • 操作
    • 开启事务(一组操作开始前,开启事务)start transaction / begin;
    • 提交事务(这组操作全部成功后,提交事务)commit;
    • 回滚事务(中间任何一个操作出现异常,回滚事务)rollback;

10.2 Spring事务管理

  • 注解@Transactional
  • 位置:业务(Service)层的方法上、类上、接口上
  • 作用:将当前方法交给Spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
  • 属性
    • rollbackFor回滚:默认情况下,只有出现RuntimeException才会回滚异常,rollbackFor属性用于控制出现何种异常类型,回滚事务
    • noRollbackFor不回滚:指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!

      如果rollbackFornoRollbackFor同时指定同一类型,Spring的行为会出现冲突和不确定性

    • timeout超时时间:当事务运行时间超过了指定的时间限制,事务将被强制回滚,并抛出一个 TransactionTimedOutException 异常。默认值为-1,表示永不超时
    • propagation传播行为:指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制
属性值含义
REQUIRED【默认值】需要事务,有则加入,无则创建新事务
REQUIRES_NEW需要新事务,无论有无,总是创建新事务
SUPPORTS支持事务,有则加入,无则在无事务状态中运行
NOT_SUPPORTED不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY必须有事务,否则抛出异常
NEVER必须没有事务,否则抛出异常
  1. REQUIRED:大部分情况下都是用该传播行为即可。
  2. REQUIRES_NEW:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
  • isolation隔离级别:设置事务的隔离级别,mysql默认是repeatable read!
  1. 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
  2. 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
  3. 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
  4. 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。

    开启spring事务管理日志
    在配置文件中添加

    1
    2
    3
    >logging:
    level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

    注意事项
    如果在非SpringBoot框架中想使用事务功能,需要在配置类添加@EnableTransactionManagement注解,编写一个Spring事务管理器配置类,并在pom.xml中添加引入spring-tx依赖。
    @EnableTransactionManagement 是Spring中的一个注解,用于启用Spring的注解驱动的事务管理功能。它通常与 @Configuration 注解一起使用,配置类中声明它表示希望在应用程序中使用基于注解的事务管理。

    1
    2
    3
    4
    5
    6
     <!-- 声明式事务依赖-->
    ><dependency>
    <groupId>org. springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>6.0.6</version>
    ></dependency>

11.AOP

11.1 概述

  • AOP:Aspect Oriented Programming(面向切面编程,面向方法编程),其实就是面对特定方法编程。
  • 场景
    案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时

    • 记录操作日志
    • 权限控制
    • 事务管理
  • 优势
    • 代码无侵入
    • 减少重复代码
    • 提高开发效率
    • 维护方便
  • 实现
    动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理Bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

11.2 快速入门

统计各个业务层方法执行耗时

  • 导入依赖:在pom.xml中添加AOP依赖
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
  • 编写AOP依赖:针对特定的方法根据业务需要进行编程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Component
    @Aspect
    public class TimeAspect {
    @Around("execution(* com.lusy.service.impl.*.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = pjp.proceed(); // 调用原始方法运行
    long end = System.currentTimeMillis();
    log.info(pjp.getSignature() + "方法执行耗时:{}ms" + (end - start));
    return result;
    }
    }

    注意事项
    如果不是在springboot框架中使用AOP,还需要在配置类中添加@EnableAspectJAutoProxy注解开启AOP功能

11.3 AOP核心概念

11.4 通知类型

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning:返回后通知,此注解标注的通知方法在目标方法正常返回后被执行,有异常不会执行
  • @AfterThrowing:异常后通知,此注解标注的通知方法在目标方法抛出异常后被执行,正常返回不会执行

    注意事项

    • @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed()方法来让原始方法执行,其它通知则不需要考虑目标方法执行
    • @Around环绕通知的返回值,必须指定为Object类型,来接收原始方法的返回值
    • @AfterReturning@AfterThrowing通知,如果需要指定返回值来接收原始方法的返回值或异常对象,可以通过各自的属性值returningthrowing来指定接收返回值或异常对象的变量名。
    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
    // @AfterReturning注解标记返回通知方法
    // 在返回通知中获取目标方法返回值分两步:
    // 第一步:在@AfterReturning注解中通过returning属性设置一个名称
    // 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
    @AfterReturning(
    value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
    returning = "targetMethodReturnValue")
    public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {

    String methodName = joinPoint.getSignature().getName();

    System.out.println("[AOP返回通知] "+methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue);
    }
    // @AfterThrowing注解标记异常通知方法
    // 在异常通知中获取目标方法抛出的异常分两步:
    // 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
    // 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
    @AfterThrowing(
    value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
    throwing = "targetMethodException")
    public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) {

    String methodName = joinPoint.getSignature().getName();

    System.out.println("[AOP异常通知] "+methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName ());
    }

11.5 @Pointcut注解

该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。

11.6 通知顺序

11.7 切入表达式

  • 概念:描述切入点方法的一种表达式
  • 作用:主要用来决定项目中的哪些方法需要加入通知
  • 常见形式
    • execution(……):根据方法的签名来匹配
    • annotation(……):根据注解匹配

11.7.1 execution

execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution([访问修饰符] 返回值 [包名.类名.]方法名(方法参数) [throws 异常])

  • 其中方法参数填写的是参数类型的全类名,简单类型可以直接指定
  • 其中可省略的部分:
    • 访问修饰符:可以省略,表示任意访问修饰符(比如:public、protected)
    • 包名.类名:可以省略,表示任意包名、类名
    • throws 异常:可以省略,表示任意异常(注意是方法上声明抛出的异常,不是实际抛出的异常)
1
2
3
4
@Before("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
public void before(JoinPoint joinPoint) {
// 方法体
}
  • 可以使用通配符描述切入点
    • *:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
    1
    execution(* com.*.service.impl.*.*(*))
    • ..:多个独立的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数,在通配包时,不能作为开头。如果要指定全部包,可以用*..
    1
    2
    3
    4
    5
    execution(* com.itheima..DeptService.*(..))
    execution(* com.example.service..*.*(..))
    execution(* ..example.service..*.*(..)) // 错误的
    // 查询全部包下,无参数的方法
    execution(* *..*.*(..)) //第三个 *:匹配类名中的最后一级,防止出现连续的 ...

注意事项
根据业务需要,可以使用 且(&&) 、或(||)、非(!)来组合比较复杂的切入点表达式。

  • 书写建议
    • 所有业务方法名命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是find开头,更新类方法都是update开头
    • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
    • 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用.. ,使用 * 匹配单个包。

image.png|450

11.7.2 annotation

@annotation注解的切点表达式用于匹配被指定注解标记的方法。例如,如果你有一个自定义注解@MyLog,你可以使用@annotation来指定所有带有@MyLog注解的方法作为切点。

  • 示例
    假设你有一个自定义注解@MyLog
    1
    2
    3
    4
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MyLog {
    }
    你希望在所有标记了@MyLog的地方记录方法执行时间,可以定义一个切面如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Aspect
    @Component
    public class LoggingAspect {

    @Before("@annotation(com.example.MyLog)")
    public void logExecutionTime(JoinPoint joinPoint) {
    // 在方法执行前记录开始时间
    System.out.println("Method " + joinPoint.getSignature().getName() + " is about to execute");
    }

    @After("@annotation(com.example.MyLog)")
    public void afterExecution(JoinPoint joinPoint) {
    // 在方法执行后记录结束时间
    System.out.println("Method " + joinPoint.getSignature().getName() + " has executed");
    }
    }
    使用@MyLog注解:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Service
    public class MyService {

    @MyLog
    public void performTask() {
    // 业务逻辑
    System.out.println("Task is being performed");
    }
    }

11.8 连接点

  • 在Spring中用JoinPoint抽象了连接点,用它可以获取方法执行时的相关信息,如目标类名,方法名,方法参数等。
    • 对于@Around通知,获取连接点信息只能使用 ProceedingJoinPoint
    • 对于其它四种通知,获取连接点信息只能使用 JoinPoint,它是ProceedingJoinPoint的父类

12.配置

  • SpringBoot除了支持配置文件属性配置,还支持Java系统属性命令行参数的方式进行属性配置。
  • 优先级(高 -> 低)
    • 命令行参数 (–xxx=xx)
    • java系统属性(-Dxxx=xxx)
    • application.properties
    • application.yml
    • application.yml(忽略)

13.Bean的管理

13.1 获取Bean

  • 默认情况下,Spring项目启动时,会把Bean都创建好放在IOC容器中,如果想要主动获取这些Bean,可以通过如下方式:
    • 根据name获取BeanObject getBean(String name)
    • 根据类型获取Bean<T> T getBean(Class<T> requireType)
    • 根据name获取Bean(带类型转换)<T> T getBean(String name, Class<T> requireType)

      注意事项
      上述所说的【Spring项目启动时,会把其中的Bean都创建好】还会受到作用域及延迟初始化影响,这里主要针对于默认的单例非延迟加载的Bean而言。

13.2 Bean作用域

  • Spring支持五种作用域,后三种在web环境下才生效:
  • 可以通过@Scope注解来进行配置作用域
    1
    2
    3
    4
    5
    6
    @Scope("prototype")
    @RestController
    @RequestMapping("/depts")
    public class DeptController{

    }

    注意事项

    • 默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)
    • prototype的bean,每一次使用该bean的时候都会创建一个新的实例
    • 实际开发当中,绝大部分的Bean是单例的,也就是说大部分Bean不需要配置Scope属性

13.3 第三方Bean

  • @Bean
    • 如果要管理的Bean对象来自第三方(不是自定义的),是无法用Component及衍生注解声明Bean的,就需要用到@Bean注解。
    • 若要管理的第三方Bean对象,建议对这些Bean进行分类配置,可以通过 @Configuration注解来声明一个配置类。

      注意事项

      • @Bean修饰的方法将当前方法的返回值对象交给IOC容器管理,成为IOC容器的Bean
      • 通过@Bean注解的name或value属性可以声明Bean的名称,如果不指定,默认Bean的名称就是方法名。
      • 周期方法指定:
        • 原有注解方案:@PostConstruct、@PreDestroy 注解指定
        • bean属性指定:initMethod、destroyMethod 指定
      • 如果第三方Bean需要依赖其它Bean对象,直接在@Bean定义的方法中设置形参即可,容器会根据类型自动装配。如果有多个类型匹配,可以使用形参名等同于对应的Beanid标识即可。

@Component及衍生注解与@Bean注解使用场景?

  • 项目中自定义的,使用@Component及其衍生注解
  • 项目中引入第三方的,使用@Bean注解

14.自动配置原理

14.1 @ComponentScan 组件扫描

14.2 @Import导入

@Import注解将配置类、普通类、第三方的组件注册到 IOC 容器,组件在容器中的名字是全类名

14.3 源码跟踪

  • @SpringBootApplication
    该注解标识在SpringBoot工程引导类上,是SpringBoot中最重要的注解。该注解由三个部分组成:

    • @SpringBootConfiguration:该注解与@Configuration注解作用相同,用来声明当前也是一个配置类。
    • @ComponentScan:组件扫描,默认扫描当前引导类所在包及其子包。
    • @EnableAutoConfiguration:SpringBoot实现自动化配置的核心注解。

  • @Conditional

    • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的Bean对象到Spring IOC容器中。
    • 位置:方法、类
    • @Conditional本身是一个父注解,派生出大量的子注解
      • @ConditionalOnClass:判断环境中是否有对应字节码文件,才注册Bean到IOC容器
      • @ConditionalOnMissingBean:判断环境中没有对应的Bean(类型或名称),才注册Bean到IOC容器
      • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册Bean到IOC容器
1
2
3
4
5
@Bean
@ConditionalOnMissingBean
public Gson gson(GsonBuilder gsonBuilder){
return gsonBuilder.create();
}

14.4 自定义starter


15.Maven高级

15.1 分模块设计

  1. 什么是分模块设计?
  • 将项目按照功能拆分成若干个子模块
  1. 为什么要分模块设计?
  • 方便项目的管理维护、拓展、也方便模块间的相互调用,资源共享
  1. 注意事项
  • 分模块设计需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分

15.2 继承

  • 概念:继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承
  • 作用:简化依赖配置、统一管理依赖
  • 实现<parent>……</parent>
  • 具体步骤


  • 总结

打包方式

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

15.3 版本锁定

  • 在maven中,可以在父工程的pom文件中通过<dependencyManagement>来统一管理依赖版本。
  • <dependencyManagement> 是用来管理依赖版本的,它用于声明依赖的版本,但这些依赖不会自动导入到项目中,还需要再<dependencies>中引入。

注意事项

  • 子工程引入依赖时,无需指定 <version>版本号,父工程统一管理。变更依赖版本,只需在父工程中统一变更。

<dependencyManagement>与<dependencies>的区别

  • <dependencies>是直接依赖,在父工程配置了依赖,子工程会直接继承下来。
  • <dependencyManagement>是统一管理依赖版本,不会直接依赖,还需要在子工程中引入所需依赖(无需指定版本)
  • 也可以通过<properties>自定义/引用属性。

15.4 聚合

  • 概念:将多个模块组织成一个整体,同时进行项目的构建。
  • 聚合工程:一个不具有业务功能的”空”工程(有且仅有一个pom文件)
  • 作用:快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)
  • 实例:聚合使得在父项目中执行 mvn install 等命令时,所有子模块都会被一起构建。

继承与聚合

  • 作用
    • 聚合用于快速构建项目
    • 继承用于简化依赖配置、统一管理依赖
  • 相同点
    • 聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
    • 聚合与继承均属于设计型模块,并无实际的模块内容
  • 不同点
    • 聚合是在聚合工程中配置关系,聚合可以感知到参与聚合的模块有哪些
    • 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

15.5 私服

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




16 Mybatis 的 Mapper 文件什么时候需要使用 @Param

@Param 注解的主要作用是 给 MyBatis 传递的参数起别名,从而在 mapper.xml 里可以正确引用参数。是否需要 @Param 取决于你的方法参数类型和 MyBatis 解析参数的方式。


✅ 需要 @Param 的情况

1️⃣ 方法参数有多个

如果 Mapper 方法有 两个或以上参数,MyBatis 默认不会自动识别参数名,需要 @Param 指定别名,否则 mapper.xml 无法正确引用:

1
2
List<DeptVo> findDeptByNameAndState(@Param("deptName") String deptName, 
@Param("dataState") String dataState);
1
2
3
4
5
<select id="findDeptByNameAndState" resultMap="BaseResultMap">
select * from sys_dept
where dept_name like concat('%', #{deptName}, '%')
and data_state = #{dataState}
</select>

❌ 不加 @Param 可能会报错

1
Parameter 'deptName' not found. Available parameters are [arg0, arg1, param1, param2]

因为 MyBatis 默认只给参数分配 param1, param2 这样的名字,XML 里 #{deptName} 找不到对应的参数。


2️⃣ [ ]ListCollection 作为参数

如果方法参数是 List 或 Collection,MyBatis 不会自动赋予参数名,需要用 @Param 指定别名:

1
List<DeptVo> findDeptInDeptNos(@Param("deptNos") List<String> deptNos);
1
2
3
4
5
6
<select id="findDeptInDeptNos" resultMap="BaseResultMap">
select * from sys_dept where dept_no in
<foreach collection="deptNos" item="deptNo" open="(" separator="," close=")">
#{deptNo}
</foreach>
</select>

❌ 不加 @Param("deptNos") 可能会报错

1
There is no getter for property named 'deptNos' in class 'java.util.ArrayList'

因为 MyBatis 认为 List 参数没有名字,而 XML 里 collection="deptNos" 需要一个明确的参数名。


3️⃣ 需要在 mapper.xml 里使用参数别名

如果你想在 SQL 里更清楚地引用参数,比如:

1
DeptVo findDeptInfo(@Param("dept") DeptDto dept);
1
2
3
4
5
<select id="findDeptInfo" resultMap="BaseResultMap">
select * from sys_dept
where dept_no = #{dept.deptNo}
and dept_name like concat('%', #{dept.deptName}, '%')
</select>

如果不加 @Param("dept"),在 XML 里就不能用 #{dept.xxx} 来访问 DeptDto 里的属性


❌ 不需要 @Param 的情况

1️⃣ 只有一个基本类型参数

如果方法只有一个参数,且是 基本数据类型或 String,MyBatis 会自动识别

1
DeptVo findDeptByNo(String deptNo);
1
2
3
<select id="findDeptByNo" resultMap="BaseResultMap">
select * from sys_dept where dept_no = #{deptNo}
</select>

不需要 @Param,MyBatis 会自动匹配 #{deptNo}


2️⃣ 只有一个 JavaBean 或 DTO 作为参数

如果方法只有一个对象参数(JavaBean 或 DTO),MyBatis 会自动匹配对象的字段

1
DeptVo findDeptByDto(DeptDto deptDto);
1
2
3
4
5
<select id="findDeptByDto" resultMap="BaseResultMap">
select * from sys_dept
where dept_no = #{deptNo}
and dept_name like concat('%', #{deptName}, '%')
</select>

MyBatis 会自动解析 DeptDto 里的字段,不需要 @Param


3️⃣ 只有一个 ListCollection 参数,并且 XML 里用 collection="list"

MyBatis 默认把 List 绑定到 list,所以如果 XML 里使用 collection="list",也可以不用 @Param

1
List<DeptVo> findDeptInDeptNos(List<String> deptNos);
1
2
3
<foreach collection="list" item="deptNo" open="(" separator="," close=")">
#{deptNo}
</foreach>

可以不加 @Param,因为 MyBatis 默认会把 List 绑定到 list 这个名字


🚀 总结:什么时候需要 @Param

情况需要 @Param说明
多个参数(基本类型或 String)✅ 需要MyBatis 默认不会自动匹配参数名
只有一个基本类型参数❌ 不需要MyBatis 会自动识别
只有一个 JavaBean 或 DTO 参数❌ 不需要MyBatis 会自动匹配字段
ListCollection 参数✅ 需要(除非用 list 作为默认名)MyBatis 默认不给 List 取名
想给参数起别名✅ 需要方便 XML 里使用更清晰的参数名
📌 推荐的做法
  • 单个基本类型或 JavaBean 作为参数时,不用 @Param,MyBatis 能自动解析。
  • 有多个参数时,一定要用 @Param,否则 MyBatis 只能用 param1, param2 访问参数。
  • List 作为参数时,最好加 @Param("xxx"),否则只能用 collection="list" 访问。

如果你不确定是否需要 @Param加上它总是安全的,但如果只有一个简单参数,可以省略。 🚀

  • Title: Java IO流
  • Author: Lu
  • Created at : 2024-06-01 08:46:53
  • Updated at : 2025-03-11 12:44:58
  • Link: https://lusy.ink/2024/06/01/JavaWeb/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
Java IO流