学习回顾_Java中级

MySQL

1. SQL基础

SQL(Structured Query Language) - 结构化查询语言

1.1 结构

  1. DQL:Data Query Language - 查询数据
  2. DML:Data Manipulation Language - 操作数据,包括增(insert)、删(delete)、改(update)、查(select)
  3. DDL:Data Definition Language - 定义数据,主要针对数据库对象(表、索引、视图、触发器、存储过程、函数、表空间等)进行创建(create)、修改(alter)和删除(drop)操作
  4. DCL:Data Control Language - 用来控制数据库权限,如:授权(grant)、回收(revoke)
  5. TCL:Transaction Control Language - 用于数据库的事务管理,如:开启事务(start transaction)、提交事务(commit)、回滚事务(rollback)、设置事务的属性(set transaction)

1.2 数据库字段的类型

  1. 整数型:TINYINT(1字节)、SMALLINT(2字节)、MEDIUMINT(3字节)、INT/INTEGER(4字节)、BIGINT(8字节)
  2. 浮点型:FLOAT(4字节)、DOUBLE(8字节)
  3. 字符型:CHAR、VARCHAR、TEXT…
  4. 日期型:DATE(yyyy-MM-dd)、DATETIME(yyyy-MM-dd HH:mm:ss)…

1.3 DDL操作

  1. 查看所有数据库名称
    show databases;
    
  2. 创建数据库
    create database test;
    //替换
    create or replace database test;
    
  3. 使用数据库
    //use 不是SQL语句,而是MySQL指令,所以后面可以不加分号
    use test
    
  4. 删除数据库
    drop database test;
    
  5. 查看所有表名
    show tables;
    
  6. 创建表
    create table tableName(
    	id int(6),
    	name varchar(10),
    	gender char(2),
    	age int(3)
    );
    
  7. 查看表结构
    desc tableName;
    
  8. 删除表
    drop table tableName;
    
  9. 修改表名
    //to可以省略
    alter table tableNmae 
    rename to tableName2;
    
  10. 查看表的创建过程
    show create table tableName;
    
  11. 增加字段
    //默认追加在最后
    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;
    
  12. 删除字段
    alter table tableName 
    drop score;
    
  13. 修改字段
    //修改字段的数据类型
    alter table tableName 
    modify score double(5,2);
    
    //修改字段名称
    alter table tableName 
    change score scores double(4,1);
    

1.4 DML操作

  1. 新增数据
    //按字段顺序增加数据
    insert into tableName 
    values(1,"赵大土","男",25);
    
    //增加指定字段的数据
    insert into tableName(name,age) 
    values("大土",26);
    
  2. 修改数据
    update tableName 
    set age=20,name="大土" 
    where id=1;
    
  3. 删除数据
    //删除id为1的行数据(逐条扫描,一一判断)
    delete from tableName where id=1;
    
    //使用truncate
    truncate table tableName;
    

    truncate与delete的比较:
    1.truncate属于DDL,其操作会导致隐式提交,不能回滚
    2.truncate不是逐条删除,而是会保留表的结构,重新创建表(所以对于自增约束的字段会重新从1开始自增)
    3.truncate操作成功不会返回已删除的行数,只是返回成功还是失败

  4. 查询数据
    //查询表中所有数据
    select * from tableName;
    

1.5 完整性约束

  1. 分类一:
    1. 表级约束:约束表中任意一个或多个字段;可以在创建表的时候添加,也可以在表创建完毕后添加
    2. 列级约束:只能约束某个字段;可以在创建表的时候添加,也可以在表创建完毕后通过修改列的形式添加;修改列级约束并不会影响表级约束
  2. 分类二:
    1. 主键约束
    2. 非空约束
    3. 唯一约束
    4. 检查约束
    5. 外键约束
  3. MySQL的约束:
    1. 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;
      
    2. 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;
      
    3. 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;
      
    4. DEFAULT:默认值约束(列级约束) - 不指定值时为默认值(注意:NULL也是指定值)
      //建表时添加
      create table tableName(
      	gender varchar(4) default "男"
      );
      
      //建表后添加
      alter table tableName 
      modify gender varchar(4) 
      default "男";
      
      //删除
      alter table tableName 
      modify gender varchar(4);
      
    5. AUTO_INCREMENT:自动增加约束(列级约束) - 只用于主键,添加数据时使用NULL填充即可自动递增
      //建表时添加
      create table tableName(
      	id int auto_increment;
      );
      
      //建表后添加
      alter table tableName 
      modify id int auto_increment;
      
      //删除
      alter table tableName 
      modify id int;
      
    6. 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("男","女");
      
    7. 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
      

1.6 where单表查询语句

  1. 等值查询
    select * from student where name="大土";
    
  2. 不等值查询
    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);
    
  3. 模糊查询
    //查询姓名以赵开头的学生
    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 "%\\\\%";
    
  4. 判断空值
    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 单行函数

单行函数是输入一个参数或内部扫描一条数据,返回一个结果

  1. 字符串函数
    在这里插入图片描述

    //查询学生姓名长度为4的学生
    select id,name from student where length(name) = 6;
    
  2. 数值函数
    在这里插入图片描述

    //返回绝对值
    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;//非四舍五入
    
  3. 日期函数
    在这里插入图片描述

    //返回当前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();
    

在这里插入图片描述

  1. 流程函数
    在这里插入图片描述

    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);
    
  2. JSON函数
    在这里插入图片描述

  3. 其他函数
    在这里插入图片描述

    //返回当前数据库名称、当前用户、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 多表查询

  1. 多表查询原理
    //两表查询生成笛卡尔积 - 结果显示是两表的乘积
    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;
    
  2. 内连接查询
    select * from student s inner join class c on s.class=c.id; 
    

    注意:内连接只会查询除多张表中根据某个字段匹配符合的记录,不符合的记录不会显示

  3. 外连接查询
    //左外关联-左表为主表,左表的数据全部显示
    select * from student left outer join class on student.class=class.id;
    
    //右外关联-右表为主表,右表的信息全部显示
    select * from student right outer join class on student.class=class.id;
    

    注意:外连接查询即能查询出符合条件的记录,也能根据一个表将另一个表查询出来

  4. 自连接查询
    select * from student s1 inner join student s2 on s1.id=s2.groupLeardId
    
  5. 子查询
    //不相关子查询
    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不支持事务

  1. 事务并发问题
    • 脏读:一个事务读取到了另一个事务修改后并未提交的数据(数据未提交,所以可能回滚导致脏数据)
      在这里插入图片描述
    • 幻读:一个事务修改并提交了另一个事务正在重复读取的数据(一个事务重复读取同一数据出现数据不一致的问题)
      在这里插入图片描述
    • 不可重复读:一个事务修改并提交了另一个事务正在重复读取的数据(一个事务重复读取同一数据出现数据不一致的问题)
      在这里插入图片描述

    幻读与不可重复读的区别:幻读重点在新增或删除(需要锁住表);不可重复读重点在修改(需要锁住行)

  2. 事务的隔离级别
    //查看当前事务隔离级别
    select @@tx_isolation;//默认是REPEATABLE-READ
    
    //修改
    set session transaction isolation level read uncommitted;
    

在这里插入图片描述

1.16 索引

索引是提高查询效率的一种手段,一旦创建,完全由数据库自行维护
MySQL会自动给主键约束、唯一约束和外键约束的字段添加索引

  1. 类型
    • 单列索引和多列索引
    • 唯一索引和非唯一索引
    • 存储结构:B-Tree(默认)、R-Tree、Hash
  2. 使用
    //查看所有
    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. 安装

  1. 下载安装包直接安装
  2. 配置环境变量

3. 使用

  1. 使用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.优化三:注入攻击与预编译连接对象

  1. 注入攻击
    //JDBC中,sql如果采用上面的拼接方法,可能会出现输入密码aaa'or'1'='1登录成功,因为sql语句已经变成了如下
    select * from user where username='随便' and password='aaa' or '1'='1'
    
  2. 使用预编译连接对象避免注入攻击
    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.事务控制

  1. 没有事务控制
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);
	}
}
  1. 设置事务控制
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反射机制是指在运行状态中,对于任意一个对象,都能够调用其方法和属性

  1. 获取类的字节码对象
    //通过类的全限定名获取
    Class test=Class.forName("cn.khue.test.Test");
    
    //通过类名获取
    Class test=Test.class;
    
    //通过对象名获取
    Test test=new Test();
    Class testClass=test.getClass();
    
    //返回类的全限定名
    test.getName();
    //返回类名
    test.getSimpleName();
    
  2. 获取类的属性
    //获取类的字节码对象
    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,"赵大土");
    
  3. 获取方法
    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,"赵大土");
    
  4. 获取构造器
    Class test=Class.forName("cn.khue.test.Test");
    //有参构造器
    Constructor constructor=test.getDeclaredConstaructor(String.class);
    
    //返回构造器名称
    constructor.getName();
    //返回参数个数
    constructor.getParameterCount();
    //返回参数类型
    constructor.getParameterTypes();
    
  5. 使用反射
    //获取字节码
    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元素进行说明,可以起到文档生成、代码分析与编译检查的作用

  1. 自带注解
    //@Override - 重写规范检验
    
    //@Decpreacted - 方法名增加中划线,提示该方法已过时
    
    //@SupressWarning - 禁止显示警告信息
    
  2. 元注解:用于注解其他注解
    //@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 - 允许子类继承父类中的注解
    
  3. 自定义注解
    //创建一个自定义注解
    @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配置文件

  1. MySQL的配置文件
    driver=com.msyql.jdbc.Driver
    url=jdbc:mysql://127.0.0.1/test?useSSL=false
    username=root
    password=root
    
  2. 非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");
    	}
    }
    
  3. 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.实现日志的方式

  1. System.out.println():不能持久化
  2. 使用IO+System.out.println():不便定制
  3. 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的使用

  1. 导入jar包:log4j-1.2.8.jar
  2. 添加属性文件:/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
    
  3. 程序调用
    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 - 数据库

  1. 基于传统DAO层开发(无接口)
  2. 基于代理模式开发(接口)

2.基于DAO层开发的一般流程

  1. 导入jar包
    Mybatis核心包:mybatis-3.2.7.jar
    日志文件包:log4j-1.2.17.jar
    MySQL驱动包:mysql-connector-java-5.1.38.jar
  2. 准备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>
    
  3. 准备log4j配置文件
    # 日志打印级别及打印方式(控制台还是持久化)
    log4j.rootLogger=DEBUG,CONSOLE
    
    # 控制台显示
    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.layout=org.apache.log4j.SimpleLayout
    
  4. 准备实体类
    public class User implements Serializable{
    	private int id;
    	private String name;
    	private String password;
    	...
    }
    

    注意:实体类必须要有无参构造器,mybatis通过反射构建参数时,会调用class.newInstance方法

  5. 准备映射文件
    <?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>
    
  6. 测试
    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需要写实体类的全限定路径,不免过于繁琐,可以使用别名

  1. 修改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>
    
  2. 修改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标签简化数据库配置信息

  1. 新建mysql-config.properties
    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false
    username=root
    password=root
    
  2. 修改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文件的三种传参方式

  1. 单个基本类型+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>
    
  2. 对象传参: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>
    
  3. 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文件(多参数传递问题、方法调用不规范)

  1. 创建mapper接口
    public interface UserMapper{
    	//查询所有数据
    	List<User> queryAll();
    }
    
  2. 创建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>
    
  3. 修改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>
    
  4. 测试
    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.基于代理模式开发的参数传递

  1. 单个基本数据类型+String:名称任意写
  2. 多个基本数据类型+String:名称为0、1…(使用数组封装)或param1、param2…(使用map封装)或使用别名
  3. 单个引用类型:属性名
  4. 多个引用类型:别名.属性名
	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.缓存技术

  1. 一级缓存(默认开启):同一个SqlSession中第一次执行时会将查询的数据写到缓存(HashMap结构,key为hashcode+sqlID+sql语句,value为查询出来的映射对象),第二次执行相同 的sql语句时会直接从缓存中获取,从而提高查询效率
    //会清理缓存
    sqlSession.commit();
    
    //手动清理缓存
    sqlSession.clearCache();
    
  2. 二级缓存:同一接口下的多个SqlSession共享缓存数据
    开启方式:
    1.在核心配置文件中开启全局二级缓存
    <?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>
    
    2.在需要开启二级缓存的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">
    <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.简单实现

  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>
    
  2. 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>
    
  3. 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的生命周期

  1. 实例化-构造方法(服务器启动-执行一次)
  2. 初始化-init方法(服务器启动执-行一次)
  3. 服务-service方法(每次请求)
  4. 销毁-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

  1. get请求:
    • 数据通过url提交,相对不安全
    • 提交的数据量小
    • 只能提交文本数据
  2. 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.项目结构
在这里插入图片描述

  1. 核心思路:
    web.xml中配置登录页的servlet(servlet中登录成功后在session存入用户信息,并重定向到一个地址)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    web.xml中配置登录成功访问地址的servlet(servlet中获取session验证用户信息,成功则转发到主页)
    在这里插入图片描述
    在这里插入图片描述
    web.xml中配置session存活时间
    在这里插入图片描述
    主页通过js验证session
    在这里插入图片描述

  2. 源码

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.常用的指令标签

  1. page:导包、指定编码、指定脚本、指定错误页面
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page import="cn.khue.test.Test" %>
    <%@ page errorPage="error500.jsp" %>
    <!-- 指定当前页为错误页,当前页可接收异常对象 -->
    <%@ page isError="true" %>
    
    在web.xml中配置错误页
    <error-page>
    	<error-code>500</error-code>
    	<location>error500.jsp</location>
    </error-page>
    
  2. Include:静态导入页面
  3. 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)

  1. HttpServletRequest requst:域对象-单次请求
  2. PageContext pageContext:域对象-当前页面
  3. HttpSession session:域对象-单次会话
  4. ServletContext application:域对象-整个项目
  5. HttpServletResponse response
  6. JspWriter out
  7. ServletConfig config
  8. Object page=this
  9. 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文件中)

  1. 核心标签库core
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  1. 格式化标签库format
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
  1. 函数标签库function
  2. SQL标签库
  3. XML标签库

GSON

Gson可以快速将Java对象和json字符串进行相互转换

  1. 导入gson-2.2.4.jar
  2. 后端使用
    User user=new User(1,"khue",25);
    Gson gson=new Gson();
    //将单个对象转换成json字符串
    String userJson=gson.toJson(user);
    
  3. 前端使用
    <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实现分页

  1. 后端核心:
    entity层
    public class PageResult{
    	//分页后的数据
    	private List<User> list;
    	//总的数据量
    	private int totalSize;
    	//总的页码
    	private int totalPages;
    	...
    }
    
    mapper层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">
    <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>
    
    mapper层注解版
    //分页查询语句
    @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);
    
    serviceimpl层
    @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);
    	
    }
    
  2. 前端回显
    <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.文件上传的核心问题

  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>
    
  2. 后端如何接收并区分?
    由于前端提交的并非编码之后的键值对形式的数据,所以需要借助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{
    		//文件数据
    	}
    }
    
  3. 文件类型及大小如何限制?
    //类型限制 - 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{
    	//不符合
    }
    
  4. 文件重名如何解决?
    //真实存储在服务器的文件必须重命名,且名称唯一
    //之前的名字使用记录表记录关联即可
    String oldNmae=item.getName();
    String suffix=oldNmae.subString(oldName.lastIndexOf("."));
    String newName=UUID.randomUUID()+suffix;
    
  5. 文件存储路径如何解耦?
    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));
    
  6. 文件删除后如何恢复?
    文件一般不会直接物理删除,而是更改文件记录表的isDel状态(文件记录表的一般字段:id、userID、path、name、uploadTime、desc、isDel)

2.文件下载的核心问题

  1. 文件如何下载?
    res.setContentType(fileType);
    
    FileInputStream fis=new FileInputStream(filePath);
    ServletOutputStream sos=res.getOutputStream();
    IOUtils.copy(fis,sos);
    
    sos.close();
    fis.close();
    
  2. 页面如何实现另存为?
    上面实现的文件下载会直接跳转到一个新页面,该页面地址为文件的存储地址,下面代码实现了点击下载,跳出另存为提示框
    res.setContentType(fileType);
    res.setHeader("Content-Disposition", "attachment;filename="+fileName);
    

过滤器

在Servlet执行之前用来过滤请求的类,可以接收req对象对请求进行判断、拦截、放行;可以接收resp对象进行响应;

实现:

  1. 实现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(){}
    }
    
  2. 配置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),符合监听要求即执行监听方法

实现:

  1. 实现对应接口并重写方法
    //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){}
    }
    
  2. 配置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>
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值