Spring Boot和maven


mysql

介绍


数据库:存储和管理数据的仓库,数据是有组织的进行存储。数据库英文名是 DataBase,简称DB。

数据库就是将数据存储在硬盘上,可以达到持久化存储的效果。那又是如何解决上述问题的?使用数据库管理系统。

数据库管理系统:管理数据库的大型软件。DataBase Management System,简称 DBMS

我们平时说的MySQL数据库其实是MySQL数据库管理系统。

常见的数据库管理系统:

Oracle:收费的大型数据库,Oracle 公司的产品
==MySQL==: 开源免费的中小型数据库。后来 Sun公司收购了 MySQL,而 Sun 公司又被 Oracle 收购
SQL Server:MicroSoft 公司收费的中型的数据库。C#、.net 等语言常使用
PostgreSQL:开源免费中小型的数据库
DB2:IBM 公司的大型收费数据库产品
SQLite:嵌入式的微型数据库。如:作为 Android 内置数据库
MariaDB:开源免费中小型的数据库

SQL:英文:Structured Query Language,简称 SQL,结构化查询语言。操作关系型数据库的编程语言。定义操作所有关系型数据库的统一标准,可以使用SQL操作所有的关系型数据库管理系统,以后工作中如果使用到了其他的数据库管理系统,也同样的使用SQL来操作。


MySQL


下载:https://downloads.mysql.com/archives/community/

配置环境变量

配置文件:

[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8 
[mysqld]
# 设置mysql的安装目录
basedir = E:\mysql-5.7.27-winx64\mysql-5.7.27-winx64
# 设置mysql数据库的数据的存放目录
datadir = E:\mysql-5.7.27-winx64\mysql-5.7.27-winx64\data
# 允许最大连接数
max_connections=200
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 如果需要的存储引擎被禁用或未编译,可以防止自动替换存储引擎
# 为事务存储引擎启用严格模式,也可能为非事务存储引擎启用严格模式
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
# 开启查询缓存
explicit_defaults_for_timestamp=true

以管理员方式运行,初始化MySQL:

mysqld --initialize-insecure

注册MySQL服务:

mysqld -install

启动MySQL服务:

net start mysql

修改默认账户密码:

mysqladmin -u root password ****

登陆:

mysql -uroot -p****

登陆其他机器参数:

mysql -u用户名 -p密码 -h要连接的mysql服务器的ip地址(默认127.0.0.1) -P端口号(默认3306)

退出mysql:

exit
quit

MySQL数据模型


关系型数据库:关系型数据库是建立在关系模型基础上的数据库,简单说,关系型数据库是由多张能互相连接的二维表组成的数据库。

在MySQL中一个数据库对应到磁盘上的一个文件夹。db.frm 是表文件,db.MYD 是数据文件,通过这两个文件就可以查询到数据展示成二维表的效果。

MySQL中可以创建多个数据库,每个数据库对应到磁盘上的一个文件夹;在每个数据库中可以创建多个表,每张都对应到磁盘上一个 frm 文件;每张表可以存储多条数据,数据会被存储到磁盘中 MYD 文件中。


SQL


通过SQL语句对数据库、表、数据进行增删改查操作。 英文:Structured Query Language,简称 SQL。结构化查询语言,一门操作关系型数据库的编程语言。

SQL语句可以单行或多行书写,以分号结尾。

MySQL 数据库的 SQL 语句不区分大小写,关键字建议使用大写。

注释

单行注释: -- 注释内容 或 #注释内容(MySQL 特有) 

多行注释: /* 注释 */

SQL分类

DDL(Data Definition Language) : 数据定义语言,用来定义数据库对象:数据库,表,列等
DML(Data Manipulation Language) 数据操作语言,用来对数据库中表的数据进行增删改
DQL(Data Query Language) 数据查询语言,用来查询数据库中表的记录(数据)
DCL(Data Control Language) 数据控制语言,用来定义数据库的访问权限和安全级别,及创建用户

DDL操作数据库


查询所有的数据库:

SHOW DATABASES;

创建数据库:

CREATE DATABASE 数据库名称;

创建数据库(判断,如果不存在则创建):

CREATE DATABASE IF NOT EXISTS 数据库名称;

删除数据库:

DROP DATABASE 数据库名称;

删除数据库(判断,如果存在则删除):

DROP DATABASE IF EXISTS 数据库名称;

使用数据库:

USE 数据库名称;

查看当前使用的数据库:

SELECT DATABASE();

DDL操作数据表


操作表也就是对表进行增(Create)删(Retrieve)改(Update)查(Delete)。

查询当前数据库下所有表名称:

SHOW TABLES;

查询表结构:

DESC 表名称;

创建表:

CREATE TABLE 表名 (
    字段名1  数据类型1,
    字段名2  数据类型2,
    …
    字段名n  数据类型n
);

注意:最后一行末尾,不能加逗号。

数据类型-MySQL支持多种类型,可以分为三类:

数值:
TINYINT	1 byte	小整数值
SMALLINT	2 bytes	大整数值
MEDIUMINT	3 bytes	大整数值
INT或INTEGER	4 bytes	大整数值
BIGINT	8 bytes	极大整数值
FLOAT	4 bytes	单精度浮点数值
DOUBLE	8 bytes	双精度浮点数值
DECIMAL		小数值

日期:
DATE	3	日期值
TIME	3	时间值或持续时间
YEAR	1	年份值
DATETIME	8	混合日期和时间值
TIMESTAMP	4	混合日期和时间值,时间戳

字符串:
CHAR	0-255 bytes	定长字符串
VARCHAR	0-65535 bytes	变长字符串
TINYBLOB	0-255 bytes	不超过 255 个字符的二进制字符串
TINYTEXT	0-255 bytes	短文本字符串
BLOB	0-65 535 bytes	二进制形式的长文本数据
TEXT	0-65 535 bytes	长文本数据
MEDIUMBLOB	0-16 777 215 bytes	二进制形式的中等长度文本数据
MEDIUMTEXT	0-16 777 215 bytes	中等长度文本数据
LONGBLOB	0-4 294 967 295 bytes	二进制形式的极大文本数据
LONGTEXT	0-4 294 967 295 bytes	极大文本数据

char: 定长字符串。优点:存储性能高;缺点:浪费空间;name char(10) 如果存储的数据字符个数不足10个,也会占10个的空间。

varchar: 变长字符串。优点:节约空间;缺点:存储性能底;name varchar(10) 如果存储的数据字符个数不足10个,那就数据字符个数是几就占几个的空间。

double的使用举例,设置分数,0-100,保留两位小数:

score double(总长度, 小数点后保留的位数)
score double(5, 2)

删除表:

DROP TABLE 表名;

删除表时判断表是否存在:

DROP TABLE IF EXISTS 表名;

修改表名:

ALTER TABLE 表名 RENAME TO 新的表名;

# 将表名student修改为stu
alter table student rename to stu;

添加一列:

ALTER TABLE 表名 ADD 列名 数据类型;

# 给stu表添加一列address,该字段类型是varchar(50)
alter table stu add address varchar(50);

修改数据类型:

ALTER TABLE 表名 MODIFY 列名 新数据类型;

# 将stu表中的address字段的类型改为 char(50)
alter table stu modify address char(50);

修改列名和数据类型:

ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;

# 将stu表中的address字段名改为 addr,类型改为varchar(50)
alter table stu change address addr varchar(50);

删除列:

ALTER TABLE 表名 DROP 列名;

# 将stu表中的addr字段 删除
alter table stu drop addr;

DML


DML主要是对数据进行增(insert)删(delete)改(update)操作。

给指定列添加数据:

INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…);

给全部列添加数据:

INSERT INTO 表名 VALUES(值1,值2,…);

批量添加数据:

INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…),(值1,值2,…),(值1,值2,…)…;
INSERT INTO 表名 VALUES(值1,值2,…),(值1,值2,…),(值1,值2,…)…;

练习:

-- 给指定列添加数据
INSERT INTO stu (id, NAME) VALUES (1, '张三');
-- 给所有列添加数据,列名的列表可以省略的
INSERT INTO stu (id,NAME,sex,birthday,score,email,tel,STATUS) VALUES (2,'李四','男','1999-11-11',88.88,'lisi@itcast.cn','13888888888',1);

INSERT INTO stu VALUES (2,'李四','男','1999-11-11',88.88,'lisi@itcast.cn','13888888888',1);

-- 批量添加数据
INSERT INTO stu VALUES 
    (2,'李四','男','1999-11-11',88.88,'lisi@itcast.cn','13888888888',1),
    (2,'李四','男','1999-11-11',88.88,'lisi@itcast.cn','13888888888',1),
    (2,'李四','男','1999-11-11',88.88,'lisi@itcast.cn','13888888888',1);

修改表数据:

UPDATE 表名 SET 列名1=值1,列名2=值2,… [WHERE 条件] ;
# 将张三的性别改为女
update stu set sex = '女' where name = '张三';

# 将张三的生日改为 1999-12-12 分数改为99.99
update stu set birthday = '1999-12-12', score = 99.99 where name = '张三';

注意:

  1. 修改语句中如果不加条件,则将所有数据都修改!
  2. 像上面的语句中的中括号,表示在写sql语句中可以省略这部分

删除数据:

DELETE FROM 表名 [WHERE 条件] ;

# 删除张三记录
delete from stu where name = '张三';

# 删除stu表中所有的数据
delete from stu;

DQL


查询的完整语法:

SELECT 
    字段列表
FROM 
    表名列表 
WHERE 
    条件列表
GROUP BY
    分组字段
HAVING
    分组后条件
ORDER BY
    排序字段
LIMIT
    分页限定

查询多个字段:

SELECT 字段列表 FROM 表名;
SELECT * FROM 表名; -- 查询所有数据

去除重复记录:

SELECT DISTINCT 字段列表 FROM 表名;

起别名:

AS: AS 也可以省略

select name,math 数学成绩,english 英文成绩 from stu;

条件查询:

# 查询年龄大于等于20岁 并且 年龄 小于等于 30岁 的学生信息
select * from stu where age >= 20 &&  age <= 30;
select * from stu where age >= 20 and  age <= 30;
select * from stu where age BETWEEN 20 and 30;

# 查询年龄不等于18岁的学生信息
select * from stu where age != 18;
select * from stu where age <> 18;

# 查询年龄等于18岁 或者 年龄等于20岁 或者 年龄等于22岁的学生信息
select * from stu where age = 18 or age = 20 or age = 22;
select * from stu where age in (18,20 ,22);

# 查询英语成绩为 null的学员信息
# null值的比较不能使用 =  或者 != 。需要使用 is  或者 is not
select * from stu where english is null;
select * from stu where english is not null;

模糊查询:

模糊查询使用like关键字,可以使用通配符进行占位:_: 代表单个任意字符; %: 代表任意个数字符。

# 查询姓'马'的学生信息
select * from stu where name like '马%';

# 查询第二个字是'花'的学生信息  
select * from stu where name like '_花%';

# 查询名字中包含 '德' 的学生信息
select * from stu where name like '%德%';

排序查询:

SELECT 字段列表 FROM 表名 ORDER BY 排序字段名1 [排序方式1],排序字段名2 [排序方式2] …;

排序方式有两种,分别是:ASC : 升序排列 **(默认值)**,DESC : 降序排列
注意:如果有多个排序条件,当前边的条件值一样时,才会根据第二条件进行排序

select * from stu order by age ;
select * from stu order by math desc ;
select * from stu order by math desc , english asc ;

聚合函数:将一列数据作为一个整体,进行纵向计算。

聚合函数分类:

函数名 功能
count(列名) 统计数量(一般选用不为null的列)
max(列名) 最大值
min(列名) 最小值
sum(列名) 求和
avg(列名) 平均值

聚合函数语法:

SELECT 聚合函数名(列名) FROM 表;
# 注意:null 值不参与所有聚合函数运算

# 统计班级一共有多少个学生
select count(id) from stu;
select count(*) from stu;

# 查询数学成绩的最高分
select max(math) from stu;

分组查询:

SELECT 字段列表 FROM 表名 [WHERE 分组前条件限定] GROUP BY 分组字段名 [HAVING 分组后条件过滤];
# 分组之后,查询的字段为聚合函数和分组字段,查询其他字段无任何意义

# 查询男同学和女同学各自的数学平均分
select sex, avg(math) from stu group by sex;

# 查询男同学和女同学各自的数学平均分,以及各自人数
select sex, avg(math),count(*) from stu group by sex;

# 查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组
select sex, avg(math),count(*) from stu where math > 70 group by sex;

# 查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组,分组之后人数大于2个的
select sex, avg(math),count(*) from stu where math > 70 group by sex having count(*)  > 2;

where 和 having 区别:执行时机不一样:where 是分组之前进行限定,不满足where条件,则不参与分组,而having是分组之后对结果进行过滤。可判断的条件不一样:where 不能对聚合函数进行判断,having 可以。

分页查询:

SELECT 字段列表 FROM 表名 LIMIT  起始索引 , 查询条目数;
# 上述语句中的起始索引是从0开始

# 从0开始查询,查询3条数据
select * from stu limit 0 , 3;

# 每页显示3条数据,查询第1页数据
select * from stu limit 0 , 3;

# 每页显示3条数据,查询第2页数据
select * from stu limit 3 , 3;

# 公式:
起始索引 = (当前页码 - 1) * 每页显示的条数

约束


约束是作用于表中列上的规则,用于限制加入表的数据。约束的存在保证了数据库中数据的正确性、有效性和完整性。

分类:

非空约束: 关键字是 NOT NULL。保证列中所有的数据不能有null值。

唯一约束:关键字是  UNIQUE。保证列中所有数据各不相同。

主键约束: 关键字是  PRIMARY KEY。主键是一行数据的唯一标识,要求非空且唯一。一般我们都会给每张表添加一个主键列用来唯一标识数据。

检查约束: 关键字是  CHECK。 保证列中的值满足某一条件。MySQL不支持检查约束。

默认约束: 关键字是   DEFAULT。保存数据时,未指定值则采用默认值。

外键约束: 关键字是  FOREIGN KEY。外键用来让两个表的数据之间建立链接,保证数据的一致性和完整性。

非空约束:

-- 创建表时添加非空约束
CREATE TABLE 表名(
   列名 数据类型 NOT NULL,
   …
); 

-- 建完表后添加非空约束
ALTER TABLE 表名 MODIFY 字段名 数据类型 NOT NULL;

-- 删除约束
ALTER TABLE 表名 MODIFY 字段名 数据类型;

唯一约束:

-- 创建表时添加唯一约束
CREATE TABLE 表名(
   列名 数据类型 UNIQUE [AUTO_INCREMENT],
   -- AUTO_INCREMENT: 当不指定值时自动增长
   …
); 
CREATE TABLE 表名(
   列名 数据类型,
   …
   [CONSTRAINT] [约束名称] UNIQUE(列名)
); 

-- 建完表后添加唯一约束
ALTER TABLE 表名 MODIFY 字段名 数据类型 UNIQUE;

--删除约束
ALTER TABLE 表名 DROP INDEX 字段名;

主键约束:

-- 创建表时添加主键约束
CREATE TABLE 表名(
   列名 数据类型 PRIMARY KEY [AUTO_INCREMENT],
   …
); 
CREATE TABLE 表名(
   列名 数据类型,
   [CONSTRAINT] [约束名称] PRIMARY KEY(列名)
); 

-- 建完表后添加主键约束
ALTER TABLE 表名 ADD PRIMARY KEY(字段名);

-- 删除约束
ALTER TABLE 表名 DROP PRIMARY KEY;

默认约束:

-- 创建表时添加默认约束
CREATE TABLE 表名(
   列名 数据类型 DEFAULT 默认值,
   …
); 

-- 建完表后添加默认约束
ALTER TABLE 表名 ALTER 列名 SET DEFAULT 默认值;

-- 删除约束
ALTER TABLE 表名 ALTER 列名 DROP DEFAULT;

约束练习:

CREATE TABLE emp (
  id INT PRIMARY KEY auto_increment, -- 员工id,主键且自增长
  ename VARCHAR(50) NOT NULL UNIQUE, -- 员工姓名,非空并且唯一
  joindate DATE NOT NULL , -- 入职日期,非空
  salary DOUBLE(7,2) NOT NULL , -- 工资,非空
  bonus DOUBLE(7,2) DEFAULT 0 -- 奖金,如果没有奖金默认为0
);

默认约束只有在不给值时才会采用默认值。如果给了null,那值就是null值。

验证自动增长: auto_increment 当列是数字类型 并且唯一约束

外键约束:

-- 创建表时添加外键约束
CREATE TABLE 表名(
   列名 数据类型,
   …
   [CONSTRAINT] [外键名称] FOREIGN KEY(外键列名) REFERENCES 主表(主表列名) 
); 

-- 建完表后添加外键约束
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名称) REFERENCES 主表名称(主表列名称);

-- 删除外键约束
ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;

员工表和部门表,并添加上外键约束:

-- 部门表
CREATE TABLE dept(
    id int primary key auto_increment,
    dep_name varchar(20),
    addr varchar(20)
);
-- 员工表 
CREATE TABLE emp(
    id int primary key auto_increment,
    name varchar(20),
    age int,
    dep_id int,

    -- 添加外键 dep_id,关联 dept 表的id主键
    CONSTRAINT fk_emp_dept FOREIGN KEY(dep_id) REFERENCES dept(id)	
);

-- 删除外键
alter table emp drop FOREIGN key fk_emp_dept;

-- 重新添加外键
alter table emp add CONSTRAINT fk_emp_dept FOREIGN key(dep_id) REFERENCES dept(id);

数据库设计


数据库设计:建立数据库中的表结构以及表与表之间的关联关系的过程。有哪些表?表里有哪些字段?表和表之间有什么关系?

表关系:一对一、一对多、多对多。

一对多的实现方式:在多的一方建立外键,指向一的一方的主键。

多对多的实现方式:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键。

一对一的实现方式:在任意一方加入外键,关联另一方主键,并且设置外键为唯一(UNIQUE)。


多表查询


多表查询都有哪些呢:

连接查询
    内连接查询 :相当于查询AB交集数据
    外连接查询
        左外连接查询 :相当于查询A表所有数据和交集部门数据
        右外连接查询 : 相当于查询B表所有数据和交集部分数据
子查询

内连接查询:

-- 隐式内连接
SELECT 字段列表 FROM 表1,表2… WHERE 条件;

-- 显示内连接
SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 条件;

隐式内连接:

SELECT
    *
FROM
    emp,
    dept
WHERE
    emp.dep_id = dept.did;

执行上述语句结果如下:

查询 emp的 name, gender,dept表的dname:

SELECT
    emp. NAME,
    emp.gender,
    dept.dname
FROM
    emp,
    dept
WHERE
    emp.dep_id = dept.did;

上面语句中使用表名指定字段所属有点麻烦,sql也支持给表指别名,上述语句可以改进为:

SELECT
    t1. NAME,
    t1.gender,
    t2.dname
FROM
    emp t1,
    dept t2
WHERE
    t1.dep_id = t2.did; 

显式内连接:

select * from emp inner join dept on emp.dep_id = dept.did;
-- 上面语句中的inner可以省略,可以书写为如下语句
select * from emp  join dept on emp.dep_id = dept.did;

外连接查询:

-- 左外连接:相当于查询A表所有数据和交集部分数据
SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件;

-- 右外连接:相当于查询B表所有数据和交集部分数据
SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件;

查询emp表所有数据和对应的部门信息(左外连接):

select * from emp left join dept on emp.dep_id = dept.did;
结果显示查询到了左表(emp)中所有的数据及两张表能关联的数据。

查询dept表所有数据和对应的员工信息(右外连接):

select * from emp right join dept on emp.dep_id = dept.did;
结果显示查询到了右表(dept)中所有的数据及两张表能关联的数据。

子查询:查询中嵌套查询,称嵌套查询为子查询。

需求:查询工资高于猪八戒的员工信息。来实现这个需求,我们就可以通过二步实现,

第一步:先查询出来 猪八戒的工资
select salary from emp where name = '猪八戒'

第二步:查询工资高于猪八戒的员工信息
select * from emp where salary > 3600;

查询语句中嵌套查询语句:

select * from emp where salary > (select salary from emp where name = '猪八戒');

子查询根据查询结果不同,作用不同:

子查询语句结果是单行单列,子查询语句作为条件值,使用 =  !=  >  <  等进行条件判断
子查询语句结果是多行单列,子查询语句作为条件值,使用 in 等关键字进行条件判断
子查询语句结果是多行多列,子查询语句作为虚拟表

查询 ‘财务部’ 和 ‘市场部’ 所有的员工信息:

-- 查询 '财务部' 或者 '市场部' 所有的员工的部门did
select did from dept where dname = '财务部' or dname = '市场部';
select * from emp where dep_id in (select did from dept where dname = '财务部' or dname = '市场部');

查询入职日期是 ‘2011-11-11’ 之后的员工信息和部门信息:

-- 查询入职日期是 '2011-11-11' 之后的员工信息
select * from emp where join_date > '2011-11-11' ;
-- 将上面语句的结果作为虚拟表和dept表进行内连接查询
select * from (select * from emp where join_date > '2011-11-11' ) t1, dept where t1.dep_id = dept.did;

事务


事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么同时成功,要么同时失败。事务是一个不可分割的工作逻辑单元。

语法:

# 开启事务
START TRANSACTION;
或者  
BEGIN;

# 提交事务
commit;

# 回滚事务
rollback;

事务的四大特征:

原子性(Atomicity): 事务是不可分割的最小操作单位,要么同时成功,要么同时失败

一致性(Consistency) :事务完成时,必须使所有的数据都保持一致状态

隔离性(Isolation) :多个事务之间,操作的可见性

持久性(Durability) :事务一旦提交或回滚,它对数据库中的数据的改变就是永久的

mysql中事务是自动提交的。也就是说我们不添加事务执行sql语句,语句执行完毕会自动的提交事务。

可以通过下面语句查询默认提交方式:

SELECT @@autocommit;

查询到的结果是1 则表示自动提交,结果是0表示手动提交。当然也可以通过下面语句修改提交方式

set @@autocommit = 0;

JDBC

JDBC概念


JDBC就是使用Java语言操作关系型数据库的一套API。全称:( Java DataBase Connectivity ) Java 数据库连接。

JDBC好处:各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发;可随时替换底层数据库,访问数据库的Java代码基本不变。


使用


//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "update account set money = 2000 where id = 1";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql);//受影响的行数
//6. 处理结果
System.out.println(count);
//7. 释放资源
stmt.close();
conn.close();

API详解


DriverManager(驱动管理类)作用:注册驱动 registerDriver ;获取数据库连接 getConnection

Connection(数据库连接对象)作用:获取执行 SQL 的对象;管理事务。

Connection管理事务:

/**
 * JDBC API 详解:Connection
 */
public class JDBCDemo3_Connection {

    public static void main(String[] args) throws Exception {
        //1. 注册驱动
        //Class.forName("com.mysql.jdbc.Driver");
        //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
        String url = "jdbc:mysql:///db1?useSSL=false";
        String username = "root";
        String password = "1234";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 定义sql
        String sql1 = "update account set money = 3000 where id = 1";
        String sql2 = "update account set money = 3000 where id = 2";
        //4. 获取执行sql的对象 Statement
        Statement stmt = conn.createStatement();
        try {
            // ============开启事务==========
            // autoCommit 表示是否自动提交事务,true表示自动提交事务,false表示手动提交事务。
            conn.setAutoCommit(false);
            //5. 执行sql
            int count1 = stmt.executeUpdate(sql1);//受影响的行数
            //6. 处理结果
            System.out.println(count1);
            int i = 3/0;
            //5. 执行sql
            int count2 = stmt.executeUpdate(sql2);//受影响的行数
            //6. 处理结果
            System.out.println(count2);
            // ============提交事务==========
            //程序运行到此处,说明没有出现任何问题,则需求提交事务
            conn.commit();
        } catch (Exception e) {
            // ============回滚事务==========
            //程序在出现异常时会执行到这个地方,此时就需要回滚事务
            conn.rollback();
            e.printStackTrace();
        }
        //7. 释放资源
        stmt.close();
        conn.close();
    }
}

Statement对象的作用就是用来执行SQL语句。

执行DML语句:

/**
* 执行DML语句
* @throws Exception
*/
@Test
public void testDML() throws  Exception {
  //1. 注册驱动
  //Class.forName("com.mysql.jdbc.Driver");
  //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
  String url = "jdbc:mysql:///db1?useSSL=false";
  String username = "root";
  String password = "1234";
  Connection conn = DriverManager.getConnection(url, username, password);
  //3. 定义sql
  String sql = "update account set money = 3000 where id = 1";
  //4. 获取执行sql的对象 Statement
  Statement stmt = conn.createStatement();
  //5. 执行sql
  int count = stmt.executeUpdate(sql);//执行完DML语句,受影响的行数
  //6. 处理结果
  //System.out.println(count);
  if(count > 0){
      System.out.println("修改成功~");
  }else{
      System.out.println("修改失败~");
  }
  //7. 释放资源
  stmt.close();
  conn.close();
}

执行DDL语句(执行完DDL语句,可能是0):

/**
* 执行DDL语句
* @throws Exception
*/
@Test
public void testDDL() throws  Exception {
  //1. 注册驱动
  //Class.forName("com.mysql.jdbc.Driver");
  //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
  String url = "jdbc:mysql:///db1?useSSL=false";
  String username = "root";
  String password = "1234";
  Connection conn = DriverManager.getConnection(url, username, password);
  //3. 定义sql
  String sql = "drop database db2";
  //4. 获取执行sql的对象 Statement
  Statement stmt = conn.createStatement();
  //5. 执行sql
  int count = stmt.executeUpdate(sql);//执行完DDL语句,可能是0
  //6. 处理结果
  System.out.println(count);

  //7. 释放资源
  stmt.close();
  conn.close();
}

ResultSet(结果集对象)作用:封装了SQL查询语句的结果。我们就需要从 ResultSet 对象中获取我们想要的数据。ResultSet 对象提供了操作查询结果数据的方法,如下:

boolean  next():将光标从当前位置向前移动一行;判断当前行是否为有效行

xxx  getXxx(参数):获取数据
xxx : 数据类型;如: int getInt(参数) ;String getString(参数)
参数:int类型的参数:列的编号,从1开始; String类型的参数: 列的名称 

执行DQL:

/**
  * 执行DQL
  * @throws Exception
  */
@Test
public void testResultSet() throws  Exception {
    //1. 注册驱动
    //Class.forName("com.mysql.jdbc.Driver");
    //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
    String url = "jdbc:mysql:///db1?useSSL=false";
    String username = "root";
    String password = "1234";
    Connection conn = DriverManager.getConnection(url, username, password);
    //3. 定义sql
    String sql = "select * from account";
    //4. 获取statement对象
    Statement stmt = conn.createStatement();
    //5. 执行sql
    ResultSet rs = stmt.executeQuery(sql);
    //6. 处理结果, 遍历rs中的所有数据
    /* // 6.1 光标向下移动一行,并且判断当前行是否有数据
        while (rs.next()){
            //6.2 获取数据  getXxx()
            int id = rs.getInt(1);
            String name = rs.getString(2);
            double money = rs.getDouble(3);

            System.out.println(id);
            System.out.println(name);
            System.out.println(money);
            System.out.println("--------------");

        }*/
    // 6.1 光标向下移动一行,并且判断当前行是否有数据
    while (rs.next()){
        //6.2 获取数据  getXxx()
        int id = rs.getInt("id");
        String name = rs.getString("name");
        double money = rs.getDouble("money");

        System.out.println(id);
        System.out.println(name);
        System.out.println(money);
        System.out.println("--------------");
    }

    //7. 释放资源
    rs.close();
    stmt.close();
    conn.close();
}

PreparedStatement


代码模拟SQL注入问题:

@Test
public void testLogin() throws  Exception {
    //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
    String url = "jdbc:mysql:///db1?useSSL=false";
    String username = "root";
    String password = "1234";
    Connection conn = DriverManager.getConnection(url, username, password);

    // 接收用户输入 用户名和密码
    String name = "sjdljfld";
    String pwd = "' or '1' = '1";
    String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'";
    // 获取stmt对象
    Statement stmt = conn.createStatement();
    // 执行sql
    ResultSet rs = stmt.executeQuery(sql);
    // 判断登录是否成功
    if(rs.next()){
        System.out.println("登录成功~");
    }else{
        System.out.println("登录失败~");
    }

    //7. 释放资源
    rs.close();
    stmt.close();
    conn.close();
}

上面代码是将用户名和密码拼接到sql语句中,拼接后的sql语句如下

select * from tb_user where username = 'sjdljfld' and password = ''or '1' = '1'

从上面语句可以看出条件 username = 'sjdljfld' and password = '' 不管是否满足,而 or 后面的 '1' = '1' 是始终满足的,最终条件是成立的,就可以正常的进行登陆了。

接下来我们来学习PreparedStatement对象.

PreparedStatement作用:预编译SQL语句并执行:预防SQL注入问题

获取 PreparedStatement 对象:

// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and password = ?";
// 通过Connection对象获取,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);

上面的sql语句中参数使用 ? 进行占位,在之前之前肯定要设置这些 ? 的值。

PreparedStatement对象:setXxx(参数1,参数2):给 ? 赋值
Xxx:数据类型 ; 如 setInt (参数1,参数2)
参数:
参数1: ?的位置编号,从1 开始
参数2: ?的值

执行SQL语句:

executeUpdate();  执行DDL语句和DML语句
executeQuery();  执行DQL语句
注意:调用这两个方法时不需要传递SQL语句,因为获取SQL语句执行对象时已经对SQL语句进行预编译了。

使用PreparedStatement改进:

@Test
public void testPreparedStatement() throws  Exception {
    //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
    String url = "jdbc:mysql:///db1?useSSL=false";
    String username = "root";
    String password = "1234";
    Connection conn = DriverManager.getConnection(url, username, password);

    // 接收用户输入 用户名和密码
    String name = "zhangsan";
    String pwd = "' or '1' = '1";

    // 定义sql
    String sql = "select * from tb_user where username = ? and password = ?";
    // 获取pstmt对象
    PreparedStatement pstmt = conn.prepareStatement(sql);
    // 设置?的值
    pstmt.setString(1,name);
    pstmt.setString(2,pwd);
    // 执行sql
    ResultSet rs = pstmt.executeQuery();
    // 判断登录是否成功
    if(rs.next()){
        System.out.println("登录成功~");
    }else{
        System.out.println("登录失败~");
    }
    //7. 释放资源
    rs.close();
    pstmt.close();
    conn.close();
}

执行上面语句就可以发现不会出现SQL注入漏洞问题了。那么PreparedStatement又是如何解决的呢?它是将特殊字符进行了转义,转义的SQL如下:

select * from tb_user where username = 'sjdljfld' and password = '\'or \'1\' = \'1'

PreparedStatement 好处:预编译SQL,性能更高;防止SQL注入:==将敏感字符进行转义==.

Java代码操作数据库流程:将sql语句发送到MySQL服务器端;MySQL服务端会对sql语句进行如下操作:检查SQL语句的语法是否正确,将SQL语句编译成可执行的函数,执行SQL语句.

PreparedStatement原理:在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时);执行时就不用再进行这些步骤了,速度更快;如果sql模板一样,则只需要进行一次检查、编译。


数据库连接池


数据库连接池是个容器,负责分配、管理数据库连接(Connection)。它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。

好处:资源重用,提升系统响应速度,避免数据库连接遗漏。

之前我们代码中使用连接是没有使用都创建一个Connection对象,使用完毕就会将其销毁。这样重复创建销毁的过程是特别耗费计算机的性能的及消耗时间的。而数据库使用了数据库连接池后,就能达到Connection对象的复用。

连接池是在一开始就创建好了一些连接(Connection)对象存储起来。用户需要连接数据库时,不需要自己创建连接,而只需要从连接池中获取一个连接进行使用,使用完毕后再将连接对象归还给连接池;这样就可以起到资源重用,也节省了频繁创建连接销毁连接所花费的时间,从而提升了系统响应的速度。

数据库连接池实现:以后就不需要通过 DriverManager 对象获取 Connection 对象,而是通过连接池(DataSource)获取 Connection 对象。

常见的数据库连接池:DBCP、C3P0、Druid。

Druid(德鲁伊):Druid连接池是阿里巴巴开源的数据库连接池项目。功能强大,性能优秀,是Java语言最好的数据库连接池之一。

Driud使用

导入jar包 druid-1.1.12.jar

定义配置文件
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
    username=root
    password=1234
    # 初始化连接数量
    initialSize=5
    # 最大连接数
    maxActive=10
    # 最大等待时间
    maxWait=3000

加载配置文件
获取数据库连接池对象
获取连接

使用druid的代码如下:

/**
 * Druid数据库连接池演示
 */
public class DruidDemo {

    public static void main(String[] args) throws Exception {
        //1.导入jar包
        //2.定义配置文件
        //3. 加载配置文件
        Properties prop = new Properties();
        prop.load(new FileInputStream("jdbc-demo/src/druid.properties"));
        //4. 获取连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);

        //5. 获取数据库连接 Connection
        Connection connection = dataSource.getConnection();
        System.out.println(connection); //获取到了连接后就可以继续做其他操作了

        //System.out.println(System.getProperty("user.dir"));
    }
}

druid配置详解:

属性 说明 建议值
url 数据库的jdbc连接地址。一般为连接oracle/mysql。示例如下:
mysql : jdbc:mysql://ip:port/dbname?option1&option2&…
oracle : jdbc:oracle:thin:@ip:port:oracle_sid
username 登录数据库的用户名
password 登录数据库的用户密码
initialSize 启动程序时,在连接池中初始化多少个连接 10-50已足够
maxActive 连接池中最多支持多少个活动会话
maxWait 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池 100
没有可用连接,单位毫秒,设置-1时表示无限等待
minEvictableIdleTimeMillis 池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将 见说明部分
回收该连接,要小于防火墙超时设置
net.netfilter.nf_conntrack_tcp_timeout_established的设置
timeBetweenEvictionRunsMillis 检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查
keepAlive 程序没有close连接且空闲时长超过 minEvictableIdleTimeMillis,则会执 true
行validationQuery指定的SQL,以保证该程序连接不会池kill掉,其范围不超
过minIdle指定的连接个数。
minIdle 回收空闲连接时,将保证至少有minIdle个连接. 与initialSize相同
removeAbandoned 要求程序从池中get到连接后, N 秒后必须close,否则druid 会强制回收该 false,当发现程序有未
连接,不管该连接中是活动还是空闲, 以防止进程不会进行close而霸占连接。 正常close连接时设置为true
removeAbandonedTimeout 设置druid 强制回收连接的时限,当程序从池中get到连接开始算起,超过此 应大于业务运行最长时间
值后,druid将强制回收该连接,单位秒。
logAbandoned 当druid强制回收连接后,是否将stack trace 记录到日志中 true
testWhileIdle 当程序请求连接,池在分配连接时,是否先检查该连接是否有效。(高效) true
validationQuery 检查池中的连接是否仍可用的 SQL 语句,drui会连接到数据库执行该SQL, 如果
正常返回,则表示连接可用,否则表示连接不可用
testOnBorrow 程序 申请 连接时,进行连接有效性检查(低效,影响性能) false
testOnReturn 程序 返还 连接时,进行连接有效性检查(低效,影响性能) false
poolPreparedStatements 缓存通过以下两个方法发起的SQL: true
public PreparedStatement prepareStatement(String sql)
public PreparedStatement prepareStatement(String sql,
int resultSetType, int resultSetConcurrency)
maxPoolPrepareStatementPerConnectionSize 每个连接最多缓存多少个SQL 20
filters 这里配置的是插件,常用的插件有: stat,wall,slf4j
监控统计: filter:stat
日志监控: filter:log4j 或者 slf4j
防御SQL注入: filter:wall
connectProperties 连接属性。比如设置一些连接池统计方面的配置。
druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
比如设置一些数据库连接属性:

maven

介绍


maven 是一个项目管理工具,主要作用是在项目开发阶段对Java项目进行依赖管理和项目构建。

依赖管理:就是对jar包的管理。通过导入maven坐标,就相当于将仓库中的jar包导入了当前项目中。

项目构建:通过maven的一个命令就可以完成项目从清理、编译、测试、报告、打包,部署整个过程。

maven常用命令:clean(清理)、compile(编译)、test(测试)、package(打包)、install(安装)。

maven中央仓库:https://repo1.maven.org/maven2/


配置


解压 apache-maven-3.6.1.rar 既安装完成

配置环境变量 MAVEN_HOME 为安装路径的bin目录

配置本地仓库:修改 conf/settings.xml 中的 为一个指定目录

配置阿里云私服:修改 conf/settings.xml 中的 标签,为其添加如下子标签:

<mirror>  
    <id>alimaven</id>  
    <name>aliyun maven</name>  
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>          
</mirror>

如果不改本地仓库,默认下载到本地目录:

C:\Users\Admin\.m2\repository

检查配置版本:

mvn -version

常用命令


Maven 常用命令:

mvn compile :编译
mvn clean:清理
mvn test:测试
mvn package:打包
mvn install:安装

生命周期


Maven 构建项目生命周期描述的是一次构建过程经历经历了多少个事件。

Maven 对项目构建的生命周期划分为3套:

clean:清理工作
default:核心工作,例如编译,测试,打包,安装等
site:产生报告,发布站点等

同一生命周期内,执行后边的命令,前边的所有命令会自动执行


IDEA 配置 Maven


配置 Maven:

选择 IDEA中 File --> Settings
搜索 maven 
设置 IDEA 使用本地安装的 Maven,并修改配置文件路径。即maven home path 和 User settings file

IDEA 导入 Maven 项目:

选择右侧Maven面板,点击 + 号
选中对应项目的pom.xml文件,双击即可
如果没有Maven面板,选择 View -> Appearance -> Tool Window Bars

Maven Helper插件:

Plugins里面搜索 Maven,选择第一个 Maven Helper,点击Install安装,弹出面板中点击Accept

maven坐标


Maven 中的坐标是资源的唯一标识。使用坐标来定义项目或引入项目中需要的依赖

Maven 坐标主要组成:

groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:com.tea)
artifactId:定义当前Maven项目名称(通常是模块名称,例如 order-service、goods-service)
version:定义当前项目版本号

使用坐标导入 jar 包:

在 pom.xml 中编写 <dependencies> 标签
在 <dependencies> 标签中 使用 <dependency> 引入坐标
定义坐标的 groupId,artifactId,version
点击刷新按钮,使坐标生效

使用坐标导入 jar 包 – 快捷方式:

在 pom.xml 中 按 alt + insert,选择 Dependency
在弹出的面板中搜索对应坐标,然后双击选中对应坐标
点击刷新按钮,使坐标生效

使用坐标导入 jar 包 – 自动导入:

选择 IDEA中 File --> Settings
在弹出的面板中找到 Build Tools
选择 Any changes,点击 ok 即可生效

maven的依赖范围


maven的依赖范围:

依赖范围 对于编译classpath有效 对于测试classpath有效 对于运行时classpath有效 例子
compile Y Y Y spring-core
test - Y - Junit
provided Y Y - servlet-api
runtime - Y Y JDBC驱动
system Y Y - 本地的,maven仓库之外的类库

通过scope指定依赖范围,默认是compile

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>compile</scope>
    </dependency>

maven的依赖冲突


排除依赖:可以使用exclusions标签将传递过来的依赖排除出去。

<exclusions>
    <exclusion>
    ....
    </exclusion>
</exclusions>

版本锁定:采用直接锁定版本的方法确定依赖jar包的版本,版本锁定后则不考虑依赖的声明顺序或依赖的路径,以锁定的版本为准添加到工程中,此方法在企业开发中经常使用。

版本锁定的使用方式:第一步:在dependencyManagement标签中锁定依赖的版本;第二步:在dependencies标签中声明需要导入的maven坐标。

<!--锁定jar包的版本-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
    </dependency>

</dependencies>

maven分模块构建


分模块父工程的打包方式必须为pom:

<!--父工程的打包方式必须为pom-->
<packaging>pom</packaging>

子工程:

<!--表示当前maven工程继承了maven_parent父工程-->
<parent>
    <artifactId>maven_parent</artifactId>
    <groupId>com.study</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>

当前子工程依赖 子工程maven_dao:

<dependencies>
    <dependency>
        <groupId>com.study</groupId>
        <artifactId>maven_dao</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

maven工程的聚合:在maven工程的pom.xml文件中可以使用<modules>标签将其他maven工程聚合到一起,聚合的目的是为了进行统一操作。例如拆分后的maven工程有多个,如果要进行打包,就需要针对每个工程分别执行打包命令,操作起来非常繁琐。这时就可以使用<modules>标签将这些工程统一聚合到maven工程中,需要打包的时候,只需要在此工程中执行一次打包命令,其下被聚合的工程就都会被打包了。


Mybatis

介绍


官网:https://mybatis.org/mybatis-3/zh/index.html

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

JavaEE三层架构:表现层、业务层、持久层。持久层:负责将数据到保存到数据库的那一层代码。

MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。


入门


创建模块,导入坐标:

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.5</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- 添加slf4j日志api -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.25</version>
    </dependency>
    <!-- 添加logback-classic依赖 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
    <!-- 添加logback-core依赖 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>

编写 MyBatis 核心配置文件:resources/mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="******"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

编写 SQL 映射文件:resources/UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace:名称空间-->
<mapper namespace="test">
    <!--statement-->
    <select id="selectAll" resultType="com.tea.pojo.User">
        select *
        from tb_user;
    </select>
</mapper>

编码:

public static void main(String[] args) throws IOException {
    //1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    //2. 获取SqlSession对象,用它来执行sql
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //3. 执行sql
    List<User> users = sqlSession.selectList("test.selectAll");
    System.out.println(users);
    //4. 释放资源
    sqlSession.close();
}

Idea连接数据库


Idea右边栏点击Database,点击加号+,输入用户名,密码,数据库,建立连接。


Mapper代理开发


定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下:

在java目录下,新建com.tea.mapper.UserMapper接口
在resources目录下,新建com/tea/mapper目录,UserMapper.xml文件。注意用斜杠。

设置SQL映射文件的namespace属性为Mapper接口全限定名:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace:名称空间-->
<mapper namespace="com.tea.mapper.UserMapper">
    <!--statement-->
    <select id="selectAll" resultType="com.tea.pojo.User">
        select *
        from tb_user;
    </select>
</mapper>

在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致:

public interface UserMapper {
    List<User> selectAll();
}

配置mybatis-config.xml:

<mappers>
    <mapper resource="com/tea/mapper/UserMapper.xml"/>
</mappers>

编码:

public class MyBatisDemo2 {
    public static void main(String[] args) throws IOException {
        //1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //2. 获取SqlSession对象,用它来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3. 执行sql
        //List<User> users = sqlSession.selectList("test.selectAll");
        //3.1 获取UserMapper接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.selectAll();

        System.out.println(users);
        //4. 释放资源
        sqlSession.close();
    }
}

细节:如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载:

<mappers>
    <!--<mapper resource="com/tea/mapper/UserMapper.xml"/>-->
    <!--mapper代理-->
    <package name="com.tea.mapper"/>
</mappers>

核心配置文件


environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment。

typeAliases-别名:

mybatis-config.xml内:
<typeAliases>
    <package name="com.tea.pojo"/>
</typeAliases>

UserMapper.xml内:
可以不用	resultType="com.tea.pojo.User"
可以直接使用	resultType="User"

mybatis-config.xml中配置各个标签时,需要遵守标签前后顺序。


MyBatisX插件


MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。

主要功能:XML 和 接口方法 相互跳转;根据接口方法生成 statement。


查询


查询所有:

//编写接口方法: Mapper接口:(参数:无,结果:List<Brand>)
public interface BrandMapper {
    public List<Brand> selectAll();
}

//编写 SQL语句: SQL映射文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace:名称空间-->
<mapper namespace="com.tea.mapper.BrandMapper">
    <select id="selectAll" resultType="brand">
        select *
        from tb_brand;
    </select>
</mapper>

//执行方法,测试
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSession sqlSession = sqlSessionFactory.openSession();

BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
List<Brand> brands = brandMapper.selectAll();
System.out.println(brands);
sqlSession.close();

数据库表的字段名称和实体类的属性名称不一样时,解决办法:

//方法1、xml中用别名
<select id="selectAll" resultType="brand">
    select id, brand_name as brandName, company_name as companyName, ordered, description, status
    from tb_brand;
</select>

//方法2、sql片段抽取
<sql id="brand_column">
    id, brand_name as brandName, company_name as companyName, ordered, description, status
</sql>
<select id="selectAll" resultType="brand">
    select
    <include refid="brand_column" />
    from tb_brand;
</select>

//方法3、resultMap
<!--id:唯一标识、type:映射的类型,支持别名-->
<resultMap id="brandResultMap" type="brand">
    <!--id:完成主键字段的映射、column:表的列名、property:实体类的属性名
        result:完成一般字段的映射、column:表的列名、property:实体类的属性名-->
    <result column="brand_name" property="brandName"/>
    <result column="company_name" property="companyName"/>
</resultMap>
<select id="selectAll" resultMap="brandResultMap">
    select *
    from tb_brand;
</select>

根据Id查询:

//编写接口方法: Mapper接口(参数:id、结果:Brand)
public interface BrandMapper {
    Brand selectById(int id);
}

//编写 SQL语句: SQL映射文件
<select id="selectById" resultMap="brandResultMap">
    select *
    from tb_brand where id = #{id};
</select>

//执行方法,测试
int id = 1;
BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
Brand brand = brandMapper.selectById(id);
System.out.println(brand);
sqlSession.close();

参数占位符:

#{}:会将其替换为 ?,为了防止SQL注入
${}:拼sql。会存在SQL注入问题

使用时机:参数传递的时候使用#{};表名或者列名不固定的情况下使用${} 

参数类型:parameterType:可以省略。
特殊字符处理: 转义字符;CDATA区。比如<小于号,可以用 &lt; 或者 <![CDATA[<]]> 。

多条件查询:三种方式

散装参数:需要使用@Param("SQL中的参数名称")
实体类封装参数:只需要保证SQL中的参数名 和 实体类属性名对应上,即可设置成功
map集合:只需要保证SQL中的参数名 和 map集合的键的名称对应上,即可设置成功

//编写接口方法: Mapper接口(参数:所有查询条件、结果:List<Brand>)
//方式一:
List<Brand> selectByCondition(@Param("status")int status, @Param("companyName") String companyName, @Param("brandName") String brandName);
//方式二:
List<Brand> selectByCondition(Brand brand);
//方式三:
List<Brand> selectByCondition(Map map);

//编写 SQL语句: SQL映射文件
//方式一://方式二://方式三:
<select id="selectByCondition" resultMap="brandResultMap">
    select * from tb_brand where status = #{status} and company_name like #{companyName} and brand_name like #{brandName}
</select>

//执行方法,测试
//方式一:
int status = 1;
String companyName = "华为";
String brandName = "华为";
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";

List<Brand> brands = brandMapper.selectByCondition(status, companyName, brandName);
System.out.println(brands);
sqlSession.close();
//方式二:
int status = 1;
String companyName = "华为";
String brandName = "华为";
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);

List<Brand> brands = brandMapper.selectByCondition(brand);
System.out.println(brands);
sqlSession.close();
//方式三:
int status = 1;
String companyName = "华为";
String brandName = "华为";
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
Map map = new HashMap();
map.put("status", status);
map.put("companyName", companyName);
map.put("brandName", brandName);

List<Brand> brands = brandMapper.selectByCondition(map);
System.out.println(brands);
sqlSession.close();

多条件动态条件查询-动态SQL:

<select id="selectByCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    <where>
        <if test="status != null">
            and status = #{status}
        </if>
        <if test="companyName != null and companyName != '' ">
            and company_name like #{companyName}
        </if>
        <if test="brandName != null and brandName != '' ">
            and brand_name like #{brandName}
        </if>
    </where>

</select>

单条件动态条件查询:

<select id="selectByConditionSingle" resultMap="brandResultMap">
    select * from tb_brand
    where    
    <choose>  <!--类似于switch-->
        <when test="status != null">  <!--类似于case-->
            status = #{status}
        </when>
        <when test="companyName != null and companyName !=''">
            company_name like #{companyName}
        </when>
        <when test="brandName != null and brandName !='' ">
            brand_name like #{brandName}
        </when>
        <otherwise>  <!--类似于default-->
            1 = 1
        </otherwise>
    </choose>
</select>

或者
<select id="selectByConditionSingle" resultMap="brandResultMap">
    select *
    from tb_brand
    <where>
        <choose><!--相当于switch-->
            <when test="status != null"><!--相当于case-->
                status = #{status}
            </when>
            <when test="companyName != null and companyName != '' "><!--相当于case-->
                company_name like #{companyName}
            </when>
            <when test="brandName != null and brandName != ''"><!--相当于case-->
                brand_name like #{brandName}
            </when>
        </choose>
    </where>
</select>

添加


MyBatis事务:

openSession():默认开启事务,进行增删改操作后需要使用 sqlSession.commit(); 手动提交事务
openSession(true):可以设置为自动提交事务(关闭事务)

添加:

//编写接口方法: Mapper接口(参数:除了id之外的所有数据,结果:void)
public interface BrandMapper {
    void add(Brand brand);
}	

//编写 SQL语句: SQL映射文件
<insert id="add">
    insert into tb_brand (brand_name, company_name, ordered, description, status)
    values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>

//执行方法,测试
int status = 1;
String companyName = "三星";
String brandName = "三星";
String des = "三星三星三星三星三星三星三星三星";
int ordered = 100;
Brand brand = new Brand();
brand.setStatus(status);
brand.setCompanyName(companyName);
brand.setBrandName(brandName);
brand.setDescription(des);
brand.setOrdered(ordered);

SqlSession sqlSession = sqlSessionFactory.openSession(false);

BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
brandMapper.add(brand);
sqlSession.commit();
sqlSession.close();

添加-主键返回:useGeneratedKeys和keyProperty

<insert id="add" useGeneratedKeys="true" keyProperty="id">
    insert into tb_brand (brand_name, company_name, ordered, description, status)
    values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>

修改


修改全部字段:

<update id="update">
    update tb_brand
    set
        brand_name = #{brandName},
        company_name = #{companyName},
        ordered = #{ordered},
        description = #{description},
        status = #{status}
    where id = #{id};
</update>

修改动态字段:

<update id="update">
    update tb_brand
    <set>
        <if test="brandName != null and brandName != ''">
            brand_name = #{brandName},
        </if>
        <if test="companyName != null and companyName != ''">
            company_name = #{companyName},
        </if>
        <if test="ordered != null">
            ordered = #{ordered},
        </if>
        <if test="description != null and description != ''">
            description = #{description},
        </if>
        <if test="status != null">
            status = #{status}
        </if>
    </set>
    where id = #{id};
</update>

注意逗号。


删除


删除一个:

<delete id="deleteById">
    delete from tb_brand where id = #{id};
</delete>

批量删除:

mybatis会将数组参数,封装为一个Map集合。默认:array = 数组。使用@Param注解改变map集合的默认key的名称。

//void deleteByIds(@Param("ids") int[] ids);	collection="ids"
void deleteByIds(int[] ids);

<delete id="deleteByIds">
    delete from tb_brand where id
    in
        <foreach collection="array" item="id" separator="," open="("   close=")">
            #{id}
        </foreach>
         ;
</delete>

参数传递


Mybatis 接口方法中可以接收各种各样的参数,如下:

单个参数:
    POJO类型:直接使用,属性名 和 参数占位符名称 一致
    Map集合:直接使用,键名 和 参数占位符名称 一致
    Collection:封装为Map集合,可以使用@Param注解,替换Map集合中默认的arg键名
        map.put("arg0",collection集合);
        map.put("collection",collection集合);
    List:封装为Map集合,可以使用@Param注解,替换Map集合中默认的arg键名
        map.put("arg0",list集合);
        map.put("collection",list集合);
        map.put("list",list集合);
    Array:封装为Map集合,可以使用@Param注解,替换Map集合中默认的arg键名
        map.put("arg0",数组);
        map.put("array",数组);
    其他类型:直接使用
多个参数:封装为Map集合,可以使用@Param注解,替换Map集合中默认的arg键名
    map.put("arg0",参数值1)
    map.put("param1",参数值1)
    map.put("param2",参数值2)
    map.put("agr1",参数值2)
    ---------------@Param("username")
    map.put("username",参数值1)
    map.put("param1",参数值1)
    map.put("param2",参数值2)
    map.put("agr1",参数值2)

Mybatis提供了ParamNameResolver类来进行参数封装。


注解开发


使用注解开发会比配置文件开发更加方便。

@Select(value = "select * from tb_user where id = #{id}")
public User select(int id);

注解完成简单功能;配置文件完成复杂功能。

查询:@Select
添加:@Insert
修改:@Update
删除:@Delete

参数设置:

@Select(value = "select * from tb_user where id = #{id} and username = #{username}")
public User select(@Param("id") int id, @Param("username") String username);

结果集映射:

@Select(value = "select * from tb_user where id = #{id} and username = #{username}")
@ResultMap("userResultMap")
public User select(@Param("id") int id, @Param("username") String username);

SqlSessionFactory工具类


public class SqlSessionFactoryUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        //静态代码块会随着类的加载而自动执行,且只执行一次
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSessionFactory getSqlSessionFactory(){
        return sqlSessionFactory;
    }
}

Tomcat

配置


官网:https://tomcat.apache.org/

解决中文乱码,修改配置 conf/logging.properties :

java.util.logging.ConsoleHandler.encoding = GBK

修改启动端口号:conf/server.xml :

<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

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


Web项目


Maven Web项目结构

和java同级下有webapp目录,包含html文件夹和WEB-INF文件夹

编译后的Java字节码文件和resources的资源文件,放到WEB-INF下的classes目录下。pom.xml中依赖坐标对应的jar包,放入WEB-INF下的lib目录下.

使用骨架

创建Maven项目:

勾选Create from archetype
选择maven-archetype-webapp

删除pom.xml中多余内容:

<build>
    <finalName>tomcatdemo</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>

补齐Maven Web项目缺失的目录结构:

java
resource

IDEA使用Tomcat


Maven Web项目创建成功后,通过Maven的package命令可以将项目打包成war包,将war文件拷贝到Tomcat的webapps目录下,启动Tomcat就可以将项目部署成功,然后通过浏览器进行访问即可。

然而我们在开发的过程中,项目中的内容会经常发生变化,如果按照上面这种方式来部署测试,是非常不方便的,如何在IDEA中能快速使用Tomcat呢?

在IDEA中集成使用Tomcat有两种方式,分别是集成本地Tomcat和Tomcat Maven插件。

集成本地Tomcat:

Edit Configurations -> 加号 -> Tomcat Server -> Local -> Configure 

配置TomEE Home:apache-tomcat-8.5.68

Deployment -> 加号 -> Artifat 配置war

Tomcat Maven 插件:只需要两步,分别是:

在pom.xml中添加Tomcat插件
<build>
   <plugins>
   	<!--Tomcat插件 -->
       <plugin>
           <groupId>org.apache.tomcat.maven</groupId>
           <artifactId>tomcat7-maven-plugin</artifactId>
           <version>2.2</version>
       </plugin>
   </plugins>
</build>

使用Maven Helper插件快速启动项目,选中项目,右键-->Run Maven --> tomcat7:run

在IDEA中下载Maven Helper插件,具体的操作方式为: File –> Settings –> Plugins –> Maven Helper —> Install,安装完后按照提示重启IDEA,就可以看到了。

Maven Tomcat插件目前只有Tomcat7版本,没有更高的版本可以使用。

使用Maven Tomcat插件,要想修改Tomcat的端口和访问路径,可以直接修改pom.xml:

<build>
    <plugins>
    	<!--Tomcat插件 -->
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
            	<port>80</port><!--访问端口号 -->
                <!--项目访问路径
                    未配置访问路径: http://localhost:80/tomcat-demo2/a.html
                    配置/后访问路径: http://localhost:80/a.html
                    如果配置成 /hello,访问路径会变成什么?
                        答案: http://localhost:80/hello/a.html
                -->
                <path>/</path>
            </configuration>
        </plugin>
    </plugins>
</build>

Servlet

介绍


Servlet 是 Java提供的一门动态web资源开发技术。

Servlet 是JavaEE 规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet。


Servlet快速入门


创建 web项目,导入 Servlet依赖坐标:

<dependencies>
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>
</dependencies>

创建:定义一个类,实现 Servlet接口,并重写接口中所有方法,并在 service方法中输入一句话:

public class ServletDemo1 implements Servlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("servlet hello vorld");
    }
}

配置:在类上使用@WebServlet 注解,配置该 Servlet的访问路径:

@WebServlet("/demo1")
public class ServletDemo1 implements Servlet {

访问:启动 Tomcat,浏览器输入URL 访问该Servlet

http://localhost:8080/web-demo/demo1

Servlet执行流程和生命周期


Servlet 由谁创建?Servlet方法由谁调用?

Servlet由web服务器创建,Servlet方法由web服务器调用。

服务器怎么知道Servlet中一定有service方法?

因为我们自定义的Servlet,必须实现Servlet接口并复写其方法,而Servlet接口中有service方法。

Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:

加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象
初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法**只调用一次**
请求处理:**每次请求**Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。
服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收

loadOnstartup:

@WebServlet(urlPatterns = "/demo1",loadOnStartup = 1)
loadOnstartup的取值有两类情况
    (1)负整数:第一次访问时创建Servlet对象
    (2)0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高

Servlet 方法介绍:

//初始化方法,在Servlet被创建时执行,只执行一次
void init(ServletConfig config)

//提供服务方法, 每次Servlet被访问,都会调用该方法
void service(ServletRequest req, ServletResponse res) 

//销毁方法,当Servlet被销毁时,调用该方法。在内存释放或服务器关闭时销毁
void destroy()

//获取ServletConfig对象
ServletConfig getServletConfig()

//获取Servlet信息
String getServletInfo() 

HttpServlet


HttpServlet:对HTTP协议封装的Servlet实现类。

@WebServlet("/demo4")
public class ServletDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("get...");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post...");
    }
}

自己封装请求方式:

public class MyHttpServlet implements Servlet {
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        // 根据请求方式的不同,进行分别的处理
        HttpServletRequest request = (HttpServletRequest) req;
        //1. 获取请求方式
        String method = request.getMethod();
        //2. 判断
        if("GET".equals(method)){
            // get方式的处理逻辑
            doGet(req,res);
        }else if("POST".equals(method)){
            // post方式的处理逻辑
            doPost(req,res);
        }
    }
}

urlPattern配置


一个Servlet,可以配置多个 urlPattern:

@WebServlet(urlPatterns = {"/demo7","/demo8"})

urlPattern 配置规则:

//精确匹配
@WebServlet(urlPatterns = {"/demo7"})

//目录匹配-通配符
@WebServlet(urlPatterns = "/user/*")

//扩展名匹配-注意没有斜杠
@WebServlet(urlPatterns = "*.do")

//任意匹配
@WebServlet(urlPatterns = "/")
@WebServlet(urlPatterns = "/*")

//* 区别:当我们的项目中的Servlet配置了“/”,会覆盖掉tomcat中的DefaultServlet,当其他的 url-pattern都匹配不上时都会走这个Servlet。当我们的项目中配置了“/*”,意味着匹配任意访问路径

优先级:

   精确路径 > 目录路径 > 扩展名路径 > /* > /

XML配置方式编写Servlet


Servlet 从3.0版本后开始支持使用注解配置,3.0版本前只支持 XML 配置文件的配置方式。

在 main/webapp/WEB-INF/web.xml中配置该Servlet:

<!--Servlet 全类名 -->
<servlet>
    <servlet-name>demo13</servlet-name>
    <servlet-class>com.tea.web.ServletDemo13</servlet-class>
</servlet>

<!--Servlet 访问路径-->
<servlet-mapping>
    <servlet-name>demo13</servlet-name>
    <url-pattern>/demo13</url-pattern>
</servlet-mapping>

Request

继承体系


ServletRequest(Java提供的请求对象根接口) -> HttpServletRequest(Java提供的对Http协议封装的请求对象接口
) -> RequestFacade(Tomcat 定义的实现类)


获取请求数据


请求行:

String getMethod():获取请求方式: GET
String getContextPath():获取虚拟目录(项目访问路径): /request-demo
StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/request-demo/req1
String getRequestURI():获取URI(统一资源标识符): /request-demo/req1
String getQueryString():获取请求参数(GET方式): username=zhangsan&password=123

请求头:

String getHeader(String name):根据请求头名称,获取值

请求体:

ServletInputStream  getInputStream():获取字节输入流
BufferedReader getReader():获取字符输入流

代码:

@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // String getMethod():获取请求方式: GET
        String method = req.getMethod();
        System.out.println(method);//GET
        // String getContextPath():获取虚拟目录(项目访问路径):/request-demo
        String contextPath = req.getContextPath();
        System.out.println(contextPath);
        // StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/request-demo/req1
        StringBuffer url = req.getRequestURL();
        System.out.println(url.toString());
        // String getRequestURI():获取URI(统一资源标识符): /request-demo/req1
        String uri = req.getRequestURI();
        System.out.println(uri);
        // String getQueryString():获取请求参数(GET方式): username=zhangsan
        String queryString = req.getQueryString();
        System.out.println(queryString);
        //------------
        // 获取请求头:user-agent: 浏览器的版本信息
        String agent = req.getHeader("user-agent");
        System.out.println(agent);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取post 请求体:请求参数
        //1. 获取字符输入流
        BufferedReader br = req.getReader();
        //2. 读取数据
        String line = br.readLine();
        System.out.println(line);
    }
}

Request 通用方式获取请求参数:

//三种方式:
Map<String, String[]> getParameterMap():获取所有参数Map集合
String[] getParameterValues(String name) :根据名称获取参数值(数组)
String getParameter(String name):根据名称获取参数值(单个值)

请求参数中文乱码


POST请求参数解决中文乱码:

request.setCharacterEncoding("UTF-8");//设置字符输入流的编码

GET请求参数解决中文乱码,原因:

// 乱码原因:tomcat进行URL解码,默认的字符集ISO-8859-1
//1. URL编码
String encode = URLEncoder.encode(username, "utf-8");
System.out.println(encode);
//2. URL解码
String decode = URLDecoder.decode(encode, "ISO-8859-1");
System.out.println(decode);

GET请求参数解决中文乱码,解决办法:

//获取username
String username = request.getParameter("username");
System.out.println("解决乱码前:"+username);
//先对乱码数据进行编码:转为字节数组。ISO_8859_1和UTF_8字节数组是一样的。
byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1);
//字节数组解码
username = new String(bytes, StandardCharsets.UTF_8);*/

Tomcat 8.0 之后,已将GET请求乱码问题解决,设置默认的解码方式为UTF-8。


Request请求转发


请求转发(forward):一种在服务器内部的资源跳转方式。

demo5转发到demo6:

//存储数据
request.setAttribute("msg","hello");
//请求转发
request.getRequestDispatcher("/req6").forward(request,response);

请求转发资源间共享数据:使用Request对象:

void setAttribute(String name, Object o):存储数据到 request域中
Object getAttribute(String name):根据 key,获取值
void removeAttribute(String name):根据 key,删除该键值对

请求转发特点:

浏览器地址栏路径不发生变化
只能转发到当前服务器的内部资源
一次请求,可以在转发的资源间使用request共享数据

Response

继承体系


ServletResponse(Java提供的请求对象根接口) -> HttpServletResponse(Java提供的对Http协议封装的请求对象接口
) -> ResponseFacade(Tomcat 定义的实现类)


设置响应数据


响应行:

void setStatus(int sc) :设置响应状态

响应头:

void setHeader(String name, String value) :设置响应头键值对

响应体:

PrintWriter getWriter():获取字符输出流
ServletOutputStream getOutputStream():获取字节输出流

重定向


实现方式:

//方式一:
resp.setStatus(302);
resp.setHeader(“location”,“资源B的路径");

//方式二:
resp.sendRedirect("资源B的路径");

重定向特点:

浏览器地址栏路径发生变化
可以重定向到任意位置的资源(服务器内部、外部均可)
两次请求,不能在多个资源使用request共享数据

可以和请求转发对比。

代码:

//重定向
//1.设置响应状态码 302
response.setStatus(302);
//2. 设置响应头 Location
response.setHeader("Location","/request-demo/resp2");

//简化方式完成重定向
//动态获取虚拟目录
String contextPath = request.getContextPath();
response.sendRedirect(contextPath+"/resp2");

response.sendRedirect("https://www.baidu.com");

资源路径


明确路径谁使用:

浏览器使用:需要加虚拟目录(项目访问路径)
服务端使用:不需要加虚拟目录,比如请求转发

动态获取虚拟目录:

String contextPath = request.getContextPath();

响应字符数据


//中文数据乱码:原因通过Response获取的字符输出流默认编码:ISO-8859-1
response.setContentType("text/html;charset=utf-8");
//1. 获取字符输出流
PrintWriter writer = response.getWriter();
//content-type	设置支持html的标签
//response.setHeader("content-type","text/html");

writer.write("你好");
writer.write("<h1>aaa</h1>");
//细节:流不需要关闭

响应字节数据


IOUtils工具类:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

IOUtils.copy(输入流,输出流);

响应字节数据:

//通过Response对象获取字节输出流
ServletOutputStream outputStream = resp.getOutputStream();
//写数据
outputStream.write(字节数据);

代码:

//1. 读取文件
FileInputStream fis = new FileInputStream("d://a.jpg");
//2. 获取response字节输出流
ServletOutputStream os = response.getOutputStream();
//3. 完成流的copy
/* byte[] buff = new byte[1024];
int len = 0;
while ((len = fis.read(buff))!= -1){
    os.write(buff,0,len);
}*/
IOUtils.copy(fis,os);
fis.close();

JSP

概念


JSP:Java Server Pages,Java服务端页面。一种动态的网页技术,其中既可以定义 HTML、JS、CSS等静态内容,还可以定义 Java代码的动态内容。

JSP = HTML + Java

JSP的作用:简化开发,避免了在Servlet中直接输出HTML标签。

缺点:

书写麻烦:特别是复杂的页面
阅读麻烦
复杂度高:运行需要依赖于各种环境,JRE,JSP容器,JavaEE…
占内存和磁盘:JSP会自动生成.java和.class文件占磁盘,运行的是.class文件占内存
调试困难:出错后,需要找到自动生成的.java文件进行调试
不利于团队协作:前端人员不会 Java,后端人员不精 HTML

快速入门


导入JSP坐标:

<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
    <scope>provided</scope>
</dependency>

创建JSP文件:

new -> jsp/jspx

编写 HTML标签 和 Java代码:

<body>
    <h1>hello jsp</h1>
    <%
        System.out.println("hello,jsp~");
    %>
</body>

原理


JSP 本质上就是一个 Servlet.

JSP 在被访问时,由JSP容器(Tomcat)将其转换为 Java文件(Servlet),在由JSP容器(Tomcat)将其编译,最终对外提供服务的其实就是这个字节码文件.


JSP脚本


JSP 脚本分类:

<%...%>:内容会直接放到_jspService()方法之中
<%=…%>:内容会放到out.print()中,作为out.print()的参数
<%!…%>:内容会放到_jspService()方法之外,被类直接包含

代码:

<%
    System.out.println("hello,jsp~");
    int i = 3;
%>

<%="hello"%>
<%=i%>

<%!
    void  show(){}
    String name = "zhangsan";
%>

案例


jsp代码是可以截断的:

<%
    for (int i = 0; i < brands.size(); i++) {
        Brand brand = brands.get(i);
%>

<tr align="center">
    <td><%=brand.getId()%></td>
    <td><%=brand.getBrandName()%></td>
    <td><%=brand.getCompanyName()%></td>
    <td><%=brand.getOrdered()%></td>
    <td><%=brand.getDescription()%></td>

    <%
        if(brand.getStatus() == 1){
            //显示启用
    %>
        <td><%="启用"%></td>
    <%
        }else {
            // 显示禁用
    %>
        <td><%="禁用"%></td>
    <%
        }
    %>

    <td><a href="#">修改</a> <a href="#">删除</a></td>
</tr>
<%
    }
%>

EL表达式


Expression Language 表达式语言,用于简化 JSP页面内的Java代码。

主要功能:获取数据。

语法:

${expression}

${brands}	获取域中存储的key为brands的数据
${cookie.key.value} // key指存储在cookie中的键名称

JavaWeb中的四大域对象:

page:当前页面有效
request:当前请求有效
session:当前会话有效
application:当前应用有效

el表达式获取数据,会依次从这4个域中寻找,直到找到为止。

示例代码:

//1. 准备数据
List<Brand> brands = new ArrayList<Brand>();
brands.add(new Brand(1,"三只松鼠","三只松鼠",100,"三只松鼠,好吃不上火",1));
brands.add(new Brand(2,"优衣库","优衣库",200,"优衣库,服适人生",0));
brands.add(new Brand(3,"小米","小米科技有限公司",1000,"为发烧而生",1));
//2. 存储到request域中
request.setAttribute("brands",brands);
request.setAttribute("status",1);
//3. 转发到 el-demo.jsp
request.getRequestDispatcher("/el-demo.jsp").forward(request,response);

//jsp代码:
<body>
    ${brands}
</body>

JSTL标签


JSP标准标签库(Jsp Standarded Tag Library) ,使用标签取代JSP页面上的Java代码。

导入坐标:

<!--jstl-->
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>

在JSP页面上引入JSTL标签库:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

c:if代码:

<%--c:if:来完成逻辑判断,替换java  if else--%>
<c:if test="${status ==1}">
    启用
</c:if>
<c:if test="${status ==0}">
    禁用
</c:if>

c:forEach代码:

<%--<c:forEach>:相当于 for 循环--%>
<c:forEach items="${brands}" var="brand" varStatus="status">
    <tr align="center">
        <%--<td>${brand.id}</td>--%>
        <td>${status.count}</td>
        <td>${brand.brandName}</td>
        <td>${brand.companyName}</td>
        <td>${brand.ordered}</td>
        <td>${brand.description}</td>
        <c:if test="${brand.status == 1}">
            <td>启用</td>
        </c:if>
        <c:if test="${brand.status != 1}">
            <td>禁用</td>
        </c:if>
        <td><a href="#">修改</a> <a href="#">删除</a></td>
    </tr>
</c:forEach>

会话跟踪技术

概念


会话跟踪技术:

会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。

HTTP协议是无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求,因此我们需要会话跟踪技术来实现会话内数据共享。

实现方式:

客户端会话跟踪技术:Cookie
服务端会话跟踪技术:Session


基本使用:

//创建Cookie对象,设置数据
Cookie cookie = new Cookie("key","value");
//发送Cookie到客户端:使用response对象
response.addCookie(cookie);

//获取客户端携带的所有Cookie,使用request对象
Cookie[] cookies = request.getCookies();
//遍历数组,获取每一个Cookie对象:for
//使用Cookie对象方法获取数据
cookie.getName();
cookie.getValue();

原理:

Cookie的实现是基于HTTP协议的
响应头:set-cookie
请求头:cookie

使用细节:

Cookie 存活时间:默认情况下,Cookie 存储在浏览器内存中,当浏览器关闭,内存释放,则Cookie被销毁

setMaxAge(int seconds):设置Cookie存活时间
正数:将 Cookie写入浏览器所在电脑的硬盘,持久化存储。到时间自动删除
负数:默认值,Cookie在当前浏览器内存中,当浏览器关闭,则 Cookie被销毁
零:删除对应 Cookie

Cookie 不能直接存储中文。如需要存储,则需要进行转码:URL编码

代码示例:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //发送Cookie
    //1. 创建Cookie对象
    //Cookie cookie = new Cookie("username","zs");
    String value = "张三";
    //URL编码
    value = URLEncoder.encode(value, "UTF-8");
    System.out.println("存储数据:"+value);
    Cookie cookie = new Cookie("username",value);
    //设置存活时间   ,1周 7天
    cookie.setMaxAge(60*60*24*7);
    //2. 发送Cookie,response
    response.addCookie(cookie);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取Cookie
    //1. 获取Cookie数组
    Cookie[] cookies = request.getCookies();
    //2. 遍历数组
    for (Cookie cookie : cookies) {
        //3. 获取数据
        String name = cookie.getName();
        if("username".equals(name)){
            String value = cookie.getValue();
            //URL解码
            value = URLDecoder.decode(value,"UTF-8");
            System.out.println(name+":"+value);
            break;
        }
    }

Session


服务端会话跟踪技术:将数据保存到服务端。JavaEE 提供 HttpSession接口,来实现一次会话的多次请求间数据共享功能。

基本使用:

//获取Session对象
HttpSession session = request.getSession();
//Session对象功能:
void setAttribute(String name, Object o):存储数据到 session 域中
Object getAttribute(String name):根据 key,获取值
void removeAttribute(String name):根据 key,删除该键值对

原理:

Session是基于Cookie实现的

使用细节:

服务器重启后,Session中的数据是否还在?
钝化:在服务器正常关闭后, Tomcat会自动将 Session数据写入硬盘的文件中
活化:再次启动服务器后,从文件中加载数据到Session中

Seesion 销毁:默认情况下,无操作,30分钟自动销毁。
web.xml手动配置时间:
<session-config>
    <session-timeout>100</session-timeout>
</session-config>
手动销毁:调用 Session对象的 invalidate()方法

代码示例:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //存储到Session中
    //1. 获取Session对象
    HttpSession session = request.getSession();
    System.out.println(session);
    //2. 存储数据
    session.setAttribute("username","zs");
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取数据,从session中
    //1. 获取Session对象
    HttpSession session = request.getSession();
    System.out.println(session);
    // 销毁
    //session.invalidate();
    //2. 获取数据
    Object username = session.getAttribute("username");
    System.out.println(username);
}

小节


Cookie 和 Session 都是来完成一次会话内多次请求间数据共享的。

区别:

存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端
安全性:Cookie 不安全,Session 安全
数据大小:Cookie 最大3KB,Session 无大小限制
存储时间:Cookie 可以长期存储,Session 默认30分钟
服务器性能:Cookie 不占服务器资源,Session 占用服务器资源

图片验证码


import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Arrays;
import java.util.Random;

/**
 * 生成验证码工具类
 */
public class CheckCodeUtil {

    public static final String VERIFY_CODES = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static Random random = new Random();


    public static void main(String[] args) throws IOException {
        OutputStream fos = new FileOutputStream("d://a.jpg");
        String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, fos, 4);

        System.out.println(checkCode);
    }


    /**
     * 输出随机验证码图片流,并返回验证码值(一般传入输出流,响应response页面端,Web项目用的较多)
     *
     * @param width 图片宽度
     * @param 
     * 
     * ght 图片高度
     * @param os  输出流
     * @param verifySize 数据长度
     * @return 验证码数据
     * @throws IOException
     */
    public static String outputVerifyImage(int width, int height, OutputStream os, int verifySize) throws IOException {
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(width, height, os, verifyCode);
        return verifyCode;
    }

    /**
     * 使用系统默认字符源生成验证码
     * @param verifySize 验证码长度
     * @return
     */
    public static String generateVerifyCode(int verifySize) {
        return generateVerifyCode(verifySize, VERIFY_CODES);
    }

    /**
     * 使用指定源生成验证码
     *
     * @param verifySize 验证码长度
     * @param sources    验证码字符源
     * @return
     */
    public static String generateVerifyCode(int verifySize, String sources) {
        // 未设定展示源的字码,赋默认值大写字母+数字
        if (sources == null || sources.length() == 0) {
            sources = VERIFY_CODES;
        }
        int codesLen = sources.length();
        Random rand = new Random(System.currentTimeMillis());
        StringBuilder verifyCode = new StringBuilder(verifySize);
        for (int i = 0; i < verifySize; i++) {
            verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
        }
        return verifyCode.toString();
    }

    /**
     * 生成随机验证码文件,并返回验证码值 (生成图片形式,用的较少)
     *
     * @param w
     * @param h
     * @param outputFile
     * @param verifySize
     * @return
     * @throws IOException
     */
    public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException {
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(w, h, outputFile, verifyCode);
        return verifyCode;
    }

    /**
     * 生成指定验证码图像文件
     *
     * @param w
     * @param h
     * @param outputFile
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, File outputFile, String code) throws IOException {
        if (outputFile == null) {
            return;
        }
        File dir = outputFile.getParentFile();
        //文件不存在
        if (!dir.exists()) {
            //创建
            dir.mkdirs();
        }
        try {
            outputFile.createNewFile();
            FileOutputStream fos = new FileOutputStream(outputFile);
            outputImage(w, h, fos, code);
            fos.close();
        } catch (IOException e) {
            throw e;
        }
    }

    /**
     * 输出指定验证码图片流
     *
     * @param w
     * @param h
     * @param os
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, OutputStream os, String code) throws IOException {
        int verifySize = code.length();
        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Random rand = new Random();
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 创建颜色集合,使用java.awt包下的类
        Color[] colors = new Color[5];
        Color[] colorSpaces = new Color[]{Color.WHITE, Color.CYAN,
                Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
                Color.PINK, Color.YELLOW};
        float[] fractions = new float[colors.length];
        for (int i = 0; i < colors.length; i++) {
            colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
            fractions[i] = rand.nextFloat();
        }
        Arrays.sort(fractions);
        // 设置边框色
        g2.setColor(Color.GRAY);
        g2.fillRect(0, 0, w, h);

        Color c = getRandColor(200, 250);
        // 设置背景色
        g2.setColor(c);
        g2.fillRect(0, 2, w, h - 4);

        // 绘制干扰线
        Random random = new Random();
        // 设置线条的颜色
        g2.setColor(getRandColor(160, 200));
        for (int i = 0; i < 20; i++) {
            int x = random.nextInt(w - 1);
            int y = random.nextInt(h - 1);
            int xl = random.nextInt(6) + 1;
            int yl = random.nextInt(12) + 1;
            g2.drawLine(x, y, x + xl + 40, y + yl + 20);
        }

        // 添加噪点
        // 噪声率
        float yawpRate = 0.05f;
        int area = (int) (yawpRate * w * h);
        for (int i = 0; i < area; i++) {
            int x = random.nextInt(w);
            int y = random.nextInt(h);
            // 获取随机颜色
            int rgb = getRandomIntColor();
            image.setRGB(x, y, rgb);
        }
        // 添加图片扭曲
        shear(g2, w, h, c);

        g2.setColor(getRandColor(100, 160));
        int fontSize = h - 4;
        Font font = new Font("Algerian", Font.ITALIC, fontSize);
        g2.setFont(font);
        char[] chars = code.toCharArray();
        for (int i = 0; i < verifySize; i++) {
            AffineTransform affine = new AffineTransform();
            affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize / 2, h / 2);
            g2.setTransform(affine);
            g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);
        }

        g2.dispose();
        ImageIO.write(image, "jpg", os);
    }

    /**
     * 随机颜色
     *
     * @param fc
     * @param bc
     * @return
     */
    private static Color getRandColor(int fc, int bc) {
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }

    private static int getRandomIntColor() {
        int[] rgb = getRandomRgb();
        int color = 0;
        for (int c : rgb) {
            color = color << 8;
            color = color | c;
        }
        return color;
    }

    private static int[] getRandomRgb() {
        int[] rgb = new int[3];
        for (int i = 0; i < 3; i++) {
            rgb[i] = random.nextInt(255);
        }
        return rgb;
    }

    private static void shear(Graphics g, int w1, int h1, Color color) {
        shearX(g, w1, h1, color);
        shearY(g, w1, h1, color);
    }

    private static void shearX(Graphics g, int w1, int h1, Color color) {

        int period = random.nextInt(2);

        boolean borderGap = true;
        int frames = 1;
        int phase = random.nextInt(2);

        for (int i = 0; i < h1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(0, i, w1, 1, (int) d, 0);
            if (borderGap) {
                g.setColor(color);
                g.drawLine((int) d, i, 0, i);
                g.drawLine((int) d + w1, i, w1, i);
            }
        }

    }

    private static void shearY(Graphics g, int w1, int h1, Color color) {
        int period = random.nextInt(40) + 10; // 50;
        boolean borderGap = true;
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < w1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            g.copyArea(i, 0, 1, h1, 0, (int) d);
            if (borderGap) {
                g.setColor(color);
                g.drawLine(i, (int) d, i, 0);
                g.drawLine(i, (int) d + h1, i, h1);
            }

        }

    }
}

Filter和Listener

Filter介绍


概念:Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。

过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。

过滤器一般完成一些通用的操作,比如:权限控制、统一编码处理、敏感字符处理等等…


Filter快速入门


定义类,实现 Filter接口,并重写其所有方法。配置Filter拦截资源的路径:在类上定义 @WebFilter 注解。在doFilter方法中输出一句话,并放行。

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class FilterDemo implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //1. 放行前,对 request数据进行处理
        System.out.println("1.FilterDemo...");
        //放行
        chain.doFilter(request,response);
        //2. 放行后,对Response 数据进行处理
        System.out.println("5.FilterDemo...");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void destroy() {
    }
}

放行后访问对应资源,资源访问完成后,还会回到Filter中吗?

如果回到Filter中,是重头执行还是执行放行后的逻辑呢?执行放行后的

执行放行前逻辑 -> 放行 -> 访问资源 -> 执行放行后逻辑

Filter使用细节


Filter 可以根据需求,配置不同的拦截资源路径:

拦截具体的资源:/index.jsp:只有访问index.jsp时才会被拦截。
目录拦截:/user/*:访问/user下的所有资源,都会被拦截。
后缀名拦截:*.jsp:访问后缀名为jsp的资源,都会被拦截。
拦截所有:/*:访问所有资源,都会被拦截

过滤器链:一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链。

注解配置的Filter,优先级按照过滤器**类名(字符串)**的自然排序。

登录验证案例代码:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
    HttpServletRequest req = (HttpServletRequest) request;
    //判断访问资源路径是否和登录注册相关
    String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"};
    // 获取当前访问的资源路径
    String url = req.getRequestURL().toString();
    //循环判断
    for (String u : urls) {
        if(url.contains(u)){
            //找到了-放行
            chain.doFilter(request, response);
            //break;
            return;
        }
    }
    //1. 判断session中是否有user
    HttpSession session = req.getSession();
    Object user = session.getAttribute("user");

    //2. 判断user是否为null
    if(user != null){
        // 登录过了-放行
        chain.doFilter(request, response);
    }else {
        // 没有登陆,存储提示信息,跳转到登录页面
        req.setAttribute("login_msg","您尚未登陆!");
        req.getRequestDispatcher("/login.jsp").forward(req,response);
    }
}

Listener


概念:Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。

监听器可以监听就是在application,session,request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。

Listener分类:JavaWeb中提供了8个监听器

ServletContextListener 使用:

@WebListener
public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //加载资源
        System.out.println("ContextLoaderListener...");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        //释放资源
    }
}

AJAX

介绍


概念:AJAX(Asynchronous JavaScript And XML):异步的 JavaScript 和 XML。

AJAX作用:

与服务器进行数据交换:通过AJAX可以给服务器发送请求,并获取服务器响应的数据。使用了AJAX和服务器进行通信,就可以使用 HTML+AJAX来替换JSP页面了。

异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用校验,等等…

同步发送请求过程:浏览器页面在发送请求给服务器,在服务器处理请求的过程中,浏览器页面不能做其他的操作。只能等到服务器响应结束后才能,浏览器页面才能继续做其他的操作。

异步发送请求:浏览器页面发送请求给服务器,在服务器处理请求的过程中,浏览器页面还可以做其他的操作。


快速入门


<script>
    //1. 创建核心对象
    var xhttp;
    if (window.XMLHttpRequest) {
        xhttp = new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    //2. 发送请求
    xhttp.open("GET", "http://localhost:8080/ajax-demo/ajaxServlet");
    xhttp.send();

    //3. 获取响应
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
               alert(this.responseText);
        }
    };
</script>

Axios


Axios 异步框架。Axios 对原生的AJAX进行封装,简化书写。官网:https://www.axios-http.cn

使用:

引入 axios 的 js 文件
<script src="js/axios-0.18.0.js"></script>

<script>
    //1. get
    axios({
        method:"get",
        url:"http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan"
    }).then(function (resp) {
        alert(resp.data);
    })

    //2. post
    axios({
        method:"post",
        url:"http://localhost:8080/ajax-demo/axiosServlet",
        data:"username=zhangsan"
    }).then(function (resp) {
        alert(resp.data);
    })
</script>

别名方式:

<script>
    //1. get
    axios.get("http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan").then(function (resp) {
        alert(resp.data);
    })

    //2. post
   axios.post("http://localhost:8080/ajax-demo/axiosServlet","username=zhangsan").then(function (resp) {
        alert(resp.data);
    })
</script>

Axios中,JSON字符串和JS对象自动进行转换。


Fastjson


Fastjson是阿里巴巴提供的一个Java语言编写的高性能功能完善的JSON库,是目前Java语言中最快的JSON库,可以实现Java对象和JSON字符串的相互转换。

导入坐标:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

Java对象转JSON:

String jsonStr = JSON.toJSONString(obj);

JSON字符串转Java对象:

User user = JSON.parseObject(jsonStr, User.class);

spring

初识Spring


Spring技术是JavaEE开发必备技能,企业开发技术选型命中率>90%。简化开发(IoC、AOP、事务处理),降低企业级开发的复杂性。框架整合,高效整合其他技术,提高企业级应用开发与运行效率。

官网:spring.io

Spring发展到今天已经形成了一种开发的生态圈,Spring提供了若干个项目,每个项目用于完成特定的功能。

Spring Framework
Spring Boot
Spring Cloud
等等。。。

Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基。

Core Container:核心容器

AOP:面向切面编程
Aspects:AOP思想实现

Data Access:数据访问
Data Integration:数据集成

Web:Web开发

Test:单元测试与集成测试

使用对象时,在程序中不要主动使用new产生对象,转换为由外部提供对象。IoC(Inversion of Control)控制反转:对象的创建控制权由程序转移到外部,这种思想称为控制反转。

Spring提供了一个容器,称为IoC容器,用来充当IoC思想中的“外部”。IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean。

DI(Dependency Injection)依赖注入:在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入。

最终效果:使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系

原始代码:

public interface BookDao {
    public void save();
}

public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
}

public interface BookService {
    public void save();
}

public class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookDaoImpl();
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

public class App {
    public static void main(String[] args) {
        BookService bookService = new BookServiceImpl();
        bookService.save();
    }
}
//输出
//book service save ...
//book dao save ...

Ioc入门:

<!--导入spring的坐标-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.10.RELEASE</version>
</dependency>

<!--新建xml:resources -> new -> xml configuration File -> Spring Config-->
<!--配置bean-->
    <!--bean标签标示配置bean
    id属性标示给bean起名字
    class属性表示给bean定义类型-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookDao" class="com.tea.web.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.tea.web.service.impl.BookServiceImpl" />
</beans>

public class App2 {
    public static void main(String[] args) {
        //获取IoC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}
//输出
//book service save ...
//book dao save ...

DI入门:

//改造BookServiceImpl
public class BookServiceImpl implements BookService {
    //5.删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
    //6.提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

<!--设置xml-->
<bean id="bookDao" class="com.tea.web.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.tea.web.service.impl.BookServiceImpl">
    <!--7.配置server与dao的关系-->
    <!--name:BookServiceImpl类中的成员变量bookDao。ref:上面的id-bookDao-->
    <property name="bookDao" ref="bookDao"/>
</bean>

bean实例化


xml中bean基础配置:

id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一
class:bean的类型,即配置的bean的全路径类名

bean别名配置:

定义bean的别名,可定义多个,使用逗号(,)分号(;)空格( )分隔

<bean id="bookDao" name="dao bookDaoImpl" class="com.dao.impl.BookDaoImpl"/>
<bean name="service,bookServiceImpl" class="com.service.impl.BookServiceImpl"/>

获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常 NoSuchBeanDefinitionException

bean作用范围配置:

scope:定义bean的作用范围,可选范围如下
singleton:单例(默认)
prototype:非单例

<bean id="bookDao" class="com.dao.impl.BookDaoImpl" scope="prototype" />

bean是如何创建的:

bean实例化:bean本质上就是对象,创建bean使用构造方法完成

实例化bean的三种方式:

实例化bean的三种方式————构造方法(常用):

public class BookDaoImpl implements BookDao {

    //public BookDaoImpl() {
    //    System.out.println("book dao constructor is running ....");
    //}

    public void save() {
        System.out.println("book dao save ...");
    }
}

<bean id="bookDao"  class="com.dao.impl.BookDaoImpl"/>

无参构造方法如果不存在,将抛出异常BeanCreationException

实例化bean的三种方式————静态工厂(了解):

//静态工厂创建对象
public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        System.out.println("factory setup....");
        return new OrderDaoImpl();
    }
}

<!--方式二:使用静态工厂实例化bean-->
<bean id="orderDao" class="com.factory.OrderDaoFactory" factory-method="getOrderDao"/>

实例化bean的三种方式————实例工厂(了解):

//实例工厂创建对象
public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}
<!--方式三:使用实例工厂实例化bean-->
<bean id="userFactory" class="com.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

实例化bean的第四种方式FactoryBean(实用):

import org.springframework.beans.factory.FactoryBean;
//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
    public Class<?> getObjectType() {
        return UserDao.class;
    }
    //设置是否单例:实现方法isSingleton即可
}

<!--方式四:使用FactoryBean实例化bean-->
<bean id="userDao" class="com.factory.UserDaoFactoryBean"/>

bean的生命周期-方式一在xml中配置对应方法:

<!--init-method:设置bean初始化生命周期回调函数-->
<!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象-->
<bean id="bookDao" class="com.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>

public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
    //表示bean初始化对应的操作
    public void init(){
        System.out.println("init...");
    }
    //表示bean销毁前对应的操作
    public void destory(){
        System.out.println("destory...");
    }
}

bean的生命周期-方式二InitializingBean和DisposableBean:

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;
    public void setBookDao(BookDao bookDao) {
        System.out.println("set .....");
        this.bookDao = bookDao;
    }
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }

    public void destroy() throws Exception {
        System.out.println("service destroy");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("service init");
    }
}

注入


向一个类中传递数据的方式:普通方法(set方法)和 构造方法。

传递数据的类型:引用类型和简单类型(基本数据类型与String)。

依赖注入方式:

setter注入
    简单类型
    引用类型
构造器注入
    简单类型
    引用类型

setter注入-引用类型:

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;
    //setter注入需要提供要注入对象的set方法
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    //setter注入需要提供要注入对象的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
        userDao.save();
    }
}

<bean id="userDao" class="com.dao.impl.UserDaoImpl"/>
<bean id="bookDao" class="com..dao.impl.BookDaoImpl"/>
<!--注入引用类型-->
<bean id="bookService" class="com.service.impl.BookServiceImpl">
    <!--property标签:设置注入属性-->
    <!--name属性:设置注入的属性名,实际是set方法对应的名称-->
    <!--ref属性:设置注入引用类型bean的id或name-->
    <property name="bookDao" ref="bookDao"/>
    <property name="userDao" ref="userDao"/>
</bean>

setter注入-简单类型:

public class BookDaoImpl implements BookDao {
    private String databaseName;
    private int connectionNum;
    //setter注入需要提供要注入对象的set方法
    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }
    //setter注入需要提供要注入对象的set方法
    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }
    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}

<!--注入简单类型-->
<bean id="bookDao" class="com.dao.impl.BookDaoImpl">
    <!--property标签:设置注入属性-->
    <!--name属性:设置注入的属性名,实际是set方法对应的名称-->
    <!--value属性:设置注入简单类型数据值-->
    <property name="connectionNum" value="100"/>
    <property name="databaseName" value="mysql"/>
</bean>

构造器注入-简单类型和引用类型:

public class BookDaoImpl implements BookDao {
    private String databaseName;
    private int connectionNum;

    public BookDaoImpl(String databaseName, int connectionNum) {
        this.databaseName = databaseName;
        this.connectionNum = connectionNum;
    }

    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;

    public BookServiceImpl(BookDao bookDao, UserDao userDao) {
        this.bookDao = bookDao;
        this.userDao = userDao;
    }

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
        userDao.save();
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
    标准书写
    <bean id="bookDao" class="com.dao.impl.BookDaoImpl">
        根据构造方法参数名称注入
        <constructor-arg name="connectionNum" value="10"/>
        <constructor-arg name="databaseName" value="mysql"/>
    </bean>
    <bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

    <bean id="bookService" class="com.service.impl.BookServiceImpl">
        <constructor-arg name="userDao" ref="userDao"/>
        <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>
-->
<!--
    解决形参名称的问题,与形参名不耦合
    <bean id="bookDao" class="com.dao.impl.BookDaoImpl">
        根据构造方法参数类型注入
        <constructor-arg type="int" value="10"/>
        <constructor-arg type="java.lang.String" value="mysql"/>
    </bean>
    <bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

    <bean id="bookService" class="com.service.impl.BookServiceImpl">
        <constructor-arg name="userDao" ref="userDao"/>
        <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>-->

    <!--解决参数类型重复问题,使用位置解决参数匹配-->
    <bean id="bookDao" class="com.dao.impl.BookDaoImpl">
        <!--根据构造方法参数位置注入-->
        <constructor-arg index="0" value="mysql"/>
        <constructor-arg index="1" value="100"/>
    </bean>
    <bean id="userDao" class="com.dao.impl.UserDaoImpl"/>

    <bean id="bookService" class="com.service.impl.BookServiceImpl">
        <constructor-arg name="userDao" ref="userDao"/>
        <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>
</beans>

强制依赖使用构造器进行。使用setter注入有概率不进行注入导致null对象出现。可选依赖使用setter注入进行,灵活性强。Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨。自己开发的模块推荐使用setter注入。


IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配。自动装配方式:按类型(常用)、按名称、按构造方法、不启用自动装配。

依赖自动装配:配置中使用bean标签autowire属性设置自动装配的类型

<bean id="bookDao" class="com.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.service.impl.BookServiceImpl" autowire="byType"/>

自动装配用于引用类型依赖注入,不能对简单类型进行操作。使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用。
使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用。

自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效。


集合注入:

public class BookDaoImpl implements BookDao {

    private int[] array;
    private List<String> list;
    private Set<String> set;
    private Map<String,String> map;
    private Properties properties;
    public void setArray(int[] array) {
        this.array = array;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    public void setSet(Set<String> set) {
        this.set = set;
    }
    public void setMap(Map<String, String> map) {
        this.map = map;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public void save() {
        System.out.println("book dao save ...");
        System.out.println("遍历数组:" + Arrays.toString(array));
        System.out.println("遍历List" + list);
        System.out.println("遍历Set" + set);
        System.out.println("遍历Map" + map);
        System.out.println("遍历Properties" + properties);
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookDao" class="com.dao.impl.BookDaoImpl">
        <!--数组注入-->
        <property name="array">
            <array>
                <value>100</value>
                <value>200</value>
                <value>300</value>
            </array>
        </property>
        <!--list集合注入-->
        <property name="list">
            <list>
                <value>it</value>
                <value>fghf</value>
                <value>ghf</value>
                <value>hgfd</value>
            </list>
        </property>
        <!--set集合注入-->
        <property name="set">
            <set>
                <value>jgf</value>
                <value>jh</value>
                <value>jhfg</value>
                <value>nv</value>
            </set>
        </property>
        <!--map集合注入-->
        <property name="map">
            <map>
                <entry key="country" value="china"/>
                <entry key="province" value="henan"/>
                <entry key="city" value="kaifeng"/>
            </map>
        </property>
        <!--Properties注入-->
        <property name="properties">
            <props>
                <prop key="country">china</prop>
                <prop key="province">henan</prop>
                <prop key="city">kaifeng</prop>
            </props>
        </property>
    </bean>
</beans>

druid和ComboPooledDataSource配置


加载properties配置文件信息:

不加载系统属性
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>

加载多个properties文件
<context:property-placeholder location="jdbc.properties,msg.properties"/>

加载所有properties文件
<context:property-placeholder location="*.properties"/>

加载properties文件标准格式
<context:property-placeholder location="classpath:*.properties"/>

从类路径或jar包中搜索并加载properties文件
<context:property-placeholder location="classpath*:*.properties"/>

Spring管理第三方资源:DruidDataSource和ComboPooledDataSource。

坐标:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.16</version>
    </dependency>
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            ">
<!--    管理DruidDataSource对象-->
<!--    <bean class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
        <property name="maxPoolSize" value="1000"/>
    </bean>-->

<!--    1.开启context命名空间-->
<!--    2.使用context空间加载properties文件-->
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
<!--    3.使用属性占位符${}读取properties文件中的属性-->
<!--    说明:idea自动识别${}加载的属性值,需要手工点击才可以查阅原始书写格式-->
    <bean class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

jdbc.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root

执行代码:

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = (DataSource) ctx.getBean("dataSource");
        System.out.println(dataSource);
    }
}

小结


创建容器的两种方式:

方式一:类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

方式二:文件路径加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");

加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");

获取bean的三种方式:

方式一:使用bean名称获取
BookDao bookDao = (BookDao) ctx.getBean("bookDao");

方式二:使用bean名称获取并指定类型
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);

方式三:使用bean类型获取
BookDao bookDao = ctx.getBean(BookDao.class);

BeanFactory初始化:

Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean("bookDao", BookDao.class);
bookDao.save();

BeanFactory是顶层接口。BeanFactory创建完毕后,所有的bean均为延迟加载(即new XmlBeanFactory之后,bean的构造方法不加载)。

BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载。ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载。

bean相关:

<bean
    id="bookDao"	<!--bean的Id	-->
    name="dao bookDaoImpl daoImpl"	<!--bean别名	-->
    class="com.dao.impl.BookDaoImpl"	<!--bean类型,静态工厂类,FactoryBean类	-->
    scope="singleton"	<!--控制bean的实例数量	-->
    init-method="init"	<!--生命周期初始化方法	-->
    destroy-method="destory"	<!--生命周期销毁方法	-->
    autowire="byType"	<!--自动装配类型	-->
    factory-method="getInstance"	<!--bean工厂方法,应用于静态工厂或实例工厂	-->
    factory-bean="com.factory.BookDaoFactory"	<!--实例工厂bean	-->
    lazy-init="true" />	<!--控制bean延迟加载	-->

依赖注入相关:

<bean id="bookService" class="com.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao" ref="bookDao"/>		<!--构造器注入引用类型	-->
    <constructor-arg name="userDao" ref="userDao"/>
    <constructor-arg name="msg" value="WARN"/>
    <constructor-arg type="java.lang.String" index="3" value="WARN"/>		<!--构造器注入简单类型,类型匹配与索引匹配	-->
    <property name="bookDao" ref="bookDao"/>		<!--setter注入引用类型	-->
    <property name="userDao" ref="userDao"/>
    <property name="msg" value="WARN"/>		<!--setter注入简单类型	-->
    <property name="names">		<!--setter注入集合类型	-->
        <list>
            <value>dsafas</value>
            <ref bean="dataSource"/>		<!--集合注入引用类型	-->
        </list>
    </property>
</bean>

注解开发


注解开发定义bean:

<bean id="bookService" class="com.tea.service.impl.BookServiceImpl"/>

//使用注解实现
@Component("bookService")
public class BookServiceImpl implements BookService {}
//xml:核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.tea"/>

Spring提供 @Component 注解的三个衍生注解:

@Controller:用于表现层bean定义
@Service:用于业务层bean定义
@Repository:用于数据层bean定义

Spring3.0升级了纯注解开发模式,使用Java类替代xml配置文件,开启了Spring快速开发赛道:

//配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//声明当前类为Spring配置类
@Configuration
//设置bean扫描路径,多个路径书写为字符串数组格式
@ComponentScan({"com.tea.service","com.tea.dao"})
public class SpringConfig {
}

//使用配置
public class AppForAnnotation {
    public static void main(String[] args) {
        //AnnotationConfigApplicationContext加载Spring配置类初始化Spring容器
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        //按类型获取bean
        BookService bookService = ctx.getBean(BookService.class);
        System.out.println(bookService);
    }
}

@Configuration 注解用于设定当前类为配置类。

@ComponentScan 注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式。

bean作用范围(Scope)和生命周期的注解(PostConstruct、PreDestroy):

@Repository
//@Scope设置bean的作用范围
@Scope("singleton")
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
    //@PostConstruct设置bean的初始化方法
    @PostConstruct
    public void init() {
        System.out.println("init ...");
    }
    //@PreDestroy设置bean的销毁方法
    @PreDestroy
    public void destroy() {
        System.out.println("destroy ...");
    }
}

依赖注入(Autowired):

@Service
public class BookServiceImpl implements BookService {
    //@Autowired:注入引用类型,自动装配模式,默认按类型装配
    @Autowired
    //@Qualifier:自动装配bean时按bean名称装配
    @Qualifier("bookDao")
    private BookDao bookDao;
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法。

注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法。

使用 @Qualifier 注解开启指定名称装配bean。注意:@Qualifier 注解无法单独使用,必须配合 @Autowired 注解使用。

简单类型注入(Value):

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    @Value("100")
    private String connectionNum;
}

加载properties文件(PropertySource):

@Configuration
@ComponentScan("com.tea")
//@PropertySource加载properties配置文件
@PropertySource({"jdbc.properties"})
public class SpringConfig {
}

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
    //@Value:注入简单类型(无需提供set方法)
    @Value("${name}")
    private String name;
    public void save() {
        System.out.println("book dao save ..." + name);
    }
}

//resources下jdbc.properties文件
name=tea

第三方bean管理


坐标:

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>
</dependencies>

定义单独配置类:

public class JdbcConfig {
    //1.定义一个方法获得要管理的对象
    @Value("com.mysql.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql://localhost:3306/spring_db")
    private String url;
    @Value("root")
    private String userName;
    @Value("root")
    private String password;
    //2.添加@Bean,表示当前方法的返回值是一个bean
    //@Bean修饰的方法,形参根据类型自动装配
    @Bean
    public DataSource dataSource(BookDao bookDao){
        System.out.println(bookDao);
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}

上面的BookDao 参数,引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象。

@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
}

配置类导入单独配置:

@Configuration
@ComponentScan("com.tea")
//@Import:导入配置信息
@Import({JdbcConfig.class})
public class SpringConfig {
}

使用:

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        DataSource dataSource = ctx.getBean(DataSource.class);
        System.out.println(dataSource);
    }
}

XML配置比对注解配置


功能 XML配置 注解
定义bean bean标签:id属性、class属性 @Component(@Controller、@Service、@Repository)、@ComponentScan
设置依赖注入 setter注入(set方法)、构造器注入(构造方法)、自动装配 @Autowired@Qualifier@Value
配置第三方bean bean标签:静态工厂、实例工厂、FactoryBean @Bean
作用范围 scope属性 @Scope
生命周期 标准接口:init-method、destroy-method @PostConstructor、@PreDestroy

Spring整合mybatis


在pom.xml中添加基础依赖:

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
</dependency>

service层代码:

public interface AccountService {
    void save(Account account);
    void delete(Integer id);
    void update(Account account);
    List<Account> findAll();
    Account findById(Integer id);
}
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    public void save(Account account) {
        accountDao.save(account);
    }
    public void update(Account account){
        accountDao.update(account);
    }
    public void delete(Integer id) {
        accountDao.delete(id);
    }
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }
    public List<Account> findAll() {
        return accountDao.findAll();
    }
}

dao层基础代码:

public interface AccountDao {
    @Insert("insert into tbl_account(name,money)values(#{name},#{money})")
    void save(Account account);
    @Delete("delete from tbl_account where id = #{id} ")
    void delete(Integer id);
    @Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
    void update(Account account);
    @Select("select * from tbl_account")
    List<Account> findAll();
    @Select("select * from tbl_account where id = #{id} ")
    Account findById(Integer id);
}

domain包下:

public class Account implements Serializable {
    private Integer id;
    private String name;
    private Double money;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Double getMoney() {
        return money;
    }
    public void setMoney(Double money) {
        this.money = money;
    }
    @Override
    public String toString() {
        return "Account{" +"id=" + id +", name='" + name + '\'' +", money=" + money +'}';
    }
}

创建JdbcConfig配置DataSource数据源:

//resources文件下jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}

创建MybatisConfig整合mybatis:

public class MybatisConfig {
    //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.tea.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }
    //定义bean,返回MapperScannerConfigurer对象
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.tea.dao");
        return msc;
    }
}

创建SpringConfig主配置类进行包扫描和加载其他配置类:

@Configuration
@ComponentScan("com.tea")
//@PropertySource:加载类路径jdbc.properties文件
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {
}

定义测试类进行测试:

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService accountService = ctx.getBean(AccountService.class);
        Account ac = accountService.findById(1);
        System.out.println(ac);
    }
}

Spring整合Junit单元测试


导入整合的依赖坐标:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
</dependency>
<!--spring整合junit-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>5.1.9.RELEASE</version>
</dependency>

使用Spring整合Junit专用的类加载器,加载配置文件或者配置类:

//使用Spring整合Junit专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
//加载配置文件或者配置类
@ContextConfiguration(classes = {SpringConfiguration.class}) //加载配置类
//@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载配置文件
public class AccountServiceTest {
    //支持自动装配注入bean
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById(){
        System.out.println(accountService.findById(1));
    }

    @Test
    public void testFindAll(){
        System.out.println(accountService.findAll());
    }
}

注意:junit的依赖至少要是4.12版本,可以是4.13等版本,否则出现异常。


AOP


AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。

OOP(Object Oriented Programming)面向对象编程。

AOP作用:在不惊动原始设计的基础上为其进行功能增强。简单的说就是在不改变方法源代码的基础上对方法进行功能增强。

连接点(JoinPoint):正在执行的方法,例如:update()、delete()、select()等都是连接点。

切入点(Pointcut):进行功能增强了的方法,例如:update()、delete()方法,select()方法没有被增强所以不是切入点,但是是连接点。在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法。一个具体方法:com.tea.dao包下的BookDao接口中的无形参无返回值的save方法。匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法。

通知(Advice):在切入点前后执行的操作,也就是增强的共性功能。在SpringAOP中,功能最终以方法的形式呈现。

通知类:通知方法所在的类叫做通知类。

切面(Aspect):描述通知与切入点的对应关系,也就是哪些通知方法对应哪些切入点方法。

AOP工作流程:

//导入aop相关坐标:
<dependencies>
    <!--spring核心依赖,会将spring-aop传递进来-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
</dependencies>

//定义dao接口与实现类:
public interface BookDao {
    public void save();
    public void update();
}
@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
    }
    public void update(){
        System.out.println("book dao update ...");
    }
}

//定义通知类,制作通知方法:
//定义切入点表达式、配置切面(绑定切入点与通知关系):
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
    //设置切入点,@Pointcut注解要求配置在方法上方
    @Pointcut("execution(void com.tea.dao.BookDao.update())")
    private void pt(){}

    //设置在切入点pt()的前面运行当前操作(前置通知)
    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

//在配置类中进行Spring注解包扫描和开启AOP功能
@Configuration
@ComponentScan("com.tea")
//开启注解开发AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {
}

//测试类和运行结果
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.update();
    }
}

AOP工作流程:

Spring容器启动
读取所有切面配置中的切入点
初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
   - 匹配失败,创建原始对象
   - 匹配成功,创建原始对象(目标对象)的代理对象
获取bean执行方法
   - 获取的bean是原始对象时,调用方法并执行,完成操作
   - 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

AOP核心概念:

目标对象(Target):被代理的对象,也叫原始对象,该对象中的方法没有任何功能增强。
代理对象(Proxy):代理后生成的对象,由Spring帮我们创建代理对象。

AOP切入点表达式,语法格式:

//切入点表达式标准格式:动作关键字(访问修饰符  返回值  包名.类/接口名.方法名(参数) 异常名)
execution(public User com.tea.service.UserService.findById(int))

动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
访问修饰符:public,private等,可以省略
返回值:写返回值类型
包名:多级包使用点连接
类/接口名:
方法名:
参数:直接写参数的类型,多个类型用逗号隔开
异常名:方法定义中抛出指定异常,可以省略

通配符:

* :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

匹配com.tea包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
execution(public * com.tea.*.UserService.find*(*))

.. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
execution(public User com..UserService.findById(..))

+:专用于匹配子类类型

execution(* *..*Service+.*(..))

AOP通知共分为5种类型:

前置通知:在切入点方法执行之前执行
后置通知:在切入点方法执行之后执行,无论切入点方法内部是否出现异常,后置通知都会执行。
环绕通知(重点):手动调用切入点方法并对其进行增强的通知方式。
返回后通知(了解):在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行。
抛出异常后通知(了解):在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行。

前置通知:@Before
后置通知:@After
返回后通知:@AfterReturning(了解)
抛出异常后通知:@AfterThrowing(了解)
环绕通知:@Around(重点,常用)

@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around before advice ...");
    Object ret = pjp.proceed();
    System.out.println("around after advice ...");
    return ret;
}

环绕通知注意事项:

环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。

环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里。

//获取执行的签名对象
Signature signature = pjp.getSignature();
//获取接口/类全限定名
String className = signature.getDeclaringTypeName();
//获取方法名
String methodName = signature.getName();

AOP切入点数据获取-获取参数:

JoinPoint:适用于前置、后置、返回后、抛出异常后通知
ProceedJointPoint:适用于环绕通知

@Before("pt()")
public void before(JoinPoint jp) {
    Object[] args = jp.getArgs(); //获取连接点方法的参数们
    System.out.println(Arrays.toString(args));
}

@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    Object[] args = pjp.getArgs(); //获取连接点方法的参数们
    System.out.println(Arrays.toString(args));
    Object ret = pjp.proceed();
    return ret;
}

AOP切入点数据获取-获取切入点方法返回值:

返回后通知
环绕通知

@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) { //变量名要和returning="ret"的属性值一致
    System.out.println("afterReturning advice ..."+ret);
}

@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    // 手动调用连接点方法,返回值就是连接点方法的返回值
    Object ret = pjp.proceed();
    return ret;
}

AOP切入点数据获取-获取切入点方法运行异常信息:

抛出异常后通知
环绕通知

@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {//变量名要和throwing = "t"的属性值一致
    System.out.println("afterThrowing advice ..."+ t);
}

@Around("pt()")
public Object around(ProceedingJoinPoint pjp)  {
    Object ret = null;
    //此处需要try...catch处理,catch中捕获到的异常就是连接点方法中抛出的异常
    try {
        ret = pjp.proceed();
    } catch (Throwable t) {
        t.printStackTrace();
    }
    return ret;
}

Spring事务


实现步骤一-在业务层接口上添加Spring事务管理

public interface AccountService {
    //配置当前接口方法具有事务
    @Transactional
    public void transfer(String out,String in ,Double money) ;
}

注意事项:Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合。注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务。

实现步骤二-设置事务管理器(将事务管理器添加到IOC容器中):

//可以在JdbcConfig中配置事务管理器
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
    DataSourceTransactionManager dtm = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
}

注意事项:事务管理器要根据实现技术进行选择;MyBatis框架使用的是JDBC事务。

实现步骤三-开启注解式事务驱动:

@Configuration
@ComponentScan("com.tea")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

Spring事务角色:

事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

Spring事务相关配置:

属性 作用 示例
readOnly 设置是否为只读事务 readOnly=true 只读事务
timeout 设置事务超时时间 timeout = -1(永不超时)
rollbackFor 设置事务回滚异常(class) rollbackFor = {NullPointException.class}
propagation 设置事务传播行为

propagation传播属性:REQUIRED(默认)、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED。


SpringMVC


SpringMVC是一种基于Java实现MVC模型的轻量级Web框架。优点:使用简单,开发便捷(相比于Servlet);灵活性强。


入门案例


导入SpringMVC坐标与Servlet坐标:

<dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
</dependencies>

<build>
    <plugins>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <port>80</port>
          <path>/</path>
        </configuration>
      </plugin>
    </plugins>
</build>

创建SpringMVC控制器类(等同于Servlet功能):

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
//定义表现层控制器bean
@Controller
public class UserController {
    //设置映射路径为/save,即外部访问路径
    @RequestMapping("/save")
    //设置当前操作返回结果为指定json数据(本质上是一个字符串信息)
    @ResponseBody
    public String save(){
        return "{'info':'springmvc'}";
    }
}

设定SpringMVC加载对应的bean:

//springmvc配置类,本质上还是一个spring配置类
@Configuration
@ComponentScan("com.tea.controller")
public class SpringMvcConfig {
}

初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC请求拦截的路径:

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;

//web容器配置类
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    //加载springmvc配置类,产生springmvc容器(本质还是spring容器)
    protected WebApplicationContext createServletApplicationContext() {
        //初始化WebApplicationContext对象
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        //加载指定配置类
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }

    //设置由springmvc控制器处理的请求映射路径
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //加载spring配置类
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}

运行:

访问:http://localhost/save
返回:{'info':'springmvc'}

@RequestMapping :方法注解,类注解。设置当前控制器方法请求访问路径。如果设置在类上统一设置当前控制器方法请求访问路径前缀。

@ResponseBody : 方法注解,设置当前控制器方法响应内容为当前返回值,无需解析。

AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类。AbstractDispatcherServletInitializer提供三个接口方法供用户实现。

createServletApplicationContext()方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围。

getServletMappings()方法,设定SpringMVC对应的请求映射路径,设置为/表示拦截所有请求,任意请求都将转入到SpringMVC进行处理。

createRootApplicationContext()方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式同createServletApplicationContext()。

启动服务器初始化过程:

服务器启动,执行ServletContainersInitConfig类,初始化web容器
执行createServletApplicationContext方法,创建了WebApplicationContext对象
加载SpringMvcConfig
执行@ComponentScan加载对应的bean
加载UserController,每个@RequestMapping的名称对应一个具体的方法
执行getServletMappings方法,定义所有的请求都通过SpringMVC

单次请求过程:

发送请求localhost/save
web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理
解析请求路径/save
由/save匹配执行对应的方法save()
执行save()
检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方

bean加载控制


SpringMVC控制自己相关bean(表现层bean)。SpringMVC加载的bean对应的包均在com.tea.controller包内。

Spring控制自己相关的bean:业务bean(Service)和功能bean(DataSource等)。

Spring相关bean加载控制:

方式一:Spring加载的bean设定扫描范围为com.tea,排除掉controller包内的bean
@ComponentScan({"com.tea.service","com.tea.dao"})

方式二:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等
//excludeFilters属性:设置扫描加载bean时,排除的过滤规则
//type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除
//classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean
@ComponentScan(value="com.tea",
    excludeFilters = @ComponentScan.Filter(
        type = FilterType.ANNOTATION,
        classes = Controller.class
    )
)

方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中

配置SpringMVC和Spring:

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

简化配置SpringMVC和Spring:

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

中文处理


post请求,中文参数处理:

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //乱码处理
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}

参数传递


请求参数传递:

//请求参数
@Controller
public class UserController {

    //普通参数:请求参数与形参名称对应即可完成参数传递
    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name ,int age){
        System.out.println("普通参数传递 name ==> "+name);
        System.out.println("普通参数传递 age ==> "+age);
        return "{'module':'common param'}";
    }

    //普通参数:请求参数名与形参名不同时,使用@RequestParam注解关联请求参数名称与形参名称之间的关系
    @RequestMapping("/commonParamDifferentName")
    @ResponseBody
    public String commonParamDifferentName(@RequestParam("name") String userName , int age){
        System.out.println("普通参数传递 userName ==> "+userName);
        System.out.println("普通参数传递 age ==> "+age);
        return "{'module':'common param different name'}";
    }

    //POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
    @RequestMapping("/pojoParam")
    @ResponseBody
    public String pojoParam(User user){
        System.out.println("pojo参数传递 user ==> "+user);
        return "{'module':'pojo param'}";
    }

    //嵌套POJO参数:嵌套属性按照层次结构设定名称即可完成参数传递
    @RequestMapping("/pojoContainPojoParam")
    @ResponseBody
    public String pojoContainPojoParam(User user){
        System.out.println("pojo嵌套pojo参数传递 user ==> "+user);
        return "{'module':'pojo contain pojo param'}";
    }

    //数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
    @RequestMapping("/arrayParam")
    @ResponseBody
    public String arrayParam(String[] likes){
        System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
        return "{'module':'array param'}";
    }

    //集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
    @RequestMapping("/listParam")
    @ResponseBody
    public String listParam(@RequestParam List<String> likes){
        System.out.println("集合参数传递 likes ==> "+ likes);
        return "{'module':'list param'}";
    }

    //集合参数:json格式
    //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
    @RequestMapping("/listParamForJson")
    @ResponseBody
    public String listParamForJson(@RequestBody List<String> likes){
        System.out.println("list common(json)参数传递 list ==> "+likes);
        return "{'module':'list common for json param'}";
    }

    //POJO参数:json格式
    //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数据映射到形参的实体类对象中,要求属性名称一一对应
    @RequestMapping("/pojoParamForJson")
    @ResponseBody
    public String pojoParamForJson(@RequestBody User user){
        System.out.println("pojo(json)参数传递 user ==> "+user);
        return "{'module':'pojo for json param'}";
    }

    //集合参数:json格式
    //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数组数据映射到形参的保存实体类对象的集合对象中,要求属性名称一一对应
    @RequestMapping("/listPojoParamForJson")
    @ResponseBody
    public String listPojoParamForJson(@RequestBody List<User> list){
        System.out.println("list pojo(json)参数传递 list ==> "+list);
        return "{'module':'list pojo for json param'}";
    }

    //日期参数
    //使用@DateTimeFormat注解设置日期类型数据格式,默认格式yyyy/MM/dd
    @RequestMapping("/dataParam")
    @ResponseBody
    public String dataParam(Date date,
                            @DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
                            @DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2){
        System.out.println("参数传递 date ==> "+date);
        System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
        System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
        return "{'module':'data param'}";
    }

}

@RequestParam 形参注解:required:是否为必传参数、defaultValue:参数默认值。

请求参数key的名称要和POJO中属性的名称一致,否则无法封装。

添加json数据转换相关坐标:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>

开启自动转换json数据的支持:

@Configuration
@ComponentScan("com.tea.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}

@EnableWebMvc注解功能强大,该注解整合了多个功能,此处仅使用其中一部分功能,即json数据进行自动类型转换。

@DateTimeFormat:设定日期时间型数据格式。属性:pattern:指定日期时间格式字符串。工作原理:其内部依赖Converter接口:

public interface Converter<S, T> {
    @Nullable
    T convert(S var1);
}

请求参数年龄数据(String→Integer)
json数据转对象(json → POJO)
日期格式转换(String → Date)

传递日期类型参数必须在配置类上使用@EnableWebMvc注解。其功能之一:根据类型匹配对应的类型转换器。


响应


响应页面和响应数据:

@Controller
public class UserController {

    //响应页面/跳转页面
    //返回值为String类型,设置返回值为页面名称,即可实现页面跳转
    @RequestMapping("/toJumpPage")
    public String toJumpPage(){
        System.out.println("跳转页面");
        return "page.jsp";
    }

    //响应文本数据
    //返回值为String类型,设置返回值为任意字符串信息,即可实现返回指定字符串信息,需要依赖@ResponseBody注解
    @RequestMapping("/toText")
    @ResponseBody
    public String toText(){
        System.out.println("返回纯文本数据");
        return "response text";
    }

    //响应POJO对象
    //返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
    @RequestMapping("/toJsonPOJO")
    @ResponseBody
    public User toJsonPOJO(){
        System.out.println("返回json对象数据");
        User user = new User();
        user.setName("itcast");
        user.setAge(15);
        return user;
    }

    //响应POJO集合对象
    //返回值为集合对象,设置返回值为集合类型,即可实现返回对应集合的json数组数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
    @RequestMapping("/toJsonList")
    @ResponseBody
    public List<User> toJsonList(){
        System.out.println("返回json集合数据");
        User user1 = new User();
        user1.setName("传智播客");
        user1.setAge(15);

        User user2 = new User();
        user2.setName("黑马程序员");
        user2.setAge(12);

        List<User> userList = new ArrayList<User>();
        userList.add(user1);
        userList.add(user2);

        return userList;
    }
}

类型转换器(HttpMessageConverter)。上面return的对象,自动转换成了Json。


RESTful


REST(Representational State Transfer),表现形式状态转换。根据REST风格对资源进行访问称为RESTful。

传统风格资源描述形式:
http://localhost/user/getById?id=1
http://localhost/user/saveUser

REST风格描述形式:
http://localhost/user/1
http://localhost/user

按照REST风格访问资源时使用行为动作区分对资源进行了何种操作:

http://localhost/users		查询全部用户信息			GET(查询)
http://localhost/users/1		查询指定用户信息		GET(查询)
http://localhost/users		添加用户信息				POST(新增/保存)
http://localhost/users		修改用户信息				PUT(修改/更新)
http://localhost/users/1		删除用户信息			DELETE(删除)

RESTful入门:

@Controller
public class UserController {

    //设置当前请求方法为POST,表示REST风格中的添加操作
    @RequestMapping(value = "/users",method = RequestMethod.POST)
    @ResponseBody
    public String save(){
        System.out.println("user save...");
        return "{'module':'user save'}";
    }

    //设置当前请求方法为DELETE,表示REST风格中的删除操作
    //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
    @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id){
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }

    //设置当前请求方法为PUT,表示REST风格中的修改操作
    @RequestMapping(value = "/users",method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user){
        System.out.println("user update..."+user);
        return "{'module':'user update'}";
    }

    //设置当前请求方法为GET,表示REST风格中的查询操作
    //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
    @RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'module':'user getById'}";
    }

    //设置当前请求方法为GET,表示REST风格中的查询操作
    @RequestMapping(value = "/users",method = RequestMethod.GET)
    @ResponseBody
    public String getAll(){
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }
}

@RequestParam用于接收url地址传参或表单传参。@RequestBody用于接收json数据。@PathVariable用于接收路径参数,使用{参数名称}描述路径参数。

后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广。如果发送非json格式数据,选用@RequestParam接收请求参数。采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值。

RESTful快速开发:

@RequestParam和@RequestBody,可以放在方法上,也可以放在类上。
@RestController:等同于@Controller与@ResponseBody两个注解组合功能

@GetMapping  @PostMapping  @PutMapping  @DeleteMapping
设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如@GetMapping对应GET请求

设置对静态资源的访问放行, 配置@ComponentScan扫描到该类:

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    //设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pages/????时候,从/pages目录下查找内容
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}

SSM


数据库:

DROP TABLE IF EXISTS `tbl_book`;
CREATE TABLE `tbl_book`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Spring实战 第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
INSERT INTO `tbl_book` VALUES (2, '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,手写Spring精华思想');
INSERT INTO `tbl_book` VALUES (3, '计算机理论', 'Spring 5 设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式');
INSERT INTO `tbl_book` VALUES (4, '计算机理论', 'Spring MVC+MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
INSERT INTO `tbl_book` VALUES (5, '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
INSERT INTO `tbl_book` VALUES (6, '计算机理论', 'Java核心技术 卷I 基础知识(原书第11版)', 'Core Java 第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新');
INSERT INTO `tbl_book` VALUES (7, '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,大厂面试知识点全覆盖');
INSERT INTO `tbl_book` VALUES (8, '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉');
INSERT INTO `tbl_book` VALUES (9, '计算机理论', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术');
INSERT INTO `tbl_book` VALUES (10, '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
INSERT INTO `tbl_book` VALUES (11, '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
INSERT INTO `tbl_book` VALUES (12, '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');

pom坐标:

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
    
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>
    
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
    
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>
    
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>
</dependencies>

<build>
    <plugins>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <port>80</port>
          <path>/</path>
        </configuration>
      </plugin>
    </plugins>
</build>

resources/jdbc.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_db
jdbc.username=root
jdbc.password=*********

config/JdbcConfig:

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager ds = new DataSourceTransactionManager();
        ds.setDataSource(dataSource);
        return ds;
    }
}

config/MyBatisConfig:

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;

public class MyBatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setTypeAliasesPackage("com.tea.domain");
        return factoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.tea.dao");
        return msc;
    }
}

config/ServletConfig:

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

config/SpringConfig:

@Configuration
@ComponentScan({"com.tea.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}

config/WebMvcConfigurationSupport:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}

config/SpringMvcConfig:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan({"com.tea.controller","com.tea.config"})
@EnableWebMvc
public class SpringMvcConfig {
}

domain/Book:

public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
}

service/BookService:

@Transactional
public interface BookService {
    public boolean save(Book book);
    public boolean update(Book book);
    public boolean delete(Integer id);
    public Book getById(Integer id);
    public List<Book> getAll();
}

service/BookServiceImpl:

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    public boolean save(Book book) {
        return bookDao.save(book) > 0;
    }

    public boolean update(Book book) {
        return bookDao.update(book) > 0;
    }

    public boolean delete(Integer id) {
        return bookDao.delete(id) > 0;
    }

    public Book getById(Integer id) {
        return bookDao.getById(id);
    }

    public List<Book> getAll() {
        return bookDao.getAll();
    }
}

dao/BookDao:

public interface BookDao {

//    @Insert("insert into tbl_book values(null,#{type},#{name},#{description})")
    @Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
    public int save(Book book);

    @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
    public int update(Book book);

    @Delete("delete from tbl_book where id = #{id}")
    public int delete(Integer id);

    @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id);

    @Select("select * from tbl_book")
    public List<Book> getAll();
}

controller/BookController:

@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @PostMapping
    public boolean save(@RequestBody Book book) {
        return bookService.save(book);
    }

    @PutMapping
    public boolean update(@RequestBody Book book) {
        return bookService.update(book);
    }

    @DeleteMapping("/{id}")
    public boolean delete(@PathVariable Integer id) {
        return bookService.delete(id);
    }

    @GetMapping("/{id}")
    public Book getById(@PathVariable Integer id) {
        return bookService.getById(id);
    }

    @GetMapping
    public List<Book> getAll() {
        return bookService.getAll();
    }
}

异常处理


模拟异常:

//模拟业务异常,包装成自定义异常
if(id == 1){
    throw new BusinessException(Code.BUSINESS_ERR,"请不要使用你的技术挑战我的耐性!");
}
//模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
try{
    int i = 1/0;
}catch (Exception e){
    throw new SystemException(Code.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
}

自定义异常exception/SystemException:

//自定义异常处理器,用于封装异常信息,对异常进行分类
public class SystemException extends RuntimeException{
    private Integer code;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public SystemException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public SystemException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }

}

自定义异常exception/BusinessException:

//自定义异常处理器,用于封装异常信息,对异常进行分类
public class BusinessException extends RuntimeException{
    private Integer code;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public BusinessException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }
}

controller/ProjectExceptionAdvice:

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

//@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
    //@ExceptionHandler用于设置当前处理器类对应的异常类型
    @ExceptionHandler(SystemException.class)
    public Result doSystemException(SystemException ex){
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员,ex对象发送给开发人员
        return new Result(ex.getCode(),null,ex.getMessage());
    }

    @ExceptionHandler(BusinessException.class)
    public Result doBusinessException(BusinessException ex){
        return new Result(ex.getCode(),null,ex.getMessage());
    }

    //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
    @ExceptionHandler(Exception.class)
    public Result doOtherException(Exception ex){
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员,ex对象发送给开发人员
        return new Result(Code.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");
    }
}

@RestControllerAdvice 类注解,自带@ResponseBody注解与@Component注解,具备对应的功能。

@ExceptionHandler 方法注解,专用于异常处理的控制器方法上方。设置指定异常的处理方案,功能等同于控制器方法,出现异常后终止原始控制器执行,并转入当前方法执行。


拦截器


拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行。作用:在指定的方法调用前后执行预先设定的代码,阻止原始方法的执行。

拦截器与过滤器区别:

归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强

拦截器类:controller/interceptor/ProjectInterceptor:

@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    //原始方法调用前执行的内容
    //返回值类型可以拦截控制的执行,true放行,false终止
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String contentType = request.getHeader("Content-Type");
        HandlerMethod hm = (HandlerMethod)handler;
        System.out.println("preHandle..."+contentType);
        return true;
    }

    @Override
    //原始方法调用后执行的内容
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override
    //原始方法调用完成后执行的内容
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

配置拦截器方式一:

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //配置拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    }
}

配置拦截器方式二:

@Configuration
@ComponentScan({"com.tea.controller"})
@EnableWebMvc
//实现WebMvcConfigurer接口可以简化开发,但具有一定的侵入性
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;
   
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //配置多拦截器
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
    }
}

拦截器方法参数:

request:请求对象
response:响应对象
handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装

返回值为false,被拦截的处理器将不执行

modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整

拦截器链的运行顺序:

preHandle:与配置顺序相同,必定运行
postHandle:与配置顺序相反,可能不运行
afterCompletion:与配置顺序相反,可能不运行

Spring Boot (旧)

介绍


Spring Boot是便捷搭建基于Spring的工程脚手架。其最主要作用就是帮助开发人员快速的构建庞大的spring项目,并且尽可能的减少一切xml配置,做到开箱即用,迅速上手,让开发人员关注业务而非配置。

我们可以使用Spring Boot创建java应用,并使用java –jar 启动它,就能得到一个生产级别的web工程。


快速搭建


Spring Initializr(jdk1.8默认即可)–> 完善项目信息 –> spring web starter

pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>HelloSpringBoot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>HelloSpringBoot</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Application文件:

@SpringBootApplication
public class HelloSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloSpringBootApplication.class, args);
    }
}

Controller文件:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping(value="/hello")
    public String hello(){
        return "Hello Spring Boot";
    }
}

访问http://localhost:8080/hello即可以返回Hello Spring Boot


Druid


引入:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>

写法一:

创建数据库 springboot_test ,在项目resources下新建 jdbc.properties 文件:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.13:3306/springboot_test
jdbc.username=root
jdbc.password=123456

JdbcConfig 类:

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;

@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
    @Value("${jdbc.url}")
    String url;
    @Value("${jdbc.driverClassName}")
    String driverClassName;
    @Value("${jdbc.username}")
    String username;
    @Value("${jdbc.password}")
    String password;

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

写法二:@ConfigurationProperties可以在类上,也可以在方法上:

在项目resources下 application.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.13:3306/springboot_test
jdbc.username=root
jdbc.password=123456

JdbcConfig 类:

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class JdbcConfig {

    //prefix = "jdbc" 中的jdbc指的是 jdbc.password=123456 中的jdbc
    @Bean
    @ConfigurationProperties(prefix = "jdbc")
    public DataSource dataSource() {
        return new DruidDataSource();
    }
}

两种写法写完后,使用:

@Autowired
private DataSource dataSource;

System.out.println("dataSource: " + dataSource);

yml文件配置


yaml与properties配置文件除了展示形式不相同以外,其它功能和作用都是一样的;在项目中原路的读取方式不需要改变。

yml配置文件的特征:树状层级结构展示配置项;配置项之间如果有关系的话需要分行空两格;配置项如果有值的话,那么需要在 :之后空一格再写配置项值;

jdbc:
  driverClassName: com.mysql.jdbc.Driver
  url: jdbc:mysql://127.0.0.1:3306/hehe
  username: root
  password: 123456

key:
  abc: cba
  def:
    - g
    - h
    - j

多个yml配置文件,在spring boot中是被允许的。这些配置文件的名称必须为 application-***.yml,并且这些配置文件必须要在application.yml配置文件中激活之后才可以使用。

#激活配置文件;需要指定其它的配置文件名称,abc,def就是application-abc.yml,application-def.yml
spring:
  profiles:
    active: abc,def

如果properties和yml配置文件同时存在在spring boot项目中;那么这两类配置文件都有效。在两个配置文件中如果存在同名的配置项的话会以properties文件的为主。


lombok应用


添加lombok对应的依赖到项目pom.xml文件:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

在Bean上使用:

@Data :自动提供getter和setter、hashCode、equals、toString等方法
@Getter:自动提供getter方法
@Setter:自动提供setter方法
@Slf4j:自动在bean中提供log变量,其实用的是slf4j的日志功能。

修改项目tomcat端口


yml文件方式

#tomcat端口
server:
  port: 80

application.properties中修改端口和path:

server.port=8082
server.servlet.context-path=/shiyanlou/

静态资源


默认的静态资源路径为:

classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public

SpringMVC拦截器


编写拦截器(实现HandlerInterceptor):

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.debug("这是MyInterceptor的preHandle方法。");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.debug("这是MyInterceptor的postHandle方法。");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.debug("这是MyInterceptor的afterCompletion方法。");
    }
}

编写配置类实现 WebMvcConfigurer,在该类中添加各种组件:

import com.jiangtea.interceptor.MyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    //注册拦截器
    @Bean
    public MyInterceptor myInterceptor(){
        return new MyInterceptor();
    }

    //添加拦截器到spring mvc拦截器链
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor()).addPathPatterns("/*");
    }
}

设置日志级别:

#日志记录级别
logging:
  level:
    com.jiangtea: debug
    org.springframework: info

整合事务和连接池


配置Spring Boot自带默认的 hikari数据库连接池和使用 @Transactional注解进行事务配置。

pom.xml文件中:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

application.properties:

spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=hikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1

UserService类:

import com.jiangtea.pojo.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
    //根据id查询
    public User queryById(Long id){
        return new User();
    }
    //新增保存用户
    @Transactional
    public void saveUser(User user){
        System.out.println("新增用户... ");
    }
}

使用:

@Autowired
private DataSource dataSource;

Spring Boot整合Mybatis


pom.xml文件中:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.1</version>
</dependency>

application.yml配置:

mybatis:
  # 实体类别名包路径
  type-aliases-package: com.jiangtea.pojo
  # 映射文件路径
  # mapper-locations: classpath:mappers/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

扫描mybatis所有的业务mapper接口配置:

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
// 扫描mybatis所有的业务mapper接口
@MapperScan("com.jiangtea.mapper")
public class HelloSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloSpringBootApplication.class, args);
    }
}

整合通用Mapper


pom.xml文件中:

<!-- 通用mapper -->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.1.5</version>
</dependency>

继承Mapper<User>:

import com.jiangtea.pojo.User;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {
}

修改启动引导类Application中的Mapper扫描注解,注意修改成tk.mybatis.spring.annotation.MapperScan:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
// 扫描mybatis所有的业务mapper接口
@MapperScan("com.jiangtea.mapper")
public class HelloSpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloSpringBootApplication.class, args);
    }
}

修改User实体类添加jpa注解:

import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Data
@Table(name = "tb_user")
public class User {
    @Id
    //主键回填
    @KeySql(useGeneratedKeys = true)
    private Long id;
    private String userName;
    private String password;
    private String name;
    private Integer age;
    private Integer sex;
    private Date birthday;
    private String note;
    private Date created;
    private Date updated;
}

UserService:

import com.jiangtea.mapper.UserMapper;
import com.jiangtea.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    //根据id查询
    public User queryById(Long id){
        return userMapper.selectByPrimaryKey(id);
    }
    //新增保存用户
    @Transactional
    public void saveUser(User user){
        System.out.println("新增用户... ");
        //选择性新增;如果属性为空则该属性不会出现在insert语句上
        userMapper.insertSelective(user);
        int i = 1/0;
    }
}

编写Controller:

import com.jiangtea.pojo.User;
import com.jiangtea.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;

@RestController
public class HelloController {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private UserService userService;

    /**
     * 根据用户id查询用户
     * @param id 用户id
     * @return 用户
     */
    @GetMapping("/user/{id}")
    public User queryById(@PathVariable Long id){
        return userService.queryById(id);
    }
    @GetMapping("hello")
    public String hello(){
        System.out.println(" DataSource = " + dataSource);
        return "Hello, Spring Boot!";
    }
}

开始测试-测试地址:http://localhost:8082/user/7 :


Spring Boot整合Junit


添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

在类UserService上按 ctrl+shift+T 选择 JUnit4 创建测试类:

import com.jiangtea.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Date;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
    @Autowired
    private UserService userService;
    @Test
    public void queryById() {
        User user = userService.queryById(8L);
        System.out.println("user = " + user);
    }
    @Test
    public void saveUser() {
        User user = new User();
        user.setUserName("test2");
        user.setName("test2");
        user.setAge(13);
        user.setPassword("123456");
        user.setSex(1);
        user.setCreated(new Date());
        userService.saveUser(user);
    }
}

注意添加 @SpringBootTest


Spring Boot整合redis


添加依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置application.yml中修改redis的连接参数(redis需要启动):

spring:
  redis:
    host: localhost
    port: 6379

编写测试类应用RedisTemplate操作redis中的5种数据类型(string/hash/list/set/sorted set)

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import java.util.Set;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void test(){
        //string 字符串
        //redisTemplate.opsForValue().set("str", "hehe");
        redisTemplate.boundValueOps("str").set("hehe");
        System.out.println("str = " + redisTemplate.opsForValue().get("str"));

        //hash 散列
        redisTemplate.boundHashOps("h_key").put("name", "hehe");
        redisTemplate.boundHashOps("h_key").put("age", 13);
        //获取所有域
        Set set = redisTemplate.boundHashOps("h_key").keys();
        System.out.println(" hash散列的所有域:" + set);
        //获取所有值
        List list = redisTemplate.boundHashOps("h_key").values();
        System.out.println(" hash散列的所有域的值:" + list);

        //list 列表
        redisTemplate.boundListOps("l_key").leftPush("c");
        redisTemplate.boundListOps("l_key").leftPush("b");
        redisTemplate.boundListOps("l_key").leftPush("a");
        //获取全部元素
        list = redisTemplate.boundListOps("l_key").range(0, -1);
        System.out.println(" list列表中的所有元素:" + list);

        // set 集合
        redisTemplate.boundSetOps("s_key").add("a", "b", "c");
        set = redisTemplate.boundSetOps("s_key").members();
        System.out.println(" set集合中的所有元素:" + set);

        // sorted set 有序集合
        redisTemplate.boundZSetOps("z_key").add("a", 30);
        redisTemplate.boundZSetOps("z_key").add("b", 20);
        redisTemplate.boundZSetOps("z_key").add("c", 10);
        set = redisTemplate.boundZSetOps("z_key").range(0, -1);
        System.out.println(" zset有序集合中的所有元素:" + set);
    }
}

Spring Boot项目部署


pom添加打包组件:

<build>
  <plugins>
     <!-- 打jar包时如果不配置该插件,打出来的jar包没有清单文件 -->
      <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
  </plugins>
</build>

可以点击右边栏maven的Lifecycle中的package进行打包。

部署:java -jar 打好的jar包名字


Spring Boot (新)

入门


SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程。

创建新模块,选择Spring Initializr,选择当前模块需要使用的技术集 Web -> Spring Web

基础文件-pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.tea</groupId>
    <artifactId>SpringBootDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBootDemo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

基础文件-Application:

@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}

打包并运行:

右边栏Maven -> 项目下Lifecycle -> package -> 生成jar包在target文件夹下

执行启动指令
java –jar 项目.jar

起步依赖


SpringBoot中已经有了各种依赖的的版本号,及依赖。

默认的Tomcat服务改成Jetty:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!--web起步依赖环境中,排除Tomcat起步依赖-->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--添加Jetty起步依赖,版本由SpringBoot的starter控制-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

基础配置


SpringBoot提供了多种属性配置方式:

application.properties文件:

server.port=80

application.yml文件:

server:
  port: 81

application.yaml文件:

server:
  port: 82

pringBoot配置文件加载顺序:

application.properties  >  application.yml  >  application.yaml

自动提示功能消失解决方案:

File -> Project Structure -> Facets -> Customize Spring Boot -> 添加配置文件

yaml


yaml语法规则:

大小写敏感
属性层级关系使用多行描述,每行结尾使用冒号结束
使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
# 表示注释

核心规则:数据前面要加空格与冒号隔开

数组数据:

subject:
 - Java
 - 前端    
 - 大数据

application.yaml:

lesson: SpringBoot

server:
  port: 80

enterprise:
  name: itcast
  age: 16
  tel: 4006184000
  subject:
    - Java
    - 前端
    - 大数据

读取数据三种格式- @Value(直接读取):

//使用@Value读取单一属性数据
@Value("${lesson}")
private String lesson;
@Value("${server.port}")
private Integer port;
@Value("${enterprise.subject[0]}")
private String subject_00;

读取数据三种格式-Environment(封装后读取):

//使用Environment封装全配置数据
@Autowired
private Environment environment;

System.out.println(environment.getProperty("lesson"));
System.out.println(environment.getProperty("server.port"));
System.out.println(environment.getProperty("enterprise.age"));
System.out.println(environment.getProperty("enterprise.subject[1]"));

读取数据三种格式-实体类封装属性(封装后读取):

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//封装yaml对象格式数据必须先声明当前实体类受Spring管控
@Component
//使用@ConfigurationProperties注解定义当前实体类读取配置属性信息,通过prefix属性设置读取哪个数据
@ConfigurationProperties(prefix = "enterprise")
public class Enterprise {
    private String name;
    private Integer age;
    private String tel;
    private String[] subject;
}


@Autowired
private Enterprise enterprise;

多环境开发配置


application.yml文件:

#设置启用的环境
spring:
  profiles:
    active: dev

---
#开发
spring:
  config:
    activate:
      on-profile: dev
server:
  port: 80
---
#生产
spring:
  profiles: pro
server:
  port: 81
---
#测试
spring:
  profiles: test
server:
  port: 82
---

properties文件多环境启动:

主启动配置文件application.properties
spring.profiles.active=pro

环境分类配置文件application-pro.properties
server.port=80

环境分类配置文件application-dev.properties
server.port=81

环境分类配置文件application-test.properties
server.port=82

File -> Setting -> File Encodings 中编码都改为UTF-8。

命令行启动多环境:

java –jar springboot.jar –-spring.profiles.active=test
java –jar springboot.jar –-server.port=88
java –jar springboot.jar –-server.port=88 –-spring.profiles.active=test

Maven与SpringBoot多环境兼容-Maven的设置大于yml的设置。

在yml中读取Maven的设置:

maven:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <encoding>UTF-8</encoding>
                <useDefaultDelimiters>true</useDefaultDelimiters>
            </configuration>
        </plugin>
    </plugins>
</build>

<profiles>
    <!--开发环境-->
    <profile>
        <id>dev</id>
        <properties>
            <profile.active>dev</profile.active>
        </properties>
    </profile>
    <!--生产环境-->
    <profile>
        <id>pro</id>
        <properties>
            <profile.active>pro</profile.active>
        </properties>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
    </profile>
    <!--测试环境-->
    <profile>
        <id>test</id>
        <properties>
            <profile.active>test</profile.active>
        </properties>
    </profile>
</profiles>

application.yml读取maven中activeByDefault的那个环境:

#设置启用的环境
spring:
  profiles:
    active: ${profile.active}

SpringBoot中4级配置文件:

1级: file :config/application.yml	【最高】
2级: file :application.yml
3级:classpath:config/application.yml【项目中】
4级:classpath:application.yml	 【最低,项目中】

作用:
1级与2级留做系统打包后设置通用属性
3级与4级用于系统开发阶段设置通用属性

整合JUnit


@SpringBootTest
class SpringbootTestApplicationTests {

    @Autowired
    private BookService bookService;

    @Test
    public void save() {
        bookService.save();
    }
}

如果测试类在SpringBoot启动类的包或子包中,可以省略启动类的设置,也就是省略classes的设定

@SpringBootTest(classes = Springboot07JunitApplication.class)

整合ssm


SpringBoot整合Spring-不存在
SpringBoot整合SpringMVC-不存在
SpringBoot整合MyBatis-主要

SpringBoot整合MyBatis及SSM:

创建项目-需要使用的技术集:

We -> Spring Web
SQL -> MyBatis Framework
SQL -> MySQL Driver

pom.xml文件-导入druid:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.tea</groupId>
    <artifactId>springboot_ssm</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_ssm</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml配置数据源、端口等:

server:
  port: 80

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db?servierTimezone=UTC #SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区
    username: root
    password: *********

dao设置 @Mapper:

@Mapper
public interface BookDao {
    @Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
    public int save(Book book);

    @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
    public int update(Book book);

    @Delete("delete from tbl_book where id = #{id}")
    public int delete(Integer id);

    @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id);

    @Select("select * from tbl_book")
    public List<Book> getAll();
}

页面放置在resources目录下的static目录中。


Maven高级

分模块开发


创建Maven模块,gav导入主模块:

<dependency>
    <groupId>com.tea</groupId>
    <artifactId>SpringBootDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

子模块实行:右边栏Maven -> Lifecycle -> install。通过maven指令安装模块到本地仓库(install指令)


依赖管理


依赖具有传递性:

直接依赖:在当前项目中通过依赖配置建立的依赖关系
间接依赖:被资源的资源如果依赖其他资源,当前项目间接依赖其他资源

依赖传递冲突问题:

路径优先:当依赖中出现相同的资源时,层级越深,优先级越低,层级越浅,优先级越高
声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序靠后的
特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的

可选依赖:可选依赖指对外隐藏当前所依赖的资源————不透明

<dependency>
    <groupId>com.tea</groupId>
    <artifactId>maven_03_pojo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依赖传递性-->
    <optional>true</optional>
</dependency>

排除依赖:排除依赖指主动断开依赖的资源,被排除的资源无需指定版本————不需要

<dependency>
  <groupId>com.tea</groupId>
  <artifactId>maven_04_dao</artifactId>
  <version>1.0-SNAPSHOT</version>
  <!--排除依赖是隐藏当前资源对应的依赖关系-->
  <exclusions>
    <exclusion>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
    </exclusion>
    <exclusion>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </exclusion>
  </exclusions>
</dependency>

聚合


聚合:将多个模块组织成一个整体,同时进行项目构建的过程称为聚合。

聚合工程:通常是一个不具有业务功能的“空”工程(有且仅有一个pom文件)。

作用:使用聚合工程可以将多个工程编组,通过对聚合工程进行构建,实现对所包含的模块进行同步构建。当工程中某个模块发生更新(变更)时,必须保障工程中与已更新模块关联的模块同步更新,此时可以使用聚合工程来解决批量模块同步构建的问题。

聚合工程:创建Maven模块,设置打包类型为pom

<packaging>pom</packaging>

<modules>
    <module>../maven_ssm</module>
    <module>../maven_pojo</module>
    <module>../maven_dao</module>
</modules>

聚合工程中所包含的模块在进行构建时会根据模块间的依赖关系设置构建顺序,与聚合工程中模块的配置书写位置无关。参与聚合的工程无法向上感知是否参与聚合,只能向下配置哪些模块参与本工程的聚合。


继承


继承:继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。作用:简化配置;减少版本冲突。

创建Maven模块,设置打包类型为pom:

<packaging>pom</packaging>

在父工程的pom文件中,配置依赖关系(子工程将沿用父工程中的依赖关系):

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    .......
    .......
</dependencies>

在父工程的pom文件中,配置子工程中可选的依赖关系

<!--定义依赖管理-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
    </dependencies>
</dependencyManagement>

在子工程中,配置当前工程所继承的父工程:

<!--配置当前工程继承自parent工程-->
<parent>
    <groupId>com.tea</groupId>
    <artifactId>maven_01_parent</artifactId>
    <version>1.0-RELEASE</version>
    <relativePath>../maven_01_parent/pom.xml</relativePath>
</parent>

在子工程中,配置使用父工程中可选依赖的坐标:

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
    </dependency>
</dependencies>

子工程中使用父工程中的可选依赖时,仅需要提供群组id和项目id,无需提供版本,版本由父工程统一提供,避免版本冲突。子工程中还可以定义父工程中没有定义的依赖关系。

聚合用于快速构建项目,继承用于快速配置。

相同点:

聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
聚合与继承均属于设计型模块,并无实际的模块内容

不同点:

聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

属性


properties标签定义版本:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
</dependencies>

<!--定义属性-->
<properties>
    <spring.version>5.2.10.RELEASE</spring.version>
</properties>

jdbc.properties资源文件引用pom属性:

# jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=root

<!--pom定义属性-->
<properties>
    <jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db</jdbc.url>
</properties>

<!--pom开启资源文件目录加载属性的过滤器-->
<build>
    <resources>
        <!--设置资源目录,并设置能够解析${}-->
        <resource>
            <directory>${project.basedir}/src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

配置maven打war包时,忽略web.xml检查:

<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.2.3</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
    </plugins>
</build>

属性列表:

自定义属性(常用)		${自定义属性名}
内置属性				${内置属性名}
Setting属性			${setting.属性名}
Java系统属性			${系统属性分类.系统属性名} 
环境变量属性			${env.环境变量属性名}

工程版本:

SNAPSHOT(快照版本)
项目开发过程中临时输出的版本,称为快照版本
快照版本会随着开发的进展不断更新
RELEASE(发布版本)
项目开发到进入阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构件文件是稳定的,即便进行功能的后续开发,也不会改变当前发布版本内容,这种版本称为发布版本

发布版本:

alpha版
beta版
纯数字版

多环境开发


pom里配置多环境:

<!--配置多环境-->
<profiles>
    <!--开发环境-->
    <profile>
        <id>env_dep</id>
        <properties>
            <jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
        </properties>
        <!--设定是否为默认启动环境-->
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
    </profile>
    <!--生产环境-->
    <profile>
        <id>env_pro</id>
        <properties>
            <jdbc.url>jdbc:mysql://127.2.2.2:3306/ssm_db</jdbc.url>
        </properties>
    </profile>
    <!--测试环境-->
    <profile>
        <id>env_test</id>
        <properties>
            <jdbc.url>jdbc:mysql://127.3.3.3:3306/ssm_db</jdbc.url>
        </properties>
    </profile>
</profiles>

设置activation标签指定多环境。或者 mvn 指令 –P 环境定义id 例如 mvn install –P pro_env

跳过测试:

mvn 指令 –D skipTests
mvn install –D skipTests

执行的项目构建指令必须包含测试生命周期,否则无效果。例如执行compile生命周期,不经过test生命周期。

细粒度控制跳过测试:

<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.22.1</version>
  <configuration>
    <skipTests>true</skipTests><!--设置跳过测试-->
    <includes> <!--包含指定的测试用例-->
      <include>**/User*Test.java</include>
    </includes>
    <excludes><!--排除指定的测试用例-->
      <exclude>**/User*TestCase.java</exclude>
    </excludes>
  </configuration>
</plugin>

私服


私服是一台独立的服务器,用于解决团队内部的资源共享与资源同步问题。

Sonatype公司的一款maven私服产品:Nexus。

下载地址:https://help.sonatype.com/repomanager3/download

启动服务器(命令行启动):

nexus.exe /run nexus

访问服务器(默认端口:8081):

http://localhost:8081

修改基础配置信息:安装路径下etc目录中nexus-default.properties文件保存有nexus基础配置信息,例如默认访问端口。

修改服务器运行配置信息:安装路径下bin目录中nexus.vmoptions文件保存有nexus服务器启动对应的配置信息,例如默认占用内存空间。

本地仓库访问私服权限设置(setting.xml文件中):

<servers>
  <server>
    <id>tea-release</id>
    <username>admin</username>
    <password>admin</password>
  </server>
  <server>
    <id>tea-snapshots</id>
    <username>admin</username>
    <password>admin</password>
  </server>
</servers>

本地仓库访问私服地址设置(setting.xml文件中):

<mirrors>
  <mirror>
    <id>nexus-tea</id>
    <mirrorOf>*</mirrorOf>
    <url>http://localhost:8081/repository/maven-public/</url>
  </mirror>
</mirrors>

工程上传到私服服务器设置(工程pom文件中):

<distributionManagement>
    <repository>
      	<!--和maven/settings.xml中server中的id一致,表示使用该id对应的用户名和密码-->
        <id>tea-nexus</id>
      	<!--如果jar的版本是release版本,那么就上传到这个仓库,根据自己情况修改-->
        <url>http://localhost:8081/repository/tea-releases/</url>
    </repository>
    <snapshotRepository>
      	<!--和maven/settings.xml中server中的id一致,表示使用该id对应的用户名和密码-->
        <id>tea-nexus</id>
      	<!--如果jar的版本是snapshot版本,那么就上传到这个仓库,根据自己情况修改-->
        <url>http://localhost:8081/repository/tea-snapshots/</url>
    </snapshotRepository>
</distributionManagement>

注意:要和maven的settings.xml中server中定义的tea-nexus对应

发布资源到私服命令:

mvn deploy

MyBatis Plus

入门案例


MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率。

官网:

https://mybatis.plus/	
https://mp.baomidou.com/

手动添加mp起步依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>

设置Jdbc参数(application.yml

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
    username: root
    password: *******

定义数据接口,继承BaseMapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserDao extends BaseMapper<User> {
}

测试功能:

@Autowired
private UserDao userDao;

@Test
void testGetAll() {
    List<User> userList = userDao.selectList(null);
    System.out.println(userList);
}

MyBatisPlus特性:

无侵入:只做增强不做改变,不会对现有工程产生影响
强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作
支持 Lambda:编写查询条件无需担心字段写错
支持主键自动生成
内置分页插件

CRUD操作


新增:

int insert(T t)

删除:

int deleteById(Serializable id)

修改:

int updateById(T t)

根据id查询:

T selectById(Serializable id)

查询全部:

List<T> selectList()

Lombok


坐标:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

常用注解:@Data,为当前实体类在编译期设置对应的get/set方法,无参/无参构造方法,toString方法,hashCode方法,equals方法等。

生成getter和setter方法:@Getter、@Setter
生成toString方法:@ToString
生成equals和hashcode方法:@EqualsAndHashCode
统一成以上所有:@Data

生成空参构造: @NoArgsConstructor
生成全参构造: @AllArgsConstructor

lombok还给我们提供了builder的方式创建对象,好处就是可以链式编程。 @Builder【扩展】

分页功能


分页查询:

IPage<T> selectPage(IPage<T> page)

按条件查询:

IPage<T> selectPage(Wrapper<T> queryWrapper)

设置分页拦截器作为Spring管理的bean:

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加分页拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}

执行分页查询:

//分页查询
@Test
void testSelectPage(){
    //1 创建IPage分页对象,设置分页参数
    IPage<User> page=new Page<>(1,3);
    //2 执行分页查询
    userDao.selectPage(page,null);
    //3 获取分页结果
    System.out.println("当前页码值:"+page.getCurrent());
    System.out.println("每页显示数:"+page.getSize());
    System.out.println("总页数:"+page.getPages());
    System.out.println("总条数:"+page.getTotal());
    System.out.println("当前页数据:"+page.getRecords());
}

开启MyBatisPlus日志:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
    username: root
    password: *******
# 开启mp的日志(输出到控制台)
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

取消SpringBoot启动banner图标:

spring:
  main:
    banner-mode: off # 关闭SpringBoot启动图标(banner)

取消MybatisPlus启动banner图标:

# mybatis-plus日志控制台输出
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    banner: off # 关闭mybatisplus启动图标

条件查询


MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合

方式一:按条件查询

//方式一:按条件查询
QueryWrapper<User> qw=new QueryWrapper<>();
qw.lt("age", 18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);

方式二:lambda格式按条件查询

//方式二:lambda格式按条件查询
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getAge, 10);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);

方式三:lambda格式按条件查询(推荐):

//方式三:lambda格式按条件查询
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);

组合条件


并且关系(and):

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//并且关系:10到30岁之间
lqw.lt(User::getAge, 30).gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);

或者关系(or):

//或者关系
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//或者关系:小于10岁或者大于30岁
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);

NULL值处理


if语句控制条件追加:

Integer minAge=10;  //将来有用户传递进来,此处简化成直接定义变量了
Integer maxAge=null;  //将来有用户传递进来,此处简化成直接定义变量了
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
if(minAge!=null){
    lqw.gt(User::getAge, minAge);
}
if(maxAge!=null){
    lqw.lt(User::getAge, maxAge);
}
List<User> userList = userDao.selectList(lqw);
userList.forEach(System.out::println);

条件参数控制:

Integer minAge=10;  //将来有用户传递进来,此处简化成直接定义变量了
Integer maxAge=null;  //将来有用户传递进来,此处简化成直接定义变量了
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//参数1:如果表达式为true,那么查询才使用该条件
lqw.gt(minAge!=null,User::getAge, minAge);
lqw.lt(maxAge!=null,User::getAge, maxAge);
List<User> userList = userDao.selectList(lqw);
userList.forEach(System.out::println);

条件参数控制(链式编程):

Integer minAge=10;  //将来有用户传递进来,此处简化成直接定义变量了
Integer maxAge=null;  //将来有用户传递进来,此处简化成直接定义变量了
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//参数1:如果表达式为true,那么查询才使用该条件
lqw.gt(minAge!=null,User::getAge, minAge)
   .lt(maxAge!=null,User::getAge, maxAge);
List<User> userList = userDao.selectList(lqw);
userList.forEach(System.out::println);

查询投影


查询结果包含模型类中部分属性:

/*LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.select(User::getId, User::getName, User::getAge);*/
//或者
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("id", "name", "age", "tel");
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);

查询结果包含模型类中未定义的属性:

QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("count(*) as count, tel");
lqw.groupBy("tel");
List<Map<String, Object>> userList = userDao.selectMaps(lqw);
System.out.println(userList);

查询条件


多条件查询有哪些组合:

范围匹配(> 、 = 、between)
模糊匹配(like)
空判定(null)
包含性匹配(in)
分组(group)
排序(order)
……

用户登录(eq匹配):

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//等同于=
lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);

购物设定价格区间、户籍设定年龄区间(le ge匹配 或 between匹配):

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//范围查询 lt le gt ge eq between
lqw.between(User::getAge, 10, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);

查信息,搜索新闻(非全文检索版:like匹配):

LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//模糊匹配 like
lqw.likeLeft(User::getName, "J");
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);

统计报表(分组查询聚合函数):

QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("gender","count(*) as nums");
qw.groupBy("gender");
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);

更多查询条件设置参看https://mybatis.plus/guide/wrapper.html#abstractwrapper。


字段映射与表名映射


表字段与编码属性设计不同步:在模型类属性上方,使用**@TableField属性注解,通过value**属性,设置当前属性对应的数据库表中的字段关系。

@TableField(value="pwd")
private String password;

编码中添加了数据库中未定义的属性:在模型类属性上方,使用**@TableField注解,通过exist**属性,设置属性在数据库表字段中是否存在,默认为true。此属性无法与value合并使用。

@TableField(exist = false)
private Integer online;

采用默认查询开放了更多的字段查看权限:在模型类属性上方,使用**@TableField注解,通过select**属性:设置该属性是否参与查询。此属性与select()映射配置不冲突。

@TableField(value="pwd",select = false)
private String password;

表名与编码开发设计不同步:在模型类上方,使用**@TableName注解,通过value**属性,设置当前类对应的数据库表名称。

@TableName("tbl_user")
public class User {}

代码:

@Data
@TableName("tbl_user")
public class User {
    /*
        id为Long类型,因为数据库中id为bigint类型,
        并且mybatis有自己的一套id生成方案,生成出来的id必须是Long类型
     */
    private Long id;
    private String name;
    @TableField(value = "pwd",select = false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false) //表示online字段不参与CRUD操作
    private Boolean online;
}

id生成策略控制(Insert)


id生成策略控制(@TableId注解):属性注解,模型类中用于表示主键的属性定义上方,设置当前类中主键属性的生成策略。type:设置主键属性的生成策略,值参照IdType枚举值:

public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
}

全局策略配置(id生成和表名前缀):

mybatis-plus:
  global-config:
    db-config:
      id-type: assign_id
      table-prefix: tbl_

id生成策略控制:

AUTO(0):使用数据库id自增策略控制id生成
NONE(1):不设置id生成策略
INPUT(2):用户手工输入id
ASSIGN_ID(3):雪花算法生成id(可兼容数值型与字符串型)
ASSIGN_UUID(4):以UUID生成算法作为id生成策略

多记录操作(批量Delete/Select)


按照主键删除多条记录:

//删除指定多条数据
List<Long> list = new ArrayList<>();
list.add(1402551342481838081L);
list.add(1402553134049501186L);
list.add(1402553619611430913L);

userDao.deleteBatchIds(list);

根据主键查询多条记录:

//查询指定多条数据
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(3L);
list.add(4L);
userDao.selectBatchIds(list);

逻辑删除(Delete/Update)


在实际环境中,如果想删除一条数据,是否会真的从数据库中删除该条数据?删除操作业务问题:业务数据从数据库中丢弃

逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中。

数据库表中添加逻辑删除标记字段

实体类中添加对应字段,并设定当前字段为逻辑删除标记字段:

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

@Data
public class User {
    private Long id;
    //逻辑删除字段,标记当前记录是否被删除
    @TableLogic
    private Integer deleted;
}

配置逻辑删除字面值:

mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      # 逻辑删除字段名
      logic-delete-field: deleted
      # 逻辑删除字面值:未删除为0
      logic-not-delete-value: 0
      # 逻辑删除字面值:删除为1
      logic-delete-value: 1

逻辑删除本质:逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。

执行SQL语句:

UPDATE tbl_user SET deleted=1 WHERE id=? AND deleted=0

乐观锁(Update)


乐观锁主张的思想是什么?业务并发现象带来的问题:秒杀

数据库表中添加锁标记字段

实体类中添加对应字段,并设定当前字段为逻辑删除标记字段:

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
@Data
public class User {
    private Long id;
    @Version
    private Integer version;
}

配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装:

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        //1.定义Mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2.添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}

使用乐观锁机制在修改前必须先获取到对应数据的verion方可正常进行:

@Test
public void testUpdate() {
    /*User user = new User();
    user.setId(3L);
    user.setName("Jock666");
    user.setVersion(1);
    userDao.updateById(user);*/
    
    //1.先通过要修改的数据id将当前数据查询出来
    //User user = userDao.selectById(3L);
    //2.将要修改的属性逐一设置进去
    //user.setName("Jock888");
    //userDao.updateById(user);
    
    //1.先通过要修改的数据id将当前数据查询出来
    User user = userDao.selectById(3L);     //version=3
    User user2 = userDao.selectById(3L);    //version=3
    user2.setName("Jock aaa");
    userDao.updateById(user2);              //version=>4
    user.setName("Jock bbb");
    userDao.updateById(user);               //verion=3?条件还成立吗?
}

执行修改前先执行查询语句:

SELECT id,name,age,tel,deleted,version FROM tbl_user WHERE id=? 

执行修改时使用version字段作为乐观锁检查依据:

UPDATE tbl_user SET name=?, age=?, tel=?, version=? WHERE id=? AND version=?

代码生成器


工程搭建和基本代码编写:

第一步:创建SpringBoot工程,添加代码生成器相关依赖,其他依赖自行添加:

<!--代码生成器-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>

<!--velocity模板引擎-->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

第二步:编写代码生成器类

import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
public class Generator {
    public static void main(String[] args) {
        //1. 创建代码生成器对象,执行生成代码操作
        AutoGenerator autoGenerator = new AutoGenerator();
        //2. 数据源相关配置:读取数据库中的信息,根据数据库表结构生成代码
        DataSourceConfig dataSource = new DataSourceConfig();
        dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("*******");
        autoGenerator.setDataSource(dataSource);
         //3. 执行生成操作
        autoGenerator.execute();
    }
}

开发者自定义配置:

设置全局配置:

//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java");    //设置代码生成位置
globalConfig.setOpen(false);    //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("Author");    //设置作者
globalConfig.setFileOverride(true);     //设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao");    //设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID);   //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);

设置包名相关配置:

//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa");   //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain");    //设置实体类包名
packageInfo.setMapper("dao");   //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);

策略设置:

//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user");  //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_");  //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名  例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true);    //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version");  //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted");  //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true);  //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);

Redis

入门介绍


Redis是一个基于内存的key-value结构数据库。

特点:基于内存存储,读写性能高。适合存储热点数据(热点商品、资讯、新闻)。

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker,翻译为:Redis是一个开源的内存中的数据结构存储系统,它可以用作:数据库、缓存和消息中间件。

官网:https://redis.io

Redis中文网:https://www.redis.net.cn

Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。它存储的value类型比较丰富,也被称为结构化的NoSql数据库。

NoSql(Not Only SQL),不仅仅是SQL,泛指非关系型数据库。NoSql数据库并不是要取代关系型数据库,而是关系型数据库的补充。

关系型数据库(RDBMS):

Mysql
Oracle
DB2
SQLServer

非关系型数据库(NoSql):

Redis
Mongo db
MemCached

Redis 应用场景:

缓存
任务队列
消息队列
分布式锁

安装启动


Redis安装包分为windows版和Linux版:

Windows版下载地址:https://github.com/microsoftarchive/redis/releases
Linux版下载地址: https://download.redis.io/releases/ 

在Linux系统安装Redis步骤:

1.将Redis安装包上传到Linux
2.解压安装包,命令:tar -zxvf redis-4.0.0.tar.gz -C /usr/local
3.安装Redis的依赖环境gcc,命令:yum install gcc-c++
4.进入/usr/local/redis-4.0.0,进行编译,命令:make
5.进入redis的src目录,进行安装,命令:make install

Linux中redis服务启动:

在src目录下执行
./redis-server,默认端口号为6379
Ctrl + C停止Redis服务

在src目录下执行
./redis-cli,进入客户端
推出客户端:exit

修改可以后台运行:进入根目录
vim redis.conf
修改为
daemonize yes
根目录下执行
src/redis-server ./redis.conf

Windows系统中启动Redis:

直接双击redis-server.exe即可启动Redis服务,redis服务默认端口号为6379
双击redis-cli.exe即可启动Redis客户端
Ctrl + C停止Redis服务

设置密码:

进入根目录,修改配置文件
vim redis.conf
修改为
requirepass 密码

输入密码:

启动客户端时候输入:
src/redis-cli -h localhost -p 6379 -a 密码
在客户端内:
auth 密码

设置redis可以远程链接:

vim redis.conf
注释掉,修改为:
# bind 127.0.0.1

redis默认提供了16个数据库。配置文件redis.windows.conf


数据类型


Redis存储的是key-value结构的数据,其中key是字符串类型,value有5种常用的数据类型:

字符串 string
哈希 hash	适合存储对象
列表 list	按照插入顺序排序,可以有重复元素
集合 set		无序集合,没有重复元素
有序集合 sorted set	有序集合,没有重复元素

命令


Redis 中字符串类型常用命令:

SET key value			设置指定key的值
GET key			获取指定key的值
SETEX key seconds value	设置指定key的值,并将 key 的过期时间设为 seconds 秒
SETNX key value		只有在 key 不存在时设置 key 的值

哈希 hash 操作命令:

Redis hash 是一个string类型的 field 和 value 的映射表,hash特别适合用于存储对象,常用命令:
HSET key field value 	将哈希表 key 中的字段 field 的值设为 value
HGET key field 		获取存储在哈希表中指定字段的值
HDEL key field		删除存储在哈希表中的指定字段
HKEYS key 		获取哈希表中所有字段
HVALS key 		获取哈希表中所有值
HGETALL key 		获取在哈希表中指定 key 的所有字段和值

列表 list 操作命令:

Redis 列表是简单的字符串列表,按照插入顺序排序,常用命令:
LPUSH key value1 [value2] 	将一个或多个值插入到列表头部
LRANGE key start stop 		获取列表指定范围内的元素
RPOP key 			移除并获取列表最后一个元素
LLEN key 			获取列表长度
BRPOP key1 [key2 ] timeout 	移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止

集合 set 操作命令:

Redis set 是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据,常用命令:
SADD key member1 [member2] 	向集合添加一个或多个成员
SMEMBERS key 		返回集合中的所有成员
SCARD key 			获取集合的成员数
SINTER key1 [key2] 		返回给定所有集合的交集
SUNION key1 [key2] 		返回所有给定集合的并集
SDIFF key1 [key2] 		返回给定所有集合的差集
SREM key member1 [member2] 	移除集合中一个或多个成员

有序集合 sorted set 操作命令:

Redis sorted set 有序集合是 string 类型元素的集合,且不允许重复的成员。每个元素都会关联一个double类型的分数(score) 。redis正是通过分数来为集合中的成员进行从小到大排序。有序集合的成员是唯一的,但分数却可以重复。
常用命令:
ZADD key score1 member1 [score2 member2] 	向有序集合添加一个或多个成员,或者更新已存在成员的						分数
ZRANGE key start stop [WITHSCORES] 		通过索引区间返回有序集合中指定区间内的成员
ZINCRBY key increment member 			有序集合中对指定成员的分数加上增量 increment
ZREM key member [member ...] 			移除有序集合中的一个或多个成员

通用命令:

KEYS pattern 		查找所有符合给定模式( pattern)的 key (keys *)
EXISTS key 		检查给定 key 是否存在
TYPE key 		返回 key 所储存的值的类型
TTL key 		返回给定 key 的剩余生存时间(TTL, time to live),以秒为单位
DEL key 		该命令用于在 key 存在是删除 key

Jedis


Jedis的maven坐标:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.8.0</version>
</dependency>

使用Jedis操作Redis的步骤:获取连接、执行操作、关闭连接。

//1 获取连接
Jedis jedis = new Jedis("localhost",6379);

//2 执行具体的操作
jedis.set("username","xiaoming");

String value = jedis.get("username");
System.out.println(value);

//jedis.del("username");

jedis.hset("myhash","addr","bj");
String hValue = jedis.hget("myhash", "addr");
System.out.println(hValue);

Set<String> keys = jedis.keys("*");
for (String key : keys) {
    System.out.println(key);
}

//3 关闭连接
jedis.close();

Spring Data Redis


Redis 的 Java 客户端很多,官方推荐的有三种:

Jedis
Lettuce
Redisson

Spring 对 Redis 客户端进行了整合,提供了 Spring Data Redis,在Spring Boot项目中还提供了对应的Starter,即
spring-boot-starter-data-redis。

坐标:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml配置:

spring:
  application:
    name: springdataredis_demo
  #Redis相关配置
  redis:
    host: localhost
    port: 6379
    #password: 123456
    database: 0 #操作的是0号数据库
    jedis:
      #Redis连接池配置
      pool:
        max-active: 8 #最大连接数
        max-wait: 1ms #连接池最大阻塞等待时间
        max-idle: 4 #连接池中的最大空闲连接
        min-idle: 0 #连接池中的最小空闲连接

Spring Data Redis中提供了一个高度封装的类:RedisTemplate,针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口,具体分类如下:

ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作

设置序列化配置类:

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
 * Redis配置类
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        //默认的Key序列化器为:JdkSerializationRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

代码:

@Autowired
private RedisTemplate redisTemplate;

/**
 * 操作String类型数据
 */
@Test
public void testString(){
    redisTemplate.opsForValue().set("city123","beijing");
    String value = (String) redisTemplate.opsForValue().get("city123");
    System.out.println(value);
    
    redisTemplate.opsForValue().set("key1","value1",10l, TimeUnit.SECONDS);

    Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent("city1234", "nanjing");
    System.out.println(aBoolean);
}

/**
 * 操作Hash类型数据
 */
@Test
public void testHash(){
    HashOperations hashOperations = redisTemplate.opsForHash();

    //存值
    hashOperations.put("002","name","xiaoming");
    hashOperations.put("002","age","20");
    hashOperations.put("002","address","bj");

    //取值
    String age = (String) hashOperations.get("002", "age");
    System.out.println(age);

    //获得hash结构中的所有字段
    Set keys = hashOperations.keys("002");
    for (Object key : keys) {
        System.out.println(key);
    }

    //获得hash结构中的所有值
    List values = hashOperations.values("002");
    for (Object value : values) {
        System.out.println(value);
    }
}

/**
 * 操作List类型的数据
 */
@Test
public void testList(){
    ListOperations listOperations = redisTemplate.opsForList();

    //存值
    listOperations.leftPush("mylist","a");
    listOperations.leftPushAll("mylist","b","c","d");

    //取值
    List<String> mylist = listOperations.range("mylist", 0, -1);
    for (String value : mylist) {
        System.out.println(value);
    }

    //获得列表长度 llen
    Long size = listOperations.size("mylist");
    int lSize = size.intValue();
    for (int i = 0; i < lSize; i++) {
        //出队列
        String element = (String) listOperations.rightPop("mylist");
        System.out.println(element);
    }
}

/**
 * 操作Set类型的数据
 */
@Test
public void testSet(){
    SetOperations setOperations = redisTemplate.opsForSet();
    //存值
    setOperations.add("myset","a","b","c","a");
    //取值
    Set<String> myset = setOperations.members("myset");
    for (String o : myset) {
        System.out.println(o);
    }
    //删除成员
    setOperations.remove("myset","a","b");
    //取值
    myset = setOperations.members("myset");
    for (String o : myset) {
        System.out.println(o);
    }
}

/**
 * 操作ZSet类型的数据
 */
@Test
public void testZset(){
    ZSetOperations zSetOperations = redisTemplate.opsForZSet();

    //存值
    zSetOperations.add("myZset","a",10.0);
    zSetOperations.add("myZset","b",11.0);
    zSetOperations.add("myZset","c",12.0);
    zSetOperations.add("myZset","a",13.0);

    //取值
    Set<String> myZset = zSetOperations.range("myZset", 0, -1);
    for (String s : myZset) {
        System.out.println(s);
    }

    //修改分数
    zSetOperations.incrementScore("myZset","b",20.0);

    //取值
    myZset = zSetOperations.range("myZset", 0, -1);
    for (String s : myZset) {
        System.out.println(s);
    }

    //删除成员
    zSetOperations.remove("myZset","a","b");

    //取值
    myZset = zSetOperations.range("myZset", 0, -1);
    for (String s : myZset) {
        System.out.println(s);
    }
}

/**
 * 通用操作,针对不同的数据类型都可以操作
 */
@Test
public void testCommon(){
    //获取Redis中所有的key
    Set<String> keys = redisTemplate.keys("*");
    for (String key : keys) {
        System.out.println(key);
    }

    //判断某个key是否存在
    Boolean itcast = redisTemplate.hasKey("itcast");
    System.out.println(itcast);

    //删除指定key
    redisTemplate.delete("myZset");

    //获取指定key对应的value的数据类型
    DataType dataType = redisTemplate.type("myset");
    System.out.println(dataType.name());
}


文章作者:
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 !
  目录