- MySQL
- JDBC
- 反射
- 代理模式
- 注解
- ORM
- MVC
- properties配置文件
- IOC与DI
- 数据库设计的三大范式
- log4j
- Mybatis
- 1.Mybatis执行流程
- 2.基于DAO层开发的一般流程
- 3.配置优化一:使用typeAliases标签简化mapper
- 4.配置优化二:使用properties标签简化数据库配置信息
- 5.SqlSession的三种查询方式
- 6.mapper文件的三种传参方式
- 7.mapper文件中#与$的区别
- 8.模糊查询的实现
- 9.基于代理模式开发的一般流程
- 10.基于代理模式开发的参数传递
- 11.动态SQL
- 12.模糊查询
- 13.手动映射
- 14.多表关联一对一手动映射
- 15.多表关联一对多手动映射
- 16.多表关联多对多手动映射
- 17.调用其他的namespace下的sql
- 18.延迟加载/懒加载
- 19.缓存技术
- 20.注解使用
- 21.Mybatis的工具类封装
- Servlet
- JSP
- GSON
- AJAX
- 文件上传下载的实现
- 过滤器
- 监听器
MySQL
1. SQL基础
SQL(Structured Query Language) - 结构化查询语言
1.1 结构
- DQL:Data Query Language - 查询数据
- DML:Data Manipulation Language - 操作数据,包括增(insert)、删(delete)、改(update)、查(select)
- DDL:Data Definition Language - 定义数据,主要针对数据库对象(表、索引、视图、触发器、存储过程、函数、表空间等)进行创建(create)、修改(alter)和删除(drop)操作
- DCL:Data Control Language - 用来控制数据库权限,如:授权(grant)、回收(revoke)
- TCL:Transaction Control Language - 用于数据库的事务管理,如:开启事务(start transaction)、提交事务(commit)、回滚事务(rollback)、设置事务的属性(set transaction)
1.2 数据库字段的类型
- 整数型:TINYINT(1字节)、SMALLINT(2字节)、MEDIUMINT(3字节)、INT/INTEGER(4字节)、BIGINT(8字节)
- 浮点型:FLOAT(4字节)、DOUBLE(8字节)
- 字符型:CHAR、VARCHAR、TEXT…
- 日期型:DATE(yyyy-MM-dd)、DATETIME(yyyy-MM-dd HH:mm:ss)…
1.3 DDL操作
- 查看所有数据库名称
show databases;
- 创建数据库
create database test; //替换 create or replace database test;
- 使用数据库
//use 不是SQL语句,而是MySQL指令,所以后面可以不加分号 use test
- 删除数据库
drop database test;
- 查看所有表名
show tables;
- 创建表
create table tableName( id int(6), name varchar(10), gender char(2), age int(3) );
- 查看表结构
desc tableName;
- 删除表
drop table tableName;
- 修改表名
//to可以省略 alter table tableNmae rename to tableName2;
- 查看表的创建过程
show create table tableName;
- 增加字段
//默认追加在最后 alter table tableName add score double(4,1); //追加在指定位置 alter table tableName add score double(4,1) first; alter table tableName add score double(4,1) after name;
- 删除字段
alter table tableName drop score;
- 修改字段
//修改字段的数据类型 alter table tableName modify score double(5,2); //修改字段名称 alter table tableName change score scores double(4,1);
1.4 DML操作
- 新增数据
//按字段顺序增加数据 insert into tableName values(1,"赵大土","男",25); //增加指定字段的数据 insert into tableName(name,age) values("大土",26);
- 修改数据
update tableName set age=20,name="大土" where id=1;
- 删除数据
//删除id为1的行数据(逐条扫描,一一判断) delete from tableName where id=1; //使用truncate truncate table tableName;
truncate与delete的比较:
1.truncate属于DDL,其操作会导致隐式提交,不能回滚
2.truncate不是逐条删除,而是会保留表的结构,重新创建表(所以对于自增约束的字段会重新从1开始自增)
3.truncate操作成功不会返回已删除的行数,只是返回成功还是失败 - 查询数据
//查询表中所有数据 select * from tableName;
1.5 完整性约束
- 分类一:
- 表级约束:约束表中任意一个或多个字段;可以在创建表的时候添加,也可以在表创建完毕后添加
- 列级约束:只能约束某个字段;可以在创建表的时候添加,也可以在表创建完毕后通过修改列的形式添加;修改列级约束并不会影响表级约束
- 分类二:
- 主键约束
- 非空约束
- 唯一约束
- 检查约束
- 外键约束
- MySQL的约束:
- PRIMARY KEY:主键约束(非空且唯一)(表级约束)
//建表时添加 create table tableName( id int primary key ); create table tableName( id int, primary key(id) ); //建表后添加 alter table tableName add constraint PK_tableName_id primary key(id); //删除 alter table tableName drop primary key;
- UNIQUE:唯一约束(表级约束)
//建表时添加 create table tableName( phone varchar(11) unique ); //建表后添加 alter table tableName add constraint UK_tableName_phone unique(phone); //删除 alter table tableName drop index UK_tableName_phone; drop index UK_tableName_phone on tableName;
- NOT NULL:非空约束(列级约束)
//建表时添加 create table tableName( id int not null, name varchar(10) not null ); //建表后添加 alter table tableName modify name varchar(10) not null; //删除 alter table tableName modify name varchar(10) null;
- DEFAULT:默认值约束(列级约束) - 不指定值时为默认值(注意:NULL也是指定值)
//建表时添加 create table tableName( gender varchar(4) default "男" ); //建表后添加 alter table tableName modify gender varchar(4) default "男"; //删除 alter table tableName modify gender varchar(4);
- AUTO_INCREMENT:自动增加约束(列级约束) - 只用于主键,添加数据时使用NULL填充即可自动递增
//建表时添加 create table tableName( id int auto_increment; ); //建表后添加 alter table tableName modify id int auto_increment; //删除 alter table tableName modify id int;
- Check:检查约束 - 在MySQL中不生效,MySQL推荐使用枚举类型来替代检查约束
//建表时添加 create table tableName( gender varchar(4) check(gender in ("男","女")) ); //建表后添加 alter table tableName add constraint CK_tableName_gender check (gender in("男","女")); //使用枚举类型 alter table tableName modify gender enum("男","女");
- FOREIGN KEY:外键约束(表级约束)
//建表时添加 create table table02( classno int, constraint FK_table02_class foreign key(classno) references table01(id) ); //建表后添加 //table02的classno对应table01的id //约束的表是table02,参考的表是table01 //此时删除table01表中的数据时会报错 alter table table02 add constraint FK_table02_class foreign key(classno) references table01(id); //默认级联方案 //1.不允许 - 报错 //2.级联删除、修改-删除、修改了参考表中的数据,约束表中的数据也跟着删除、修改 alter table table02 add constraint FK_table02_class foreign key(classno) references table01(id) on delete cascade on update cascade; //3.级联置空 alter table table02 add constraint FK_table02_class foreign key(classno) references table02(id) on delete set null on update set null; //删除 alter table table02 drop foreign key FK_table02_class
- PRIMARY KEY:主键约束(非空且唯一)(表级约束)
1.6 where单表查询语句
- 等值查询
select * from student where name="大土";
- 不等值查询
select * from student where score >= 80; select * from student where class != 2;//!=可以使用<>替换 select * from student where score >= 80 and socre <= 100;//and 可以使用&&替换 select * from student where score between 80 and 100;//这里and不能使用&&替换 select * from student where score <= 60 or score >=90;//or可以使用||替换 select * from student where score not between 60 and 90; select * from student where class = 1 or class = 2; select * from student where class in(1,2); select * from student where class != 1 and class != 2; select * from student where class not int(1,2);
- 模糊查询
//查询姓名以赵开头的学生 select * from student where name like "赵%"; //查询姓名以土结尾的学生 select * from student where name like "%土"; //查询姓名中包含土的学生 select * from student where name like "%土%"; //查询姓名以赵开头,以土结尾的学生 select * from student where name like "赵%土"; //查询姓名为三个字符的学生(一个字符使用一个_替换) select * from student where name like "___"; //查询姓名为三个字符且第二个字符为大的学生 select * from student where name like "_大_"; //查询姓名中包含_的学生 select * from student where name like "%\_%"; //查询姓名中包含%的学生 select * from student where name like "%\%%"; //查询姓名中包含\的学生 select * from student where name like "%\\\\%";
- 判断空值
select * from student where name is null; select * from student where name is not null;
1.7 快速创建表格和插入数据
//创建一个和student表一样的表
create table student02 as select * from student;
//创建一个和student表一样的表,但不包含数据
create table student02 as select * from student where 1=2;
//创建一个包含student表中指定字段的表(不包含数据)
create table student02 as selct id,name from student where 1=2;
//将student表中的全部数据插入到student02表中
insert into student02 select * from student;
//将student表中指定数据插入到student02表中
insert into student02 select * from student where score >= 80;
//一次插入多条数据
insert into student values(1,"赵大土",90),(2,"赵大土土",99);
1.8 单行函数
单行函数是输入一个参数或内部扫描一条数据,返回一个结果
-
字符串函数
//查询学生姓名长度为4的学生 select id,name from student where length(name) = 6;
-
数值函数
//返回绝对值 select abs(-5) from dual;//dual是虚表/哑表,是不存在的,dual可以省略 select abs(-5); //查询学生的分数,保留两位小数 select id, name, round(score,2) from student;//四舍五入 select id, name, truncate(socre,2) from student;//非四舍五入
-
日期函数
//返回当前SQL开始执行日期yyyy-MM-dd select curdate(); //返回当前当前SQL开始执行时间hh-mm-ss select curtime(); //返回当前SQL开始执行日期和时间yyyy-MM-dd hh-mm-ss select now() //返回当前SQL执行到sysdate函数时的时间 select now(), sysdate(), sleep(2), now(), sysdate();
-
流程函数
select id, name, score, if(score>=60,"合格","不合格") 及格情况 from student; select id,name,score, case when score>=60 then "及格" else "不及格" end 及格情况 from student; select id, name, socre, if(score>=90,"优秀", if(score>=80,"良好", if(score>=60,"合格","不合格"))) 评价 from student; select id, name, socre, case when socre>=90 then "优秀" else case when score>=80 then "良好" else case when score>=60 then "及格" else "不及格" end end end 评价 from student; select id, name, socre, case position when "班长" then score+10 when "学习委员" then socre+10 else socre+1 end 加分总分 from student; select id, name, socre, case when position= "班长" then score+10 when position="学习委员" then socre+10 else socre+1 end 加分总分 from student; //在student表中,id为1的name为null则返回“it is null”,不为null则返回name的值 select ifnull(name,"it is null") from student where id=1; //1等于2返回null,不等返回1 select nullif(1,2);
-
JSON函数
-
其他函数
//返回当前数据库名称、当前用户、MySQL版本、加密、地址转换成数字、数字转换成地址 select database(), user(), version(), password("123"), inet_aton("192.168.1.1"),inet_ntoa();
1.9 多行函数
多行函数是输入多个参数或内部扫描多次,返回一个结果
//统计学生总数
select count(*) from student;//效率较低,扫描全部字段
select count(1) from student;//先查找有没有该列,没有相当于在后面补了一列值为1
select count("随便") from student;//相当于补了一列值为"随便"
select count(id) from student;//扫描不为null的id字段
//去重
select distinct class from student;
//统计班级个数
select count(distinct class) from student;
//统计学生总分数
select sum(score) from student;
//统计学生平均分数
select sum(score)/count(score) from student;
select avg(score) from student;
//处理结果保留2位(四舍五入) - round位单行函数
select round(avg(score),2) from student;
//查询最高分
select max(score) 最高分 from student;
//查询最低分
select min(score) 最低分 from student;
1.10 分组函数
//统计每个班的平均分,且分数取整,且按平均分降序排序
select class, round(avg(score)) score
from student
group by class
order by score desc;//升序
//查询平均分大于80的班级
select class 班级, avg(score) 平均分
from student where avg(score) > 80
group by class;//错误,因为where是行级筛选器,是对分组前的数据进行筛选
select class 班级, avg(score) 平均分
from student
group by class
having avg(score) > 80;//having是组级筛选器,是对分组后的数据进行筛选
//除5班外,查询平均分大于80的班级及平均分
select class,avg(score)
from student
group by avg(score)
having class != 10 and avg(score) > 80
order by avg(score) desc;//已经计算了5班的
select class,avg(score)
from student
where class != 5
group by avg(score)
having avg(score) > 80
order by avg(score);//没有计算5班的平均分
注意:
1.分组的字段通常为分组的条件或多行函数
2.在select子句中出现的非多行函数的所有字段,必须出现在group by子句中
3.在group by子句出现的所有字段,非必须出现在select子句中
4.有效编写sql的顺序:from(确定表) - where(分组前的筛选条件) - group by(分组) - select(确定字段) - having(分组后的筛选条件) - order by(排序)
1.11 多表查询
- 多表查询原理
//两表查询生成笛卡尔积 - 结果显示是两表的乘积 select * from student cross join class; //筛选 select * from student as s cross join class as c where s.class=c.id; select * from student s cross join class c where s.class=c.id; //进一步筛选 select id,s.name,c.name from student s cross join class c where s.class=c.id;
- 内连接查询
select * from student s inner join class c on s.class=c.id;
注意:内连接只会查询除多张表中根据某个字段匹配符合的记录,不符合的记录不会显示
- 外连接查询
//左外关联-左表为主表,左表的数据全部显示 select * from student left outer join class on student.class=class.id; //右外关联-右表为主表,右表的信息全部显示 select * from student right outer join class on student.class=class.id;
注意:外连接查询即能查询出符合条件的记录,也能根据一个表将另一个表查询出来
- 自连接查询
select * from student s1 inner join student s2 on s1.id=s2.groupLeardId
- 子查询
//不相关子查询 select * from student where score > (from select avg(score) from student); //相关子查询 select * from student s1 where score=(select max(socre) from student s2 where s1.class=s2.class);
注意:
1.子查询与父查询可以针对同一张表,也可以针对不同表
2.子查询与父查询在传递参数时,可以多个,但数量、类型要相同
3.相关子查询:子句与父句相关,子句不可独立执行
4.不相关子查询:子句与父句不相关,子句可以独立执行
1.12 视图
视图以一种虚表,建立在已有表(基表)的基础上,用于简化复杂查询、限制数据访问
//创建视图
create view studentView as select * from student;
//创建只读视图 MySQL不支持,Oracle支持
create or replace view testView as select * from tableName with read only;
//查看视图
select * from studentView;
//删除视图
drop view studentView;
注意:操作视图会影响基表,但删除视图不会影响基表
1.13 集合查询
集合查询是指使用3种集合操作符将2个或2个以上的查询语句连接起来,形成一条查询语句的查询方法
//union all - 并集(包括重复数据)
select * from student where name like "%大%"
union all
select * from student where name like "%土%";
//union - 并集(不包括重复数据)
select * from student where name like "%大%"
union
select * from student where name like "%土%";
//等价于
select * from student where name like "%大%" or name like "%土%";
//intersect - 交集
select * from student where name like "%大%"
intersect
select * from student where name like "%土%";
//等同于
select * from student where name like "%大%" and name like "%土%";
//minus - 差集
select * from student where name like "%大%"
minus
select * from student where name like "%土%";
注意:intersect、minus在MySQL中无效
1.14 分页查询
//显示前4条数据
//0表示从1开始,4表示每页显示条数
select * from student limit 0,4;
注意:limit后面不支持表达式(limit (3-1)*2,3 是无效的)
1.15 事务
事务是指一个不可拆分的操作序列,满足ACID特性【原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durablility)】
//开启事务
start transaction
//设置回滚点
savepoint
//提交事务
commit
//回滚
rollback
注意:InnoDB支持事务,但MyISAM不支持事务
- 事务并发问题
- 脏读:一个事务读取到了另一个事务修改后并未提交的数据(数据未提交,所以可能回滚导致脏数据)
- 幻读:一个事务修改并提交了另一个事务正在重复读取的数据(一个事务重复读取同一数据出现数据不一致的问题)
- 不可重复读:一个事务修改并提交了另一个事务正在重复读取的数据(一个事务重复读取同一数据出现数据不一致的问题)
幻读与不可重复读的区别:幻读重点在新增或删除(需要锁住表);不可重复读重点在修改(需要锁住行)
- 脏读:一个事务读取到了另一个事务修改后并未提交的数据(数据未提交,所以可能回滚导致脏数据)
- 事务的隔离级别
//查看当前事务隔离级别 select @@tx_isolation;//默认是REPEATABLE-READ //修改 set session transaction isolation level read uncommitted;
1.16 索引
索引是提高查询效率的一种手段,一旦创建,完全由数据库自行维护
MySQL会自动给主键约束、唯一约束和外键约束的字段添加索引
- 类型
- 单列索引和多列索引
- 唯一索引和非唯一索引
- 存储结构:B-Tree(默认)、R-Tree、Hash
- 使用
//查看所有 show index from tableName; //创建索引 create index index_student_name on student(name); //删除索引 修改的话需要先删除再创建 drop index index_student_name;
1.17 存储过程
存储过程是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过名字并给定参数即可调用执行
//创建一个存储过程(注意格式) - 计算学生总数
delimiter //
create procedure test(out number int)
begin
select count(1) into number from student;
end //
delimiter ;
//测试
//创建一个变量
set @a=0;
//查看变量
select @a;
//调用存储过程
call test(@a);
//根据班级获取班级平均分
delimiter //
create procedure getAVG(in classNumber, out avgs double)
begin
select avg(score) into avgs from student where class=classNumber;
end //
delimiter ;
//测试
set @b=0;
call getAVG(5,@b);
2. 安装
- 下载安装包直接安装
- 配置环境变量
3. 使用
- 使用cmd进入MySQL
JDBC
JDBC(Java DataBase Connectivity)是Java数据库连接的简称,是用于执行SQL的API
1.一般使用流程
导入mysql-connector-java-5.1.38.jar包
public class Test{
public static void main(String[] args){
//1.获取驱动对象
Driver driver=new Driver();
//2.在驱动中心注册
DriverManager.registerDriver(driver);
//3.通过注册管理器获取连接对象
Connection connection=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?useSSL=false","root","root");
//4.获取语句对象
Statement statement=connection.createStatement()
//5.执行语句
//增
int i=statement.executeUpdate("insert int test values(null,"赵大土",25)");
//删
int i=statement.executeUpdate("delete from student where id=1");
//改
int i=statement.excuteUpdate("update student set id=1 where id=2");
//查
ResultSet result=statement.excuteQuery("select * from student");
List<Person> list=new ArrayList();
while(result.next(){
//int id=result.getInt("id");
int id=result.getInt(1);
String name=result.getString("name");
int age=result.getInt("age");
list.add(new Person(id,name,age));
}
//6.释放资源
result.close();
statement.close();
connection.close();
}
}
2.优化一:使用反射获取驱动
public class Test{
public static void main(String[] args){
//1.获取驱动对象 - 只需要driver的字节码,不需要对象,可通过反射加载
//Driver driver=new Driver();
Class.forName("com.mysql.jdbc.Driver");
//2.在驱动中心注册 - 静态代码块在第一次类加载的时候就会执行,所以可以省略
//DriverManager.registerDriver(driver);
//3.通过注册管理器获取连接对象
Connection connection=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?useSSL=false","root","root");
//4.获取语句对象
Statement statement=connection.createStatement()
//5.执行语句
int i=statement.executeUpdate("insert int test values(null,"赵大土",25);");
//6.释放资源
statement.close();
connection.close();
}
}
3.优化二:创建连接对象工具类
public class Test{
public static void main(String[] args){
Connection connection=DBUtil.getConnection();
Statement statement=connection.createStatement();
String sql="select * from user where username='"+username+"' and password='"+password+"'";
ResultSet result=statement.excuteQuery(sql);
while(result.next()){
int id=result.getInt("id");
String username=result.getString("username");
String password=result.getString("password");
User user=new User(id,username,password);
}
DBUtil.relase(result,statement,connection);
}
}
//连接对象只在第一次需要,可以提取成一个工具类
class DBUtil{
//第一次类加载时执行driver的初始化
static{
try{
Class.forName("com.mysql.jdbc.Driver");
}catch(ClassNotFoundException e){
e.printStackTrance();
}
}
//获取连接对象
public static Connection getConnection(){
try{
return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?useSSL=false","root","root");
}catch(SQLException e){
e.printStackTrace();
}
return null;
}
//释放资源
public static void release(ResultSet result,Statement statement,Connection connection){
if(null != result){
result.close();
}
if(null != statement){
statement.close();
}
if(null != connection){
connection.close();
}
}
}
4.优化三:注入攻击与预编译连接对象
- 注入攻击
//JDBC中,sql如果采用上面的拼接方法,可能会出现输入密码aaa'or'1'='1登录成功,因为sql语句已经变成了如下 select * from user where username='随便' and password='aaa' or '1'='1'
- 使用预编译连接对象避免注入攻击
public class Test{ public static void main(String[] args){ Connection connection=DBUtil.getConnection(); //使用?占位符 String sql="select * from user where username=? and password=?"; //使用Statement的子类PrepareStatement PrepareStatement statement=connection.prepareStatement(sql); //设置占位符内容 statement.setString(1,"大土"); statement.setString(2,"123"); while(result.next()){ int id=result.getInt("id"); String username=result.getString("username"); String password=result.getString("password"); User user=new User(id,username,password); } DBUtil.relase(result,statement,connection); } }
5.使用预编译对象完成其他操作
//模糊查询
String sql="select * from user where name like ?";
PrepareStatement statement=connection.prepareStatement(sql);
statement.setString(1,"%土%");
ResultSet result=statement.excuteQuery();
//增
String sql="insert into user values(default,?,?)";
PrepareStatement statement=connection.prepareStatement(sql);
statement.setString(1,"大土");
statement.setString(2,"123");
int i=statement.excuteUpdate();
//删
String sql="delete from user where id=?";
PrepareStatement statement=connection.prepareStatement(sql);
statement.setInt(1,1);
int i=statement.excuteUpdate();
//改
String sql="update user set name=?,password=? where id=?";
PrepareStatement statement=connection.prepareStatement(sql);
statement.setString(1,"大土");
statement.setString(2,"123");
statement.setInt(3,1);
int i=statement.excuteUpdate();
6.事务控制
- 没有事务控制
public class Test{
public static void main(String[] args){
Connection connection=DBUtil.getConnection();
String sql="update account set money=money+? where id=?";
PrepareStatement statement=connection.prepareStatement(sql);
//张三转钱
statement.setInt(1,-1000);
statement.setInt(2,1);
int i=statement.excuteUpdate();
//抛出异常,后续代码终止,转钱成功,收钱失败
int x=1/0;
//李四收钱
statement.setInt(1,1000);
statement.setInt(2,2);
int i=statement.excuteUpdate();
DBUtil.relase(result,statement,connection);
}
}
- 设置事务控制
public class Test{
public static void main(String[] args){
Connection connection=DBUtil.getConnection();
//开启事务手动提交,默认自动提交
connection.setAutoCommit(false);
String sql="update account set money=money+? where id=?";
PrepareStatement statement=connection.prepareStatement(sql);
//张三转钱
try{
statement.setInt(1,-1000);
statement.setInt(2,1);
int i=statement.excuteUpdate();
//抛出异常,后续代码终止,转钱成功,收钱失败
int x=1/0;
//李四收钱
statement.setInt(1,1000);
statement.setInt(2,2);
int i=statement.excuteUpdate();
}catch(Exception e){
//出现异常,开始回滚
connection.rollback();
e.printStackTrace();
}finally{
//提交事务
connection.commit();
//关闭资源
DBUtil.relase(result,statement,connection);
}
}
}
8.连接池的实现
public class MyConnectionPool{
//连接池
private static LinkedList<Connection> pool;
private final String RUL="jdbc:mysql://127.0.0.1:3306/test?useSSL=false";
private final String USERNAME="root";
private final String PASSWORD="root";
//初始化
static{
Class.forName("com.mysql.jdbc.Driver");
pool=new LinkedList<Connection>();
for(int i=0;i<5;i++){
pool.add(initConnection());
}
}
//创建一个连接对象
private static Connection initConnection(){
return DriverManager.getConnection(URL,USERNAME,PASSWORD);
}
//获取连接
public static Connection getConnection(){
if(pool.size>0){
return pool.removeFirst();
}else{
return initConnection();
}
}
//归还连接
public static void returnConnection(Connection ... connections){
for(Connection connection:connections){
if(pool.size<5){
pool.addLast(connection);
}else{
connection.close();
}
}
}
}
反射
Java反射机制是指在运行状态中,对于任意一个对象,都能够调用其方法和属性
- 获取类的字节码对象
//通过类的全限定名获取 Class test=Class.forName("cn.khue.test.Test"); //通过类名获取 Class test=Test.class; //通过对象名获取 Test test=new Test(); Class testClass=test.getClass(); //返回类的全限定名 test.getName(); //返回类名 test.getSimpleName();
- 获取类的属性
//获取类的字节码对象 Class test=Test.class; //获取公有属性 Field ageField=test.getField("age"); //获取私有属性 Field nameFeild=test.getDeclaredField("name"); //获取全部属性 Field[] fields=test.getDeclaredField(); for(field:fields){ field.getName(); field.getType().getName(); } //赋值 Test test=new Test(); Field field=test.getDeclaredField("name"); field.setAccessible(true); field.set(test,"赵大土");
- 获取方法
Test test=new Test(); Class class=test.getClass(); //无参 Method getNameMethod=class.getDeclaredMethod("getName"); //有参 Method setNameMethod=class.getDeclaredMethod("setName",String.class); //返回方法名 method.getName(); //返回形参个数 method.getParameterCount(); //返回参数类型 Class[] parameterType=method.getParameterTypes(); //返回返回值类型 Class returnType=method.getReturnType(); //执行非静态方法 Test test=new Test(); Class class=test.getClass(); Method setNameMethod=class.getDeclaredMethod("setName",String.class); Object object=setNameMethod.invoke(test,"赵大土"); //执行静态方法 Object object=setNameMethod.invoke(null,"赵大土");
- 获取构造器
Class test=Class.forName("cn.khue.test.Test"); //有参构造器 Constructor constructor=test.getDeclaredConstaructor(String.class); //返回构造器名称 constructor.getName(); //返回参数个数 constructor.getParameterCount(); //返回参数类型 constructor.getParameterTypes();
- 使用反射
//获取字节码 Class test=Class.forName("cn.khue.test.Test"); //通过构造方法获取对象 Constructor constructor=test.getDeclaredConstructor(); Object object=constructor.newInstance(); //直接获取对象-底层执行的是无参构造器 Obejct object=test.newInstance(); //获取属性 Field field=test.getDeclaredField("name"); //设置属性可方法 field.setAccessible(true); //属性赋值 field.set(object,"赵大土"); //获取方法 Method method=test.getDeclaredMethod("setName",String.class); //执行方法 Object result=method.invoke(object,"赵大土土");
代理模式
当对某一个类的某一方法不满意,又不能直接重写该方法时,可以使用代理对象
1.静态代理
静态代理是指代理类和代理方法需要手动编写
//测试类
public class Test {
public static void main(String[] args) {
Agent agent=new Agent();
agent.test();
}
}
//委托类
class Client{
//需要增强的方法
public String test(){
System.out.println("委托类的方法");
return "委托类";
}
}
//代理类
class Agent{
//代理增强的方法
public String test(){
System.out.println("代理方法");
Client client = new Client();
client.test();
return "代理类";
}
}
为了规范代理方法,一般采用接口或继承的方式保证方法名一致
//测试类
public class Test {
public static void main(String[] args) {
AgentInterface agent=new Agent();
agent.test();
}
}
//委托类
class Client implements AgentInterface{
//需要增强的方法
@Override
public String test(){
System.out.println("委托类的方法");
return "委托类";
}
}
//代理类
class Agent implements AgentInterface{
//代理增强的方法
@Override
public String test(){
System.out.println("代理方法");
Client client = new Client();
client.test();
return "代理类";
}
}
//代理接口
interface AgentInterface{
String test();
}
//测试类
public class Test {
public static void main(String[] args) {
AgentClass agent=new Agent();
agent.test();
}
}
//委托类
class Client extends AgentClass{
//需要增强的方法
@Override
public String test(){
System.out.println("委托类的方法");
return "委托类";
}
}
//代理类
class Agent extends AgentClass{
//代理增强的方法
@Override
public String test(){
System.out.println("代理方法");
Client client = new Client();
client.test();
return "代理类";
}
}
//代理基类
class AgentClass{
public String test(){}
}
2. 动态代理
动态代理是指代理类和代理方法动态生成
2.1JDK动态代理
JDK动态代理是Java自带的Proxy代理(jdk1.3),是一种面向接口的代理
特点:
- 底层利用拦截器(实现InvocationHandler接口)加上反射机制生成一个匿名代理接口类,在调用前执行InvokeHandler
- 只能代理接口中被实现的方法
- 代理对象只能强制转换成接口对象,不能转换成委托对象
- 只能读取到接口方法上的注解,不能读取到委托类方法上的注解
//代理接口
interface AgentInterface{
String say();
}
//委托类
class Client implements AgentInterface{
@Override
public String say(){
System.out.println("委托类需要被增强的方法");
return "委托类";
}
}
//测试类
class Test{
public static void main(String[] args){
Client client=new Client();
//原方法
System.out.println(client.say());
//创建代理
AgentInterface agent=(AgentInterface)Proxy.newProxyInstance(client.getClass().getClassLoader(),client.getClass().getInterfaces(),new InvocationHandler(){
@Override
public Obejct invoke(Object proxy,Method method,Object[] args) throws Throwable{
//因为代理类执行任意方法都会触发本方法,所以可以先判断是否是需要被代理的方法
if(method.getName().equals("say")){
//原方法执行前的增强代码
System.out.println("代理类先执行的增强方法");
//执行原方法
method.invoke(client,args);
//原方法执行后的增强代码
System.out.println("代理类后执行的增强方法");
//返回
return "代理类";
}else{
//直接返回该方法的执行结果
return method.invoke(client,args);
}
}
});
//代理后的方法
System.out.println(agent.say());
}
}
Proxy.newProxyInstance()的3个参数:
1.client.getClass().getClassLoader() - 委托类的类加载器
2.client.getClass().getInterfaces() - 委托类的所有的实现的接口的字节码数组
3.new InvocationHander() - 执行处理器 - 具体代理的内容,必须是InvocationHandler的实现类(这里使用的匿名内部类)
InvocationHandler中invoke方法的3个参数:
1.Object proxy - 代理类
2.Method method - 委托的方法
3.Object[] args - 代理方法的实参的字节码数组
代理类执行任意方法时,都会先触发执行InvocationHandler的invoke方法
2.2Cglib动态代理
Cglib(Code Generator Library)动态代理需要导jar(cglib.jar+asm.jar),是一种面向继承的代理
特点:
- 底层采用ASM字节码生成框架,使用字节码技术生成委托类的子类(代理类)
- 不能对声明为final的方法进行代理
- 可以转换成委托对象
- 可以读取到委托类方法上的注解
//测试类
public class Test {
public static void main(String[] args) {
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(Client.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理前增强");
methodProxy.invokeSuper(o,objects);
System.out.println("代理后增强");
return "代理类";
}
});
Client proxy = (Client) enhancer.create();
proxy.test();
}
}
//委托类
class Client{
public String test(){
System.out.println("委托类");
return "委托类";
}
}
2.3关于JDK动态代理与Cglib动态代理的性能比较
随着反射效率的优化提升,在jdk1.8中JDK动态代理的效率要高于Cglib(也有使用场景的限制)
3.代理模式的使用
在连接池的代码中,我们需要调用returnConnection方法归还连接对象至连接池,而不能使用连接对象自带的close方法归还,这时候我们就可考虑使用代理增强连接对象的close方法了
class DBUtils{
private int max=10;
private final String URL="mysql:jdbc://127.0.0.1:3306/test?useSSL=false";
private final String UERNAME="root";
private final String PASSWORD="root";
private List<Connection> pool;
//初始化连接池
static{
Class.forName("com.jdbc.mysql.Driver");
list=new LinkedList<Connection>();
for(int i=0;i<5;i++{
pool.add(initConnction());
}
}
//初始化连接对象
private static Connection initConnection(){
//创建连接对象
Connection connection=DriverManager.getConenction(URL,USERNAME,PASSWORD);
//返回代理对象
return (Conenction)Proxy.newProxyInstance(connection.getClass().getClassLoader(),connection.getClass().getInterfaces(),new InvocationHandler(){
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
Object result=null;
//如果是close方法,就增强
if(method.getName().equals("close")){
if(pool.size()<max){
pool.addLast((Connection)proxy);
}else{
connection.close();
}
}else{
//不是close方法就正常执行
result=method.invoke(connection,args);
}
return result;
}
});
}
//获取连接对象
public static Connection getConnection(){
if(pool.size>0){
return pool.removeFirst();
}else{
return initConnection();
}
}
//归还连接对象
/*public static void returnConnection(Connection ... connections){
for(Connection connection:connections){
if(pool.size<max){
pool.addLast(connection);
}else{
connection.close();
}
}*/
}
}
注解
注解也叫元数据,jdk1.5引入,用来对包、类、字段等Java元素进行说明,可以起到文档生成、代码分析与编译检查的作用
- 自带注解
//@Override - 重写规范检验 //@Decpreacted - 方法名增加中划线,提示该方法已过时 //@SupressWarning - 禁止显示警告信息
- 元注解:用于注解其他注解
//@Target-控制注解的使用范围 //使用在包上 @Target(ElementType.PACKAGE) //使用在类、接口、枚举、注解上 @Target(ElementType.TYPE) //使用在构造器上 @Target(ElementType.CONSTRUCTOR) //使用在属性上 @Target(ElementType.FIELD) //使用在方法上 @Target(ElementType.METHOD) //可以写多个,value=可以省略 @Target(value={ElementType.TYPE,ElementType.METHOD}) //@Retention-控制注解的生命周期 //源代码时有效 @Retention(RetentionPolicy.SOURCE) //编译时有效 @Retention(RetentionPolicy.CLASS) //运行时有效 - 可以被反射读取 @Retention(RetentionPolicy.RUNTIME) //@Documented - 允许被Javadoc文档化 //@Inherited - 允许子类继承父类中的注解
- 自定义注解
//创建一个自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation{ String name() default "测试"; int age(); } //使用 class Test{ @MyAnnotation(age=25) public void test(){} }
当注解的属性值只有value一个字段时,value=可以省略不写
ORM
ORM(Object Relationship Mapping)是指Java中的实体类和数据库表格之间的转换
学习了反射与注解之后,可以使用反射与注解来实现ORM
//本注解用于实现类名与数据库表名的对应关系
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableAnnotation{
String value();
}
//本注解用于实现类中属性与数据库表中字段的对应关系
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnnotation{
//字段类型
String type();
//字段长度
int length();
//字段约束
String constraint() default "";
}
@TableAnnotation("student")
class Student{
@FieldAnnotation(type="int",length=6,constraint="primary key auto_increment")
private int id;
@FieldAnnotation(type="varchar",length=20,constraint="not null")
private String name;
@FieldAnnotation(type="int",length=3)
private int age
}
class Test{
//通过类名生成对应表格的SQL语句
public static String getSQL(String className){
StringBuilder sql=new StringBuilder();
sql.append("create table ");
//获取类的字节码对象
Class class=Class.forName(className);
//获取该类的注解
TableAnnotation ta=(TableAnnoation)class.getAnnotation(TableAnnotation.class);
//获取表名
String tableName=ta.value();
//拼接SQL语句
sql.append(tableName);
sql.append(" (");
//获取类的所有属性
Field[] fields=class.getDeclaredFields();
//遍历类的属性
for(int i=0;i<fields.length;i++){
//获取属性的注解
FieldAnnotation fa=(FieldAnnotation)fields[i].getAnnotation(FieldAnnotation.class);
//拼接SQL语句
sql.append(fields[i].getName()+" ");
sql.append(fa.type()+"(");
sql.append(fa.length()+")");
sql.append(" "+fa.constraint());
if(i != fields.length){
sql.append(",");
}
}
sql.append(")");
//返回SQL
return sql.toString();
}
}
MVC
M - Model(模型层:数据库映射模型,如JavaBean、POJO、Entity)
V - View(视图层:HTML、JSP、网页静态化技术等)
C - Controller(控制层:接收数据、返回数据、控制页面跳转等)
DAO层(Data Access Object):数据库访问对象,对对数据库进行增删改查
view - controller - service - dao - entity
properties配置文件
- MySQL的配置文件
driver=com.msyql.jdbc.Driver url=jdbc:mysql://127.0.0.1/test?useSSL=false username=root password=root
- 非Servlet下读取配置文件
public class Test{ public static void main(String args){ Properties properties=new Proterties(); InputStream is=properties.getClass().getResourceAsStream("/mysql-config.properties"); properties.load(is); String driver=properties.getProperty("driver"); String url=properties.getProperty("url"); String username=properties.getProperty("username"); String password=properties.getProperty("password"); } }
- Servlet下读取配置文件
@WebServlet("/test") public class TestServlet extends HttpServlet{ @Override protected void service(HttpServletRequest req, HttpServletResponse res){ Properties properties=new Properties(); InputStream is=this.getClass().getClassLoader().getResoureceAsStream("test.properties"); //或者Thread.currentThread().getContextClassLoader().getResourceAsStream(); //或者this.getServletContext().getClassLoader().getResourceAsStream(); properties.load(is); String test=properties.getProperty("test"); } }
IOC与DI
IOC(inversion Of Control) - 控制反转:管理对象的生命周期的权利反转其他形式
DI(Dependency Injection) - 依赖注入:使用反射给对象属性赋值
DI是实现IOC思想的一种方式
数据库设计的三大范式
数据库范式(NF-Normal Form)是指设计数据库时遵守的一般原则,目的是为了提高数据库结构的合理性、降低冗余、减少插入增删改异常
1NF:当关系模式R的所有属性都不能在分解为更基本的数据单位时,称R满足第一范式(列不可再分):如地址需要拆分成省、市、乡等多个字段,而不是只有地址一个字段
2NF:如果关系模式R满足第一范式,并且R的所有非主属性都完全依赖于R的每一个候选关键属性,称R满足第二范式(每列都与唯一主键相关):如设计表(学号、学生姓名、联系方式、班级、班主任、班主任联系方式),因为班主任联系方式只与班主任有关,班主任只与班级有关,设计成一张表就会导致有的列于主键无关,所以需要拆分
3NF:如果关系模式R满足第一范式,X是R的任意属性集,如果X非传递依赖于R的任意一个候选关键字,称R满足第三范式(每列都与主键直接相关):如设计表(学号、学生姓名、联系方式、班级、班主任、班主任联系方式),因为班主任联系方式依赖班主任,班主任依赖班级,学生依赖班级,设计成一张表就会导致依赖传递,所以需要拆分
log4j
1.实现日志的方式
- System.out.println():不能持久化
- 使用IO+System.out.println():不便定制
- log4j:支持持久化、方便定制
2.log4j配置文件的参数含义
# 指定全局的日志级别及输出方式
# log4j提供的日志级别有以下五种:
# 1.FATAL:非常严重的错误,可能导致程序异常终止
# 2.ERROR:非严重的错误,程序依然可以运行
# 3.WARN:运行环境潜藏着危害
# 4.INFO:报告信息,在粗粒度上显示程序的进程
# 5.DEBUG:细粒度信息
# 这里指定日志级别为ERROE,输出方式为控制台输出和文件输出
log4j.rootCategory=ERROE,CONSOLE,File
# 指定某位置的日志级别
# 这里指定com.test.mapper里所有类的日志级别为DEBUG
log4j.logger.com.test.mapper=DEBUG
# 控制台打印
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# 指定打印方式(默认调用System.out)
log4j.appender.CONSOLE.target=System.err
# 指定打印布局,提供以下三种布局
# 1.HTMLLayout:以HTML表格形式布局
# 2.SimpleLayout:包含简单信息的布局方式
# 3.PatternLayout:用户自定义布局
log4j.appender.CONSOLE.layout=org.apache.log4j.SimpleLayout
# 上一句等同于下两句
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layou=%p - %m%n
# 文件打印
log4j.appender.FILE=org.apache.log4j.FileAppender
# 指定文件保存位置
log4j.appender.FILE.File=d:/log.log
# 指定打印布局
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
# 自定义样式
# %r表示输出自应用启动到输出该日志信息所耗费的毫秒数
# %d表示输出输出该日志的日期时间(默认格式为ISO8601,追加{}可自定义日期时间格式)
# %p表示输出输出该日志的日志级别
# %t表示输出产生该日志的线程名
# %c表示输出调用该日志的类的全限定类名.方法名
# %l表示输出该日志打印语句的全限定类名.方法名(类名.java:行数)
# %F表示输出该日志打印语句的类名
# %m表示输出该日志信息(message字符串)
# %n表示换行
log4j.appender.FILE.layout.ConversionPattern=%r [%d{yyyy-MM-dd HH:mm:ss}] %p %t %c %l %F %m%n
测试想过如下:
0 [2020-05-25 16:17:37] DEBUG http-nio-8080-exec-80 cn.khue.mapper.UserMapper.login org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:139) BaseJdbcLogger.java ==> Preparing: select * from user where name=? and password=?
19 [2020-05-25 16:17:37] DEBUG http-nio-8080-exec-80 cn.khue.mapper.UserMapper.login org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:139) BaseJdbcLogger.java ==> Parameters: admin(String), 123(String)
27 [2020-05-25 16:17:37] DEBUG http-nio-8080-exec-80 cn.khue.mapper.UserMapper.login org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:139) BaseJdbcLogger.java <== Total: 1
3.log4j的使用
- 导入jar包:log4j-1.2.8.jar
- 添加属性文件:/src/log4j.properties
log4j.rootLogger=ERROR,CONSOLE,FILE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.Target=System.err log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout=[%d] %p %c %m%n log4j.appender.FILE=org.apache.log4j.FileAppender log4j.appender.FILE.File=d:/mylog.log log4j.appender.FILE.layout=org.apache.log4j.PatternLayout log4j.appender.FILE.layou.ConversionPattern=[%d] %p %c %m%n
- 程序调用
public class Test{ public static void main(String[] args){ //Logger looger=Logger.getLogger(Test.class); Logger logger=Logger.getLogger("cn.khue.test.Test"); logger.error("测试"); try{ int a=1/0; }catch(Exception e){ looger.error(e); } } }
Mybatis
Mybatis是一个半自动的ORM框架,Hibernat是一个全自动的ORM框架
1.Mybatis执行流程
Mybatis核心配置文件 - SqlSessionFactoryBuilder - SqlSessionFactory - SqlSession - 数据库
- 基于传统DAO层开发(无接口)
- 基于代理模式开发(接口)
2.基于DAO层开发的一般流程
- 导入jar包
Mybatis核心包:mybatis-3.2.7.jar
日志文件包:log4j-1.2.17.jar
MySQL驱动包:mysql-connector-java-5.1.38.jar - 准备Mybatis核心配置文件
<?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"> <!--配置MySQL的事务管理方式--> <transactionManager type="JDBC"/> <!--配置MySQL数据库连接信息--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useSSL=false"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--配置映射文件信息--> <mappers> <!--配置单个映射文件--> <mapper resource="cn/khue/test/mapper/TestMapper.xml"/> </mappers> </configuration>
- 准备log4j配置文件
# 日志打印级别及打印方式(控制台还是持久化) log4j.rootLogger=DEBUG,CONSOLE # 控制台显示 log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.SimpleLayout
- 准备实体类
public class User implements Serializable{ private int id; private String name; private String password; ... }
注意:实体类必须要有无参构造器,mybatis通过反射构建参数时,会调用class.newInstance方法
- 准备映射文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="user"> <!--查询指定数据--> <select id="login" resultType="int"> select * from user where name=#{name} and password=#{password} </select> <!--查询所有数据--> <select id="queryAll" resultType="cn.khue.entity.User"> select * from user </select> <!--更新指定数据--> <update id="updateByID"> update user set name=#{name}, password=#{password} where id=#{id} </update> <!--删除指定数据--> <delete id="deleteByID"> delete from user where id=#{id} </delete> <!--新增一条数据--> <insert id="insertNew"> insert into user values(default, #{name}, #{password}) </insert> </mapper>
- 测试
public class Test { public static void main(String[] args) throws IOException { InputStream is = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactoryBuilder factoryBuilder=new SqlSessionFactoryBuilder(); SqlSessionFactory factory=factoryBuilder.build(is); //SqlSession sqlSession = factory.openSession(true);-设置自动提交事务 SqlSession sqlSession = factory.openSession(); //查询指定信息 User user=new User("admin","123"); int i = sqlSession.selectOne("user.login", user); System.out.println(i); //查询所有信息 List<User> users = sqlSession.selectList("user.queryAll"); for (User user1 : users) { System.out.println(user1); } //修改指定信息 user.setId(1); user.setName("user"); int update = sqlSession.update("user.updateByID", user); System.out.println(update); //删除指定信息 int delete = sqlSession.delete("user.deleteByID", 1); System.out.println(delete); //新增指定信息 user.setId(1); user.setName("admin"); user.setPassword("123"); int insert = sqlSession.insert("user.insertNew", user); System.out.println(insert); //手动提交事务-不设置的话会自动回滚 sqlSession.commit(); //关闭资源 sqlSession.close(); } }
3.配置优化一:使用typeAliases标签简化mapper
在mapper文件中,对于resultType需要写实体类的全限定路径,不免过于繁琐,可以使用别名
- 修改mybatis的核心配置文件,新增typeAliases标签
<?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> <!--配置实体类别名--> <typeAliases> <!--添加单个实体类别名--> <typeAliase type="cn.khue.entity.User" alias="user" /> <!--添加一个目录中的所有类,默认别名是首字母小写的类名--> <package name="cn.khue.entity" /> </typeAliases> </configuration>
- 修改mapper文件的resultType
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="user"> <!--查询所有数据--> <select id="queryAll" resultType="user"> select * from user </select> </mapper>
4.配置优化二:使用properties标签简化数据库配置信息
- 新建mysql-config.properties
driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false username=root password=root
- 修改mybatis核心配置文件
<?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> <!--配置数据库配置文件--> <properties resource="mysql-config.properties" /> <!--也可以用这种写法,如果同时存在,或先读取外部的--> <properties resource="mysql-config.properties"> <property name="driver" value="com.mysql.jdbc.Driver" /> </properties> <!--配置数据库连接信息--> <environments default="development"> <environment id="development"> <!--配置MySQL的事务管理方式--> <transactionManager type="JDBC"/> <!--配置MySQL数据库连接信息--> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!--配置映射文件信息--> <mappers> <!--配置单个映射文件--> <mapper resource="cn/khue/test/mapper/TestMapper.xml"/> </mappers> </configuration>
5.SqlSession的三种查询方式
//1.返回单个对象
Object object=sqlSession.selectOne("queryByID",11);
//2.返回List集合
List<Object> list=sqlSession.selectList("queryAll");
//3.返回Map集合 - Map的键为name,值为查询的对应列的数据
Map<Object,Object> map=sqlSession.selectMap("queryAll","name");
6.mapper文件的三种传参方式
- 单个基本类型+String传参:(#取参)mapper中参数可随意写,也可以使用0(数组)或param1(Map)
sqlSession.selectList("queryByID",1);
<select id="queryByID" parameterType="int" resultType="cn.khue.entity.User"> select * from user where id=#{id} </select> <select id="queryByID" parameterType="int" resultType="cn.khue.entity.User"> select * from user where id=#{0} </select>
- 对象传参:mapper中的参数必须是传递的对象的属性名
user.setId(1); sqlSession.selectList("queryByID",user);
<select id="queryByID" parameterType="cn.khue.entity.User" resultType="cn.khue.entity.User"> select * from user where id=#{id} </select>
- Map传参:mapper中的参数必须是传递的Map对应的键名
Map<Integer,String> map=new HashMap<>(); map.put(1,"admin"); map.put(2,"123"); sqlSession.selectList("queryByNameAndPassword",map);
<select id="queryByNameAndPassword" parameterType="map" resultType="cn.khue.entity.User"> select * from user where name=#{1} and password=#{2} </select>
注意:mapper文件中的parameterType可以省略不写,resultType在select中必须写
7.mapper文件中#与$的区别
<!--使用#取值-->
<select id="queryByName" resultType="cn.khue.entity.User">
select * from user where name=#{name}
</select>
<!--使用$取值-->
<select id="queryByName" resultType="cn.khue.entity.User">
select * from user where name="${name}"
</select>
<!--区别:
#
1.底层使用的是PrepareStatement对象,会使用?占位符:
2.对于varchar/String类型不用再拼接引号
3.对于单个基本数据类和String传参取参可使用任意字符或0
$
1.底层使用的是Statement对象,是直接拼接:
2.对于varchar/String类型还需要拼接引号
3.参数是基本数据类型/String,${}中只能写value
4.在order by ${param}中只能用$
-->
8.模糊查询的实现
<select id="fuzzyQueryByName" resultType="cn.khue.entity.User">
select * from user where name like '%${value}%'
</select>
9.基于代理模式开发的一般流程
基于DAO层开发的弊端:直接读取mapper文件(多参数传递问题、方法调用不规范)
- 创建mapper接口
public interface UserMapper{ //查询所有数据 List<User> queryAll(); }
- 创建mapper:此时namespace必须是对应接口的全限定名;此时id必须是对于接口中方法的名称
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.khue.mapper.UserMapper"> <!--查询所有数据--> <select id="queryAll" resultType="cn.khue.entity.User"> select * from user </select> </mapper>
- 修改mybatis核心配置文件的mappers标签
<?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> <!--配置数据库配置文件--> <properties resource="mysql-config.properties" /> <!--配置别名--> <typeAliases> <package name="cn.khue.entity" /> </typeAliases> <!--配置数据库连接信息--> <environments default="development"> <environment id="development"> <!--配置MySQL的事务管理方式--> <transactionManager type="JDBC"/> <!--配置MySQL数据库连接信息--> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!--配置映射文件信息--> <mappers> <!--配置单个映射文件(映射文件和接口文件同名的情况下,会同时加载映射文件和接口文件)--> <mapper class="cn.khue.mapper.UserMapper" /> <!--直接包扫描--> <package name="cn.khue.mapper" /> </mappers> </configuration>
- 测试
public class Test{ public static void main(Stirng[] args){ InputStream is=Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory factory=new SqlSessionFactoryBuilder.build(is); SqlSession sqlSession=factory.openSession(); //读取接口文件信息 UserMapper userMapper=sqlSession.getMappser(UserMapper.class); //查询 List<User> list=userMapper.queryAll(); //关闭资源 sqlSession.close(); } }
注意:
1.映射文件和接口文件名称必须相同
2.映射文件中namespace必须是对应接口文件的全限定名
3.映射文件中id必须是对应接口中对应的方法名
10.基于代理模式开发的参数传递
- 单个基本数据类型+String:名称任意写
- 多个基本数据类型+String:名称为0、1…(使用数组封装)或param1、param2…(使用map封装)或使用别名
- 单个引用类型:属性名
- 多个引用类型:别名.属性名
public interface UserMapper{
//传递单个基本数据类型
User queryByID(int id);
//传递多个传递基本数据类型
User queryByNameAndPassword01(String name, String password);
User queryByNameAndPassword02(String name, String password);
User queryByNameAndPassword03(@Param("name")String name, @Param("password")String password);
//传递单个引用类型
User queryByName(User user);
//传递多个引用类型
User queryByNameAndPassword04(@Param("u1")User user01, @Param("u2")User user02);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.khue.mapper.UserMapper">
<!--根据ID查询-->
<select id="queryByID" resultType="cn.khue.entity.User">
select * from user where id=#{id}
</select>
<!--根据name和password查询-->
<select id="queryByNameAndPassword01" resultType="cn.khue.entity.User">
select * from user where name=#{0} and password=#{1}
</select>
<select id="queryByNameAndPassword02" resultType="cn.khue.entity.User">
select * from user where name=#{param1} and password=#{param2}
</select>
<select id="queryByNameAndPassword03" resultType="cn.khue.entity.User">
select * from user where name=#{name} and password=#{password}
</select>
<!--根据name查询-->
<select id="queryByName" resultType="cn.khue.entity.User">
select * from user where name=#{name}
</select>
<!--根据name和password查询-->
<select id="queryByNameAndPassword04" resultType="cn.khue.entity.User">
select * from user where name=#{u1.name} and password=#{u2.password}
</select>
</mapper>
11.动态SQL
public interface UserMapper{
//根据user条件查询
List<User> queryByCondition01(User uer);
List<User> queryByCondition02(User uer);
List<User> queryByCondition03(User uer);
//根据user条件更新
int updateByCondition01(User user);
int updateByCondition02(User user);
//传入数组、集合
List<User> queryByCondition04(int ... ids);//可变参数本质是数组
List<User> queryByCondition05(List list);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.khue.mapper.UserMapper">
<!--多分支,前一个执行,后面依然执行-->
<select id="queryByCondition01" resultType="user">
select * from user
<where>
<if test="name!=null and name!=''">and name=#{name}</if>
<if test="age!=0">and age=#{age}</if>
</where>
</select>
<!--单分支,前一个执行,后面就不再执行-->
<select id="queryByCondition02" resultType="user">
select * from user
<where>
<choose>
<when test="name!=null and name!=''">and name=#{name}</when>
<when test="age!=0">and age=#{age}</when>
<!--otherwise可加可不加-->
<otherwise>1=1</otherwise>
</choose>
</where>
</select>
<!--等价于多分支-->
<select id="queryByCondition03" resultType="user">
select * from user
<!--prefix是添加指定前缀,prefixOverrides是去掉指定前缀-->
<trim prefix="where" prefixOverrides="and">
<if test="name!=null and name!=''">and name=#{name}</if>
<if test="age!=0">and age=#{age}</if>
</trim>
</select>
<update id="updateByCondition01">
update user
<set>
<if test="name!=null and name!=''">name=#{name},</if>
<if test="age!=0">age=#{age},</if>
</set>
where id=#{id}
</update>
<update id="updateByCondition02">
update user
<!---suffixOverrides是去掉指定后缀-->
<trim prefix="set" suffixOverrides=",">
<if test="name!=null and name!=''">name=#{name},</if>
<if test="age!=0">age=#{age},</if>
</trim>
where id=#{id}
</update>
<!--Mybatis会将传入的List或数组封装进一个Map中,List用list为键,数组用array为键-->
<select id="queryByCondition04" resultType="user">
select * from user where id in
<foreach collection="array" open="(" separator="," close=")" item="id">#{id}</foreach>
</select>
<!--select * from user where id in(1,2,3)-->
<select id="queryByCondition05" resultType="user">
select * from user where id in
<foreach collection="list" open="(" separator="," close=")" item="id">#{id}</foreach>
</select>
<!--sql片段-->
<sql id="query">
select * from user
</sql>
<select id="queryByCondition05" resultType="user">
<include refid="query"/>
where id in
<foreach collection="list" open="(" separator="," close=")" item="id">#{id}</foreach>
</select>
</mapper>
12.模糊查询
public interface UserMapper{
List<User> queryLikeName01(@Param("name")String name);
List<User> queryLikeName02(String name);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.khue.mapper.UserMapper">
<select id="selectLikeName01" resultType="user">
<bind name="name" value="'%'+name+'%'"/>
select * from user where name like #{name}
</select>
<select id="queryLikeName02" resultType="user">
select * from user where name like"%${value}%"
</select>
</mapper>
13.手动映射
sql查询的结果集与实体类的属性名称不一致时,可以通过别名或resultMap标签解决
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.khue.mapper.UserMapper">
<!--别名-->
<select id="queryAll" resultType="user">
select id uid, name uname, password from user
</select>
<!--resultMap标签-->
<resultMap id="userMapping" type="users">
<id property="uid" column="id" />
<result property="uname" column="name" />
<!--同名字段可省略-->
</resultMap>
<select id="queryAll" resultMap="userMapping">
select * from user
</select>
</mapper>
注意:在resultMap中,如果只有id和result子标签存在的话,可以省略同名字段
14.多表关联一对一手动映射
实体类文件
class Student{
private int id;
private String name;
private String gender;
private double score;
private int classID;
private Classes class;
...
}
class Classes{
private int id;
private String name;
...
}
mapper接口文件
public interface StudentMappser{
//查询所有学生信息(一个学生对应一个班级)
List<Student> queryAll();
}
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">
<mapper namespace="cn.khue.mapper.StudentMapper">
<resultMap id="studentMapping" type="student">
<id property="id" column="id" />
<result property="name" column="name"/>
<result property="gender" column="gender"/>
<result property="score" column="score"/>
<association propery="classes" javaType="classes">
<id property="id" column="cid" />
<result property="name" column="cname">
</association>
</resultMap>
<select id="queryAll" resultMap="studentMapping">
select s.id,s.name,s.gender,s.score,s.classID,c.id cid,c.name cname from student s left join class c on s.classID=c.id
</select>
</mapper>
注意:
1.在此例中,由于resultMap中还有association子标签存在,所以不可可以省略同名字段
2.如果字段名重复,必须使用别名
15.多表关联一对多手动映射
实体类文件
class Student{
private int id;
private String name;
private String gender;
private double score;
private int classID;
private Classes classes;
...
}
class Classes{
private int id;
private String name;
private List<Student> students;
...
}
mapper接口文件
public interface ClassesMappser{
//根据班级ID查询(一个班级对应多个学生)
Classes queryByID(int id);
}
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">
<mapper namespace="cn.khue.mapper.ClassesMapper">
<resultMap id="classesMapping" type="student">
<id property="id" column="id" />
<result property="name" column="name"/>
<collection property="students" ofType="student">
<id property="id" column="id" />
<result property="name" column="name"/>
<result property="gender" column="gender"/>
<result property="score" column="score"/>
</collection>
</resultMap>
<select id="queryByID" resultMap="classesMapping">
select c.id cid,c.name cname,s.id,s.name,s.gender,s.score,s.classID from class c left join student s on c.id=s.classID where c.id=#{id}
</select>
</mapper>
16.多表关联多对多手动映射
多对多的实现需要拆分成三张表
实体类文件
class Student{
private int id;
private String name;
private String gender;
private double score;
private int classID;
private List<SubjectSelected> subjectSelected;
...
}
class SubjectSelected{
private int id;
private int studentID;
private int subejctID;
private List<Subject> subjects;
...
}
class Subject{
private int id;
private String name;
...
}
mapper接口文件
public interface ClassesMappser{
//根据学生ID查询学生选课(一个学生对应多个课程,一个课程对应多个学生)
Student queryByID(int id);
}
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">
<mapper namespace="cn.khue.mapper.StudentMapper">
<resultMap id="studentMapping" type="student">
<id property="id" column="id" />
<result property="name" column="name"/>
<result property="gender" column="gender"/>
<result property="score" column="score"/>
<result property="classID" column="classID"/>
<collection property="subejctSelected" ofType="subjectSelected">
<id property="id" column="ssid" />
<result property="studentID" column="studentID"/>
<result property="subjectID" column="subjectID"/>
<collection property="subjects" ofType="subject">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</collection>
</resultMap>
<select id="queryByID" resultMap="studentMapping">
select s1.id,s1.name,s1.gender,s1.score,s1.classID,
s2.id ssid,s2.studentID studentID,s2.subjectID subjectID,
s3.id sid,s3.name sname
from student s1
left join subject_selected s2 on s1.id=s2.studentID
left join subject s3 on s2.subjectID=s3.id
where s1.id=#{id]
</select>
</mapper>
17.调用其他的namespace下的sql
这里省去了多表关联查询及一对多的映射处理(下面的例子等同于15.多表关联一对多手动映射)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.khue.mapper.StudentMapper">
<resultMap id="studentMapping" type="student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="gender" column="gender"/>
<result property="score" column="score"/>
<result property="classID" column="classID"/>
<association property="classes" javaType="classes" column="classID" select="cn.khue.mapper.ClassMapper.queryByID"/>
</resultMap>
<select id="queryByID" resultMap="studentMapping">
select * from student where id=#{id}
</select>
</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">
<mapper namespace="cn.khue.mapper.ClassMapper">
<select id="queryByID" result="user">
select * from class where id=#{id}
</select>
</mapper>
18.延迟加载/懒加载
延迟加载也叫懒加载,是指在进行表的关联查询时,按照设置的延迟规则推迟对关联对象的select查询,比如在一对多的查询时,只查询出一方,当需要更多数据时,再发出sql语句查询。如此可减少数据库压力
<?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>
<!--配置数据库配置文件-->
<properties resource="mysql-config.properties" />
<!--启动延迟加载,并关闭积极加载-->
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="lazyLoadingEnabled" value="true" />
<setting name="aggressiveLazyLoading" value="false" />
</settings>
<!--配置别名-->
<typeAliases>
<package name="cn.khue.entity" />
</typeAliases>
<!--配置数据库连接信息-->
<environments default="development">
<environment id="development">
<!--配置MySQL的事务管理方式-->
<transactionManager type="JDBC"/>
<!--配置MySQL数据库连接信息-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--配置映射文件信息-->
<mappers>
<!--配置单个映射文件(映射文件和接口文件同名的情况下,会同时加载映射文件和接口文件)-->
<mapper class="cn.khue.mapper.UserMapper" />
<!--直接包扫描-->
<package name="cn.khue.mapper" />
</mappers>
</configuration>
注意:启动懒加载的同时需要关闭积极加载
19.缓存技术
- 一级缓存(默认开启):同一个SqlSession中第一次执行时会将查询的数据写到缓存(HashMap结构,key为hashcode+sqlID+sql语句,value为查询出来的映射对象),第二次执行相同 的sql语句时会直接从缓存中获取,从而提高查询效率
//会清理缓存 sqlSession.commit(); //手动清理缓存 sqlSession.clearCache();
- 二级缓存:同一接口下的多个SqlSession共享缓存数据
开启方式:
1.在核心配置文件中开启全局二级缓存
2.在需要开启二级缓存的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> <!--配置数据库配置文件--> <properties resource="mysql-config.properties" /> <!--开启全局二级缓存--> <settings> <setting name="cacheEnabled" value="true" /> </settings> <!--配置别名--> <typeAliases> <package name="cn.khue.entity" /> </typeAliases> <!--配置数据库连接信息--> <environments default="development"> <environment id="development"> <!--配置MySQL的事务管理方式--> <transactionManager type="JDBC"/> <!--配置MySQL数据库连接信息--> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!--配置映射文件信息--> <mappers> <!--配置单个映射文件(映射文件和接口文件同名的情况下,会同时加载映射文件和接口文件)--> <mapper class="cn.khue.mapper.UserMapper" /> <!--直接包扫描--> <package name="cn.khue.mapper" /> </mappers> </configuration>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.khue.mapper.ClassMapper"> <!--开启二级缓存,因为都采用默认值,所以属性值省略了 cache标签的属性: eviction:缓存的回收策略 默认LRU(最近最少使用) flushinterval:缓存的刷新时间间隔(毫秒) 默认不设置 readOnly:是否只读 默认false size:缓存大小 默认1024 type:自定义缓存的全类名(实现Cache接口即可) blocking:找不到key时,是否一致blocking --> <cache/> <!--useCache与flashCache默认为true和false,所以可以省略--> <select id="queryByID" result="user" useCache="true" flushCache="false"> select * from class where id=#{id} </select> </mapper>
注意:
1.二级缓存需要查询的实体类对象实现java.io.Serializable接口
2.二级缓存虽然跨同一接口的SqlSession,但必须是同一个SqlSessionFactory
20.注解使用
xml配置文件可使用注解替换
public interface StudentMapper{
//查询所有
@Select("select * from student")
@ResultType(Student.class)
List<Student> queryAll();
//根据ID查询
@Select("select * from student where id=#{id}")
Student queryByID(int id);
//新增
@Insert("insert into student values(default,#{name},#{gender},#{score},#{classID})")
int insertStudent(Student student);
//更新
@Update("update student set name=#{name} where id=#{id}")
int updateStudentName(int id,String name);
//删除
@Delete("delete from student where id=#{id}")
int deleteByID(int id);
}
注意:
1.注解对于多表关联查询、缓存问题不好处理
2.注解耦合度高
3.注解与映射文件可同时使用,但同一方法不能同时存在
4.注解适用于不经常修改且语句简单的场景
21.Mybatis的工具类封装
在Mybatis的使用过程中,需要先创建一个SqlSession对象,而在一个项目中需要多次数据操作,可能造成SqlSession语句冗余,下面使用封装解决语句冗余
class MybatisUtil{
private static SqlSessionFactory factory;
//初始化
static{
try{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
}catch(IOException e){
e.printStrackTrance();
}
}
//获取SqlSession
public static SqlSession getSqlSession(boolean isAutoCommit){
return factory.opeanSession(isAutoCommit);
}
}
class Test{
private final SqlSession sqlSession = MybatisUtil.getSqlSession(true);
public void test(){
sqlSession.getMapper(TestMapper.class).test();
}
}
同一个SqlSession多次调用同一sql语句时,只有第一次会执行数据库操作(缓存:提升读取效率、降低数据库负载压力),但在一个项目中,一次请求可能对不同表进行操作,这需要该次请求对不同表操作使用的是同一个SqlSession,可以使用ThreadLocal存储与获取
class MybatisUtil{
private static SqlSessionFactory factory;
private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal();
//初始化
static{
try{
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
}catch(IOException e){
e.printStrackTrance();
}
}
//获取SqlSession
public static SqlSession getSqlSession(boolean isAutoCommit){
if(null == threadLocal.get()){
SqlSession sqlSession = factory.opeanSession(isAutoCommit);
threadLocal.set(sqlSesion);
}
return threadLocal.get();
}
//关闭
public static void closeSqlSession(){
if(null != threadLocal.get()){
threadLocal.get().close();
threadLocal.set(NULL);
}
}
}
class Test{
private final SqlSession sqlSession = MybatisUtil.getSqlSession(true);
public void test(){
sqlSession.getMapper(TestMapper.class).test();
}
}
对于SqlSession的获取、提交、回滚与关闭一般在过滤器中进行,保证一次请求的完整数据操作
@WebFilter("/*")
class MyFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException{}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filteChain) throws IOException, ServletException{
//一个请求初始化一个SqlSession
SqlSession sqlSession = MybatisUtil.getSqlSession(false);
//放行
try{
filterChain.doFilter(servletRequest,servletResponse);
//提交
sqlSession.commit();
}catch(Exception e){
//回滚
sqlSession.rollback();
e.printStackTrace();
}
//关闭SqlSession
MybatisUtil.closeSqlSession();
}
@Override
public void destroy(){};
}
Servlet
1.简单实现
- 前端页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/test/login" method="post"> <input type="text" name="username" /> <input type="password" name="password" /> <br/> <input type="submit" value="登录" /> <intput type="reset" value="重置"> </form> </body> </html>
- web.xml设置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>login</servlet-name> <servlet-class>cn.khue.controller.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>login</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> </web-app>
- controller代码
public class LoginServlet extends HttpServlet{ @Override protected void service(HttpServletRequest req,HttpServletResponse res) throws ServletException, IOException{ //乱码处理 req.setCharacterEncoding("utf-8"); //res.setContentType("text/html"); //res.setCharacterEncoding("utf-8"); res.setContentType("text/html;charset=utf-8"); //接收页面数据 String username=req.getParameter("username"); String password=req.getParameter("password"); //返回数据 PrintWrite writer=res.getWriter(); writer.write("welecome"); writer.close(); } }
2.url-pattern参数
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet-mapping>
<servlet-name>login</servlet-name>
<!--精准匹配-->
<url-pattern>/login</url-pattern>
<!--模糊匹配:固定后缀-->
<url-pattern>*.html</url-pattern>
<!--模糊匹配:固定前缀-->
<url-pattern>/login/*</url-pattern>
<!--模糊匹配:除jsp之外的所有路径-->
<url-pattern>/</url-pattern>
<!--模糊匹配:所有路径-->
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
注意:
1.一个servelt可以对应多个servlet-mapping
2.一个servlet-mapping可以有多个url-pattern
3.Servlet的生命周期
- 实例化-构造方法(服务器启动-执行一次)
- 初始化-init方法(服务器启动执-行一次)
- 服务-service方法(每次请求)
- 销毁-destory方法(服务器关闭-执行一次)
4.关于HttpServletRequest
//获取数组参数
String[] hobby=req.getParameterValues("hobby");
//获取所有参数名
Enumeration<String> parNames=req.getParameterNames();
while(parNames.hasMoreElements()){
String name=parNames.nextElement();
}
//获取参数的键值对
Map<String,String[]> map=req.getParameterMap();
Set<Map.Entry<String,String[]>> entries=map.entrySet();
for(Map.Entry<String,String[]> entry:entries){
String key=entry.getKey();
String value=entry.getValue()
}
req.getMethod();
req.getRequestURL();
req.getRequestURI();
req.getServletPath();
req.getProtocol();
req.getScheme();
...
5.关于get和post
- get请求:
- 数据通过url提交,相对不安全
- 提交的数据量小
- 只能提交文本数据
- post请求:
- 数据通过单独的数据包发送,相对安全
- 提交的数据量大
- 可以提交文件
6.处理get请求乱码
方式一:
String name=req.getParameter("name");
byte[] bytes=name.getBytes("ISO-8859-1");
String nameCode=new String(bytes,"utf-8");
方式二:设置Tomcat的URI编码字符集
<!-- /Tomcat安装目录/conf/server.xml -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="utf-8" />
7.请求转发与响应重定向
请求转发:
class Servlet01 extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
//转发请求
req.getRequestDispatcher("/servlet02").forward(req,res);
}
}
class Servlet02 extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
...
}
}
响应重定向:
class Servlet01 extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
//响应重定向
res.sendRedirect("/项目名/servlet02");
}
}
class Servlet02 extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
...
}
}
区别:
1.浏览器地址栏:请求转发不变;重定向改变
2.请求参数的传递性:请求转发可传递;重定向不可传递
3.能否访问WEB-INF:请求转发可以;重定向不可以
4.能否访问外部资源:请求转发不可以;重定向可以
8.ServletConfig与ServletContext
ServletConfig是用于配置一个Servlet的初始化信息的对象
ServletContext是Serlvet上下文对象,可用于配置所有Servlet的初始化信息,也是一个域对象
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>test02</param-name>
<param-value>this is a test<param-value>
</context-param>
<servlet>
<servlet-name>servlet01</servlet-name>
<servlet-class>cn.khue.controller.Servlet01</servlet-class>
<init-param>
<param-name>test01</param-name>
<param-value>this is a test</param-value>
</init-param>
</servlet>
</web-app>
class Servlet01 extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
ServletConfig config=this.getServletConfig();
String test01=config.getInitParameter("test01");
ServletContext context=this.getServletContext();
ServletContext context=req.getServeltContext();
String test02=context.getInitParameter("test02");
}
}
9.会话管理技术
cookie:保存在浏览器端的一种会话技术
第一次请求时,服务器会发送一个cookie给浏览器,第二次请求时,浏览器会将cookie再发送给服务器
class Servlet01 extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
Cookie cookie=new Cookie("test","this is a test");
//设置存储时间(单位为秒)
cookie.setMaxAge(1)
//设置cookie的提交路径,只有访问该路径才会提交cookie
cookie.setPath("/test");
res.addCookie(cookie);
//通过请求头获取cookie
String cookie=req.getHeader("Cookie");
//直接获取
Cookie[] cookies=req.getCookie();
}
}
session:保存在服务器端的一种会话技术
class Servlet01 extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException{
HttpSession session=req.getSession();
session.setAttribute("test","this is test");
String test=(String)sesion.getAttribute("test");
//设置session对象活动的最大时间间隔(单位秒),默认值在/Tomcat安装目录/conf/web.xml中有配置(<session-timeout>30</session-timeout>)
session.setMaxInactiveInterval();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--覆盖了Tomcat中session的配置-->
<session-config>
<session-timeout>40</sesion-timeout>
<session-config>
</web-app>
10.使用session实现1分钟免登录
1.项目结构
-
核心思路:
web.xml中配置登录页的servlet(servlet中登录成功后在session存入用户信息,并重定向到一个地址)
web.xml中配置登录成功访问地址的servlet(servlet中获取session验证用户信息,成功则转发到主页)
web.xml中配置session存活时间
主页通过js验证session
11.BaseServlet的封装
在开发项目时,每个功能都需要创建一个Servlet来处理,如果功能过多,则项目代码非常臃肿,可以向上封装,提取一个BaseServlet类,然后将每个模块的功能封装成多个个Servlet,一个模块实现的多个功能分别放进该Servlet的多个方法,然后在BaseServlet中根据方法名实现具体功能(需要在请求中传递方法名)
class BaseServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest httpServletRequest, HttpServletResponse httpservletResponse) throws ServletException, IOExcetpion{
//获取方法名
String methodName = httpServletRequest.getParameter("method");
//反射动态调用该方法
Class cla=this.getClass();
try{
Method method = cla.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this,httpServletRequest,httpServletResponse);
}catch(Exception e){
e.printStackTrace();
}
}
}
@WebServlet("/user")
class UserServlet extends BaseServlet{
private final UserService userService = new UserServiceImpl();
public void queryAllUser(HttpServletRequest req, HttpServletResponse res) throws IOException{
List<User> list = userService.queryAllUser();
res.getWriter().write(new Gson().toJson(list));
}
...
}
JSP
1.jsp的执行流程
在第一次访问jsp文件时,tomcat(tomcat的web.xml中org.apache.jasper.servlet.JspServlet)会将jsp转换成Java文件xx.jsp - xx_jsp.java,然后会编译Java文件xx_jsp.java - xx_jsp.class,最后运行xx_jsp.class文件
2.常用的指令标签
- page:导包、指定编码、指定脚本、指定错误页面
在web.xml中配置错误页<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="cn.khue.test.Test" %> <%@ page errorPage="error500.jsp" %> <!-- 指定当前页为错误页,当前页可接收异常对象 --> <%@ page isError="true" %>
<error-page> <error-code>500</error-code> <location>error500.jsp</location> </error-page>
- Include:静态导入页面
- Taglib:导入JSTL
3.九大内置对象
当jsp页面转化成servlet之后,在其_jspService方法中可看到以下对象的创建(位置:C:\Users\Khue\AppData\Local\JetBrains\IntelliJIdea2020.1\tomcat\Tomcat_8_5_272_sport02\work\Catalina\localhost\sport02\org\apache\jsp\jsp)
- HttpServletRequest requst:域对象-单次请求
- PageContext pageContext:域对象-当前页面
- HttpSession session:域对象-单次会话
- ServletContext application:域对象-整个项目
- HttpServletResponse response
- JspWriter out
- ServletConfig config
- Object page=this
- Throwable exception(错误页才有)
4.EL表达式
EL表达式用于简化域对象的取值操作
//格式:${域标志.参数名}
${pageScope.xx}
${requestScope.xx}
${sessionScope.xx}
${applicationScope.xx}
也可用于取出请求参数
// 格式:单值${param.xx},多值${paramValues.xx[0]}
EL表达式也支持运算符使用
算术运算符:+、-、*、/、%
比较运算符:== eq、> gt、< lt、>= ge、<= le、 != ne
条件运算符:||、 &&
三目运算符:条件?表达式一:表达式二
空判断:empty
域标志可以省略
同名域参数的优先级(pageContext-request-session-application)
原理:反射,取值时使用的get方法
如果是字符串运算,会尝试将字符串转换成数字,失败会报异常
如果除零会输出infinity,而不会报异常
empty可用于判断null对象、空集合、空字符串,不能用于判断数组元素是否为空
5.JSTL
JSTL是Jsp Standard Tag Library的简写,用于简化jsp操作,其本身也是Java代码,使用需要导包(低版本需要导入两个jar,1.2+版本只需导入一个jar),然后在jsp页面通过taglib引入指定标签库即可(隐射关系存在.tld文件中)
- 核心标签库core
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
- 格式化标签库format
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
- 函数标签库function
- SQL标签库
- XML标签库
GSON
Gson可以快速将Java对象和json字符串进行相互转换
- 导入gson-2.2.4.jar
- 后端使用
User user=new User(1,"khue",25); Gson gson=new Gson(); //将单个对象转换成json字符串 String userJson=gson.toJson(user);
- 前端使用
<script> var user=JSON.parse(data); eval("var user="+data); </script>
AJAX
1.Ajax的使用
AJAX是Asynchronous JavaScript And XML的缩写,可用于异步访问、局部刷新
<!-- 使用原生JS操作Ajax比较繁琐,一般使用jQuery操作Ajax -->
<script>
$(function (){
$.ajax({
url:"/test", //请求路径
type:"post", //请求方式
asyc:true, //是否异步
cache:true, //是否使用缓存
dataType:"json", //响应数据解析格式
data:{name:"khue",age:25}, //请求数据
//响应成功函数
success:function(data){
alert("请求成功");
},
//响应失败函数
error:function(){
alert("请求失败");
}
});
//精简版
$.get("/test",{name:"khue",age:25},function(data){
alert("请求成功");
},"json");
$.post("/test",{name:"khue",age:25},function(data){
alert("请求成功");
},"json")
})
</script>
2.使用Ajax实现三级联动
思路:Ajax异步局部刷新+jQuery的change事件
<html lang="en">
<head>
<title>Title</title>
<meta charset="UTF-8">
<style>
div {
text-align: center;
}
</style>
</head>
<body>
<div>
<label for="level-1">一级
<select id="level-1" name="level01">
<option value="0">请选择</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</label>
<label for="level-2">二级
<select id="level-2">
<option value="0">请选择</option>
</select>
</label>
<label for="level-3">三级
<select id="level-3">
<option value="0">请选择</option>
</select>
</label>
</div>
</body>
<script src="js/jquery-1.8.3.min.js"></script>
<script>
$(function () {
//二级联动
//一级内容改变事件
$("#level-1").change(function () {
//获取一级内容
const val01 = $(this).val();
//传值给二级
fillHtml("level-2", val01, null);
//传值给三级
fillHtml("level-3", null, null);
});
//三级联动
//二级内容改变事件
$("#level-2").change(function () {
//获取一级内容
const val01 = $("#level-1").val();
//获取二级内容
const val02 = $(this).val();
//传值给三级
fillHtml("level-3", val01, val02);
});
//内容填充函数
function fillHtml(object, data01, data02) {
//获取当前层级对象
const $this = $("#" + object);
//填充选择项
$this.html("<option value=\"0\">请选择</option>\n");
//这里的非空与字符串判断主要是为了实现
// 1.在一级内容改变时,三级内容清空
// 2.在一级内容为0时,二、三级无选择
// 3.在二级内容为0时,三级无选择
if (null != data01 && "0" !== data01 && "0" !== data02) {
if(data02 != null){
data01=data01+"."+data02;
}
$this.append("" +
"<option value=\"1\">" + data01 + ".1</option>\n" +
"<option value=\"2\">" + data01 + ".2</option>\n" +
"<option value=\"3\">" + data01 + ".3</option>")
}
}
})
</script>
</html>
3.使用Ajax实现分页
- 后端核心:
entity层
mapper层XML版public class PageResult{ //分页后的数据 private List<User> list; //总的数据量 private int totalSize; //总的页码 private int totalPages; ... }
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"> <mapper namespace="cn.khue.mapper.UserMapper"> <!--分页查询--> <select id="queryByPage" resultType="user"> select * from user limit #{param1}, #{param2} </select> <select id="queryTotalSize" resultType="int"> select count(id) from user </select> <!--动态模糊分页查询--> <select id="qeuryByCondition" resultType="user"> select * from user <where> <if test="param3 != null and param3 != ''"> <bind name="name" value="'%'+param3+'%'"></bind> and name like #{name} </if> <where> limit #{param1}, #{param2} </select> </mapper>
serviceimpl层//分页查询语句 @Select("select * from user limit #{param1}, #{param2}") List<User> queryByPage(int startPage,int pageSize); //获取总数据量 @Select("select count(id) from user") int queryTotalSize(); //动态模糊查询 @Select("<script>select * from user"+ "<where><if test='param3 != null and param3 != \"\"'>and name like concat('%',#{param3},'%')</if></where>"+ "limit #{param1}, #{param2}</script>") List<User> queryByCondition(int startPage,int pageSize, String name);
@Override public PageResult qeuryAllUserByPage(int startPage,int pageSize){ //起始页换算 startPage = (startPage - 1) * pageSize; //获取总页数 int totalSize=mapper.queryTotalSize(); int totalPages=Math.ceil((totalSize * 1.0) / pageSize); //查询 List<User> list=mapper.qeuryByPage(startPage,pageSize); //封装 return new PageReustl(list,totalSize,totalPages); }
- 前端回显
<script> $.ajax({ url:"queryByCondition", type:"post", dataType:"json", data:{startPage:xx,pageSize:xx,name:$("#name").val()}, success:function(data){ const result=eval(data); $("#tb").empty(); for(let i=0;i<result.list.length;i++){ $("#tb").append("xx"); } } }) </script>
文件上传下载的实现
1.文件上传的核心问题
- 前端如何提交文件?
form表单默认对表单数据进行编码,在上传文件时,需要修改form的enctype<!--默认enctype='application/x-www-form-urlencoded'--> <form enctype="multipart/form-data" method="post"> <input type="file" name="file"> <input type="submit" value="上传"> </form>
- 后端如何接收并区分?
由于前端提交的并非编码之后的键值对形式的数据,所以需要借助commons-fileupload工具包解析HttpServletRequest中的数据//导入commons-fileupload-1.3.2.jar和comons-io-2.5.jar FileItemFactory fileItemFactory=new DiskFileItemFactory(); ServletFileUpload servletFileUpload=new ServletFileUpload(fileItemFactory); List<Item> items=serveltFileUpload.parseRequest(req); for(FileItem item:items){ if(item.isFormField()){ //普通表单数据 }else{ //文件数据 } }
- 文件类型及大小如何限制?
//类型限制 - contentType String contentType=item.getContentType(); if(contentType.equals("image/png") || contentType.equals(".jpg") || contentType.equals("image/jpeg")){ //符合 }else{ //不符合 } //类型限制 - 后缀名 String fileType=item.getName().subString(item.getName().lastIndexOf(".")); if(fileType.equals(".png") || fileType.equals(".jpg") || fileType.equals(".jpeg")){ //符合 }else{ //不符合 } //大小限制 - 解析对象限制 ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory()); //设置上传资源中所有资源总和最大限制 upload.setSizeMax(1024*1024*2); //设置上传资源中单个文件最大限制 upload.setFileSizeMax(1024*1024); //大小限制 - 遍历对比 //单位为B 1KB=1024B 1MB=1024KB 1GB=1024MB 1TB=1024GB 1PB=1024TB 1EB=1024PB long fileSize=item.getSize(); if(fileSize <= 1024*1024){ //符合 }else{ //不符合 }
- 文件重名如何解决?
//真实存储在服务器的文件必须重命名,且名称唯一 //之前的名字使用记录表记录关联即可 String oldNmae=item.getName(); String suffix=oldNmae.subString(oldName.lastIndexOf(".")); String newName=UUID.randomUUID()+suffix;
- 文件存储路径如何解耦?
properties配置文件
处理代理path=d:/
//可以以配置文件的形式对外暴露 Properties properties=new Properties(); properties.load(this.getClass().getClassLoader().getResourceAsStream(upload.properties")); String filePath=properties.getProperty("path"); //直接存储在项目根目录下的upload目录 String filePath=this.getServletContext().getRealPath("/upload"); item.write(new File(filePath,fileName));
- 文件删除后如何恢复?
文件一般不会直接物理删除,而是更改文件记录表的isDel状态(文件记录表的一般字段:id、userID、path、name、uploadTime、desc、isDel)
2.文件下载的核心问题
- 文件如何下载?
res.setContentType(fileType); FileInputStream fis=new FileInputStream(filePath); ServletOutputStream sos=res.getOutputStream(); IOUtils.copy(fis,sos); sos.close(); fis.close();
- 页面如何实现另存为?
上面实现的文件下载会直接跳转到一个新页面,该页面地址为文件的存储地址,下面代码实现了点击下载,跳出另存为提示框res.setContentType(fileType); res.setHeader("Content-Disposition", "attachment;filename="+fileName);
过滤器
在Servlet执行之前用来过滤请求的类,可以接收req对象对请求进行判断、拦截、放行;可以接收resp对象进行响应;
实现:
- 实现java.servelt.Filter并重写方法(init方法-服务器启动时触发、doFilter方法、destroy方法-服务器关闭时触发)
public class MyFilter implements Filter{ @Override public void init(FilterConfig filterConfig) throws ServletException{} @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException{ //放行 filteChain.doFiler(servletRequest,servletResponse); } @Overrde public void destory(){} }
- 配置web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>my-filter</filter-name> <filter-class>cn.khue.filter.MyFilter</filter-class> </filter> <filter-mapping> <filter-name>my-filter</filter-name> <!--拦截范围--> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
除了使用web.xml的filter标签,也可以直接在自定义的过滤器类上使用@WebFilter("/*")注解
监听器
监听对象(一次请求request、一个用户session、不同用户application),符合监听要求即执行监听方法
实现:
- 实现对应接口并重写方法
//Request监听器 public class MyRequestListener01 implements ServletRequestListener{ @Override public void requestInitialized(ServletRequestEvent servletRequestEvent){ System.out.println("request初始化"); } @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent){ servletRequestEvent.getServletContext(); servletRequestEvent.getServletRequest(); System.out.println("request销毁"); } } public class MyRequestListener02 implements ServletRequestAttributeListener{ @Override public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent){ servletRequestAttributeEvent.getName(); servletRequestAttributeEvent.getValue(); servletRequestAttributeEvent.getServletContext(); servletRequestAttributeEvent.getServletRequest(); System.out.println("request中新增一份数据"); } @Override public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent){ System.out.println("request中移除一份数据"); } @Override public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent){ System.out.println("request中替换一份数据"); } } //Session监听器 public class MySessionListener01 implements HttpSessionListener{ @Override public void sessionCreated(HttpSessionEvent httpSessionEvent){} @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent){} } public class MySesionListener02 implements HttpSessionAttributeListener{ @Override public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent){} @Override public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent){} @Override public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent){} } //application监听 public class MyApplicationListener01 implements ServletContextListener{ @Override public void contextInitialized(ServletContextEvent servletContextEvent){} @Override public void contextDestroyed(ServletContextEvent servletContextEvent){} } public class MyApplicationListener02 implements ServletContextAttributeListener{ @Override public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent){} @Override public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent){} @Override public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent){} }
- 配置web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <listener> <listener-class>cn.khue.listener.MyRequestListener01</listener-class> </listener> <listener> <listener-class>cn.khue.listener.MyRequestListener02</listener-class> </listener> <listener> <listener-class>cn.khue.listener.MySessionListener01</listener-class> </listener> <listener> <listener-class>cn.khue.listener.MySessionListener02</listener-class> </listener> <listener> <listener-class>cn.khue.listener.MyApplicationListener01</listener-class> </listener> <listener> <listener-class>cn.khue.listener.MyApplicationListener02</listener-class> </listener> </web-app>