重学java第二阶段(下)

day11

今日内容

0 复习昨日
1 JDBC概述
2 JDBC开发步骤
3 完成增删改操作
4 ResultSet
5 登录案例

0 复习昨日

1 写出JQuery,通过获得id获得dom,并给input输入框赋值的语句
$(“#id”).val(“值”)

2 mysql内连接和外连接的区别
内连接只会保留完全符合关联条件的数据
外连接会保留表(左外保留左表)中不符合关联条件的数据

3 事务是什么?
事务是逻辑一组操作,要么全部成功,要么全部失败

4 索引有什么好处和坏处
索引可以提高查询效率

如果表是经常性的需要增删改,有索引在就会非常慢

1 JDBC概述

目前我们操作数据库,只能通过命令行(cmd)或者图形工具Navicat来操作数据库.

但是实际开发时配合页面数据对数据操作,如果还是使用命令行(cmd)或者图形工具Navicat来操作就很麻烦!

JDBC就是另外一种操作数据库的方式.(Java操作数据库)

JDBC: Java DataBase Connectivity Java 数据库连接

JDBC的设计思想

image-20221121101745669

image-20221121101805975

Mysql厂商提供了驱动包,如下(jar包)(jar包就是把java项目压缩打包)

image-20221121102009316

驱动包,就是MYSQL厂商提供一套JDBC规范的实现.


每个知识点小问?

1
2
3
4
什么是JDBC ?
设计思想 ?
什么是jar包 ?
mysql驱动包是什么 ?

2 JDBC开发步骤

2.1 创建java项目

2.2 导入mysql驱动包

mysql厂商提供的jdbc规范的实现,要想完成JDBC操作,就需要将驱动包加入到当前项目中.

2.2.1 复制粘贴版本

  • 在项目名下创建文件夹lib
image-20221121105646545
  • 命名为lib
image-20221121105703497
  • 将mysql驱动包复制粘贴到此处

    image-20221121105753513
  • 添加驱动包为当前项目的类库

    image-20221121110032950

2.2.2 idea导入类库版本

  • 打开项目结构(Project Structure)
image-20221121111145900
  • 选择libraries,添加jar包

    image-20221121111310058
image-20221121111402075
  • 应用生效

    image-20221121111442727
  • 成功

    image-20221121111616446

2.3 JDBC编程

准备数据库表,进行CRUD.

1
2
3
4
5
6
7
8
9
create table tb_user(
id int(11) primary key auto_increment comment '用户编号',
username varchar(10) comment '用户名',
password varchar(10) comment '密码',
phone varchar(11) comment '手机号',
createTime date comment '注册时间',
money double(10,2) comment '账户余额',
sex int(1) comment '性别 1男2女'
);

需求: 使用JDBC完成对tb_user表插入数据


JDBC编程有标准步骤(八股文)

  • 注册驱动
    • 将sql语句的运行环境加载到JVM
  • 连接数据库
  • 获得执行SQL的对象
  • 执行SQL语句,获得结果
  • 关流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.qf.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 演示JDBC
*/
public class Demo1_insert {

public static void main(String[] args) throws ClassNotFoundException, SQLException {

// 1 加载驱动
// ps: 利用反射技术,将驱动类加载到JVM
Class.forName("com.mysql.jdbc.Driver");

// 2 通过驱动管理对象获得连接对象
/**
* 参数1 url: 数据库连接的地址
* 协议://ip:端口/库名
* 参数2 username: 数据库用户名
* 参数3 password: 数据库密码
*/
String url = "jdbc:mysql://localhost:3306/java2217?useSSL=false&serverTimezone=UTC";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url,username,password);

// 3 通过连接对象,创建执行sql语句的对象
Statement statement = conn.createStatement();

// 4 通过执行语句对象,执行sql,获得结果
String sql = "insert into tb_user (id,username,password,phone,createTime,money,sex) values (2,'root','123456','1122200','2022-11-21',2000.0,2)";
// 执行查询,是executeQuery()
// 执行增删改,是executeUpdate(),返回受影响的行数
int num = statement.executeUpdate(sql);

if (num > 0) {
System.out.println("插入成功!!" );
}

// 5 关流
statement.close();
conn.close();

}
}

小总结:

  • 记住5个步骤的关联和顺序,会读代码

  • 理解url的写法

    • 协议
    • ip
    • 端口
    • 参数
  • 其中涉及的单词要认识

    Driver,Connection,Manager,url,Statement, execute

3 完成增删改

3.1 插入

参考入门案例

3.2 更新

任何的JDBC都是那5个步骤.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.qf.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 更新
*/
public class Demo2_update {

public static void main(String[] args) throws Exception {

// 1 加载驱动
Class.forName("com.mysql.jdbc.Driver");

// 2 通过驱动管理对象获得连接对象
String url = "jdbc:mysql://localhost:3306/java2217?useSSL=false";
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);

// 3 通过连接对象创建执行语句对象
Statement statement = conn.createStatement( );

// 4 通过执行语句对象执行sql,获得结果
String sql = "update tb_user set username = '小孟', phone = '666666' where id = 3";
int num = statement.executeUpdate(sql);

if (num > 0) {
System.out.println("更新成功!" );
}

// 5 将对象的流关闭
statement.close();
conn.close();
}
}

ps: 一定自己主动试错,看报错信息

3.3 删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo3_delete {


public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

Statement statement = conn.createStatement( );

int num = statement.executeUpdate("delete from tb_user where id = 3");

if (num > 0) {
System.out.println("删除成功!");
}

statement.close();
conn.close();
}
}

4 查询结果集ResultSet【重要】

查询返回的是一种虚拟表,Java的JDBC中是使用结果集(ResultSet)来封装这个虚拟表,结果集就是一个集合,内部就存储了列名和每行数据,那么学习查询的重点是

  • 从结果集取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.qf.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 查询
*/
public class Demo4_select {

public static void main(String[] args) throws Exception {

Class.forName("com.mysql.jdbc.Driver");

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

Statement statement = conn.createStatement( );

String sql = "select id,username,password from tb_user";
// 执行查询的方法executeQuery,方法返回值是ResultSet
ResultSet rs = statement.executeQuery(sql);
/**
* ResultSet 内部包含了整个查询返回的虚拟表数据
* 内部提供了方法可以操作结果集
* boolean next(); 判断结果集有没有下一行数据,返回false,即没有下一行数据返回
* true就是有下一行数据,此时就可以进入取值
* getObject() 获得数据,返回值是Object
* getInt/getString/getDate() 获得数据,返回对应数据类型
* --------------------------------------
* getXxx(int columnIndex) 通过列下标获得对应Xxx数据类型的数据
* 下标从1开始,顺序是按照查询返回虚拟表顺序
* getXxx(String columnLabel) 通过列名获得对应Xxx数据类型的数据
* 根据虚拟表列名,如果有别名那就是别名
*/
while (rs.next()) {
// 通过列下标获得数据
// int id = rs.getInt(2);
// String username = rs.getString(1);

// 通过列名获得数据 【推荐】
int id = rs.getInt("id");
String username = rs.getString("username");
System.out.println(id + "-" + username);
}

statement.close();
conn.close();
}

}

image-20221121164359945


每个知识点小问?

1
2
3
4
5
ResultSet是什么?
next()有什么特点?
if(next())和while(next())有啥区别 ?
如果按下标取值,下标从哪开始,下标顺序是什么顺序?
如果按列名取值,列名根据谁来定?

5 登录案例【重要】

需求:

  • 通过控制台用户输入用户名和密码。
  • 用户输入的用户名和密码作为条件,编写查询 SQL 语句。
    • select * from user where usename = xxx and password = xxx
  • 如果该用户存在,提示登录成功,反之提示失败。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.qf.jdbc;

import com.mysql.jdbc.Driver;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class Demo5_Login {

public static void main(String[] args) {

// 1 输入用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:" );
String username = scanner.nextLine( );

System.out.println("请输入密码:" );
String password = scanner.nextLine( );

// 2 根据用户名和密码查人
boolean isOk = findUserByLogin(username,password);

// 3 结果
if (isOk) {
System.out.println("登录成功!" );
} else {
System.out.println("用户名或密码错误!" );
}
}

// 使用捕获代码完成
private static boolean findUserByLogin(String username, String password) {
Connection conn = null;
Statement statement = null;
boolean isOk = false;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

statement = conn.createStatement( );

// 根据用户名和密码查询,注意字符串拼接.特别注意单引号
ResultSet rs = statement.executeQuery("select * from tb_user where username = '"+username+"' and password = '"+password+"'");

// 只要有值,就说明数据库有这个信息,登录成功
if (rs.next()) {
// System.out.println("登录成功!" );
int id = rs.getInt("id");
String uname= rs.getString("username");
// ...

System.out.println(id+"-"+username);
isOk = true;
} else {
// System.out.println("用户名或密码错误!!" );
}
}catch (Exception e) {
System.out.println("SQL操作出错!" );
e.printStackTrace();// 打印异常
} finally {
try{
statement.close();
conn.close();
}catch (Exception e) {
System.out.println("关流异常" );
e.printStackTrace();// 打印异常
}
}
return isOk;
}
}

day12

今日内容

0 复习昨日
1 SQL注入问题
2 PreparedStatement
3 完成CRUD练习
4 ORM
5 DBUtil (properties)

6 事务操作

0 复习昨日

已经找人提问…

1 SQL注入

1.1 什么是SQL注入

用户输入的数据中有SQL关键词,导致在执行SQL语句时出现一些不正常的情况.这就是SQL注入!


出现SQL注入是很危险

image-20221122094710845

1.2 避免SQL注入

问题出现在用户输入数据时,里面有关键词,再配合字符串拼接导致出现SQL注入.所以为了避免SQL注入,可以在用户输入数据到SQL之前,先把SQL语句预编译,预处理后,JDBC就会知道此SQL需要几个参数,后续再将用户输入的数据给参数填充.

这就是PreparedStatement

2 PreparedStatement【重点】

PreparedStatement是Statement的子接口,用来预处理SQL语句

PreparedStatement使用

  • 先写SQL语句,SQL语句中的参数不能直接拼接,而是使用?占位
  • 使用ps预处理SQL语句,处理的?号,ps内部就会知道此SQL语句需要几个参数
  • 再动态给?处填充值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.qf.jdbc;

import java.sql.*;
import java.util.Scanner;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 登录-使用预处理语句完成
*/
public class Demo2_LoginPlus {

public static void main(String[] args) throws Exception {

Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:" );
String username = scanner.nextLine( );

System.out.println("请输入密码:" );
String password = scanner.nextLine( );

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

// 改造SQL,将拼接变量,变成?占位
String sql = "select * from tb_user where username = ? and password = ?";
System.out.println("处理前: " + sql);

// 由之前的Statement换成PreparedStatement
// 将改造好的SQL,传入方法
PreparedStatement ps = conn.prepareStatement(sql);
System.out.println("处理后: " + ps );

// 给处理好的占位参数赋值
// ps.setXxx() 给指定Xxx类型赋值
// 第一个?,下标是1
ps.setString(1,username);
ps.setString(2,password);

System.out.println("填充后: " + ps );

//【特别注意!!!!】 此处executeQuery不需要再传入SQL参数!!!
ResultSet rs = ps.executeQuery();

if (rs.next()) {
System.out.println("登录成功!!" );
} else {
System.out.println("用户名或密码错误!" );
}

rs.close();
ps.close();
conn.close();
}
}
1
2
3
4
5
6
7
8
请输入用户名:
111
请输入密码:
111' or '1=1
处理前: select * from tb_user where username = ? and password = ?
处理后: select * from tb_user where username = ** NOT SPECIFIED ** and password = ** NOT SPECIFIED **
填充后: select * from tb_user where username = '111' and password = '111\' or \'1=1'
用户名或密码错误!

3 完成CRUD练习

3.1 插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.qf.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Date;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 使用预处理语句插入数据
*/
public class Demo3_insert {

public static void main(String[] args) throws Exception {

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

// 1 先改造SQL(由拼接变成?)
String sql = "insert into tb_user (id,username,password,phone,createTime,money,sex) values (?,?,?,?,?,?,?)";

// 2 预处理
PreparedStatement ps = conn.prepareStatement(sql);

// 3 给?处赋值
ps.setInt(1,5);
ps.setString(2,"亚鹏");
ps.setString(3,"333000");
ps.setString(4,"333333");
// 【特别注意!!!】 setDate的参数,设置是java.sql.Date
// 我们常用的是java.util.Date
// java.sql.Date是java.util.Date的子类
// java.sql.Date有个构造方法,可以通过毫秒值创建日期
// 通过常用的java.util.Date获得毫秒值
ps.setDate(5,new java.sql.Date(new java.util.Date().getTime()));

// 如果需要设置日期时间 yyyy-MM-dd HH:mm:ss
// 使用ps.setTimestamp();

ps.setDouble(6,3000.1);
ps.setInt(7,1);
// 【特别注意!!!】 此处不要再填参数
int num = ps.executeUpdate( );

if (num > 0) {
System.out.println("插入成功" );
}
ps.close();
conn.close();
}
}

3.2 更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.qf.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Date;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class Demo4_update {

public static void main(String[] args) throws Exception{

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

// 1 改造sql
String sql = "update tb_user set username = ? , createTime = ? , money = ? where id = ?";
// 2 预处理
PreparedStatement ps = conn.prepareStatement(sql);
// 3 填充?
ps.setString(1,"小谢");

Date utilDate = new Date( );
utilDate.setYear(100);
utilDate.setMonth(0);
utilDate.setDate(1);
long time = utilDate.getTime( );
ps.setDate(2,new java.sql.Date(time));

ps.setDouble(3,4000.1);
ps.setInt(4,5);

int num = ps.executeUpdate( );

if (num > 0) {
System.out.println("更新成功" );
}

ps.close();
conn.close();
}
}

3.3 删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) throws  Exception{

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

// 1 改造sql
String sql = "delete from tb_user where id = ?";
// 2 预处理
PreparedStatement ps = conn.prepareStatement(sql);
// 3 填充?
ps.setInt(1,5);

int num = ps.executeUpdate( );

if (num > 0) {
System.out.println("删除成功" );
}

ps.close();
conn.close();
}

4 事务处理

事务是逻辑一组操作,要么全部成功,要么全部失败!


使用mysql客户端操作事务

  • 因为mysql支持事务,且每句话都在事务内,且自动提交
  • 所以关闭自动提交事务,手动开启事务 start transaction
  • 正常写sql/执行sql
  • 一切正常,提交事务 commit
  • 如果不正常,要回滚 rollback

JDBC也可以完成事务操作

  • conn.setAutoCommit(false) 关闭自动提交,就相当于是开启手动管理
  • 正常的处理sql
  • 一切正常,提交事务 conn.commit()
  • 如果不正常,回滚 conn.rollback()

演示: 以转账案例演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.qf.tx;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 以转账为案例演示
* --------------------
* Statement语句和PreparedStatement语句
* 与事务操作没有影响
*/
public class Demo6_TX {

// 张三转账给李四
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps1 = null;
PreparedStatement ps2 = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

// 【1 开启事务】
conn.setAutoCommit(false);

// 张三的钱减少100
String sql1 = "update account set money = money - 100 where id = 1";
ps1 = conn.prepareStatement(sql1);
int num = ps1.executeUpdate( );
if (num > 0) {
System.out.println("张三转账(-100)完成!");
}

System.out.println(1/0 ); // 模拟在转账中,出现异常,后续不执行

// 李四的钱要增加100
String sql2 = "update account set money = money + 100 where id = 2";
ps2 = conn.prepareStatement(sql2);
int num2 = ps2.executeUpdate( );
if (num2 > 0) {
System.out.println("李四转账(+100)完成!");
}

// 【2 一切顺利,提交事务】
conn.commit();

} catch (Exception e) {
try{
// 【3 不顺利,中间有异常,回滚事务】
conn.rollback();
}catch (Exception e2) {
System.out.println("回滚事务异常!!" );
e2.printStackTrace();
}
System.out.println("SQL异常!!!");
e.printStackTrace( );
} finally {
try {
ps1.close( );
ps2.close( );
conn.close( );
} catch (Exception e) {
System.out.println("关流时有异常!!");
e.printStackTrace( );
}
}
}
}

另外发现: 建立与Mysql连接后,关流之前,可以执行很多次SQL语句

5 ORM【重点】

5.1 什么是ORM

目前使用JDBC完成了CRUD,但是现在是进行CRUD,增删改方法要设计很多参数,查询的方法需要设计集合才能返回.


在实际开发中,我们需要将零散的数据封装到对象处理.

ORM (Object Relational Mapping) 对象关系映射

是指数据库表Java的实体类有关系,可以进行映射

  • 数据库表 –> Java的类
    • tb_user —> User.java
  • 字段 –> 类的属性
    • id int –> private int id;
    • username varchar –> private String username;
  • 一行数据 –> 类的对象

5.2 实体类

实体类: 数据表中零散数据的载体,用来封装数据.

  • 表名 设计 类名
  • 将列名设计成属性名
    • id –> id
    • create_time –> createTime (下划线转驼峰)
  • 将列的数据类型设计成属性的数据类型
  • 给类提供对应set get

一般项目中一个表就会对应一个实体类,所有的实体类都会放在model/entity/pojo/javabeen包结构中


将来写项目,数据库设计完,搭建完项目,第一件事件就是根据表结构,创建实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.qf.model; // 包
public class User { // 实体类,是表名
// 属性是字段名
private int id;
private String username;
private String password;
private String phone;
private Date createTime;
private double money;
private int sex;

// setter getter...
}

image-20221122160619448

5.3 使用ORM完成CRUD

5.3.1 查询使用ORM封装数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.qf.orm;

import com.qf.model.User;

import java.sql.*;
import java.util.Scanner;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 登录完成,查询到数据,将数据封装到User对象
* ---------------------------------------------
* 使用ORM封装数据,将查询得到结果集封装到User对象中
* 即登录成功 User对象中有值
* 登录不成功 User对象不存在
*/
public class Demo7_Login_ORM {


public static void main(String[] args) throws Exception{

Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:" );
String username = scanner.nextLine( );

System.out.println("请输入密码:" );
String password = scanner.nextLine( );

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

String sql = "select * from tb_user where username = ? and password = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,username);
ps.setString(2,password);

ResultSet rs = ps.executeQuery( );

// 声明用于封装数据表数据的实体类
User user = null;

if (rs.next()) {
// 从结果集取值
int id = rs.getInt("id");
String uname = rs.getString("username");
String pwd = rs.getString("password");
String phone = rs.getString("phone");
Date createTime = rs.getDate("createTime");
double money = rs.getDouble("money");
int sex = rs.getInt("sex");

// 创建封装数据用的实体类对象
user = new User();
// 开始封装
user.setId(id);
user.setUsername(uname);
user.setPassword(pwd);
user.setPhone(phone);
user.setCreateTime(createTime); // java.sql.Date是java.util.Date的子类
user.setMoney(money);
user.setSex(sex);
}

if (user != null) {
System.out.println("登录成功!" );
System.out.println("个人信息为:" + user );
} else {
System.out.println("登录失败!" );
}
}
}
5.3.2 插入使用ORM封装数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.qf.orm;

import com.qf.model.User;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Date;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class Demo8_Insert_ORM {

public static void main(String[] args) {
User user = new User( );
user.setId(6);
user.setUsername("李冰");
user.setPassword("123456" );
user.setCreateTime(new Date());
user.setMoney(4000.1);
user.setSex(2);
user.setPhone("222333");

insert(user);
}

public static void insert(User user) {
Connection conn = null;
PreparedStatement ps = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java2217?useSSL=false", "root", "123456");

String sql = "insert into tb_user (id,username,password,phone,createTime,money,sex) values (?,?,?,?,?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1,user.getId());
ps.setString(2,user.getUsername());
ps.setString(3,user.getPassword());
ps.setString(4,user.getPhone());
ps.setDate(5,new java.sql.Date(user.getCreateTime().getTime()));
ps.setDouble(6,user.getMoney());
ps.setInt(7,user.getSex());

int num = ps.executeUpdate( );
if (num > 0) {
System.out.println("注册成功!" );
}
} catch (Exception e) {
System.out.println("SQL异常!!!" );
e.printStackTrace();
} finally {
try{
ps.close();
conn.close();
}catch (Exception e) {
System.out.println("关流异常!!" );
e.printStackTrace();
}
}
}
}

6 DBUtil【理解,会用】

DBUtil操作数据库的工具类,因为发现每次操作数据库,JDBC的步骤第1,2,5步完全重复的,即加载驱动,获得连接对象,已经最后的关流是每次都要写但每次都是一样的!!!!


现在设计工具类,简化第1,2,5步

  • 设计个方法,调用直接获得连接对象
  • 设计个方法,调用直接关闭全部的流对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.qf.util;

import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class DBUtil {

// 创建Properties类对象,专用于操作properties文件
private static final Properties properties = new Properties();

/**
* 加载驱动的目的是为了在JVM中有sql运行的环境
* 该环境有一份就行了,不用重复加载
* ------------------------------------
* static 静态代码块
* 1) 保证内存中只有一份
* 2) 保证随着类加载而加载,即该代码块会执行
*/
static {

// 通过反射的技术获得字节码文件
// 再通过字节码文件将配置文件读取成输入流
InputStream inputStream = DBUtil.class.getResourceAsStream("/jdbc.properties");
try {
// 再通过流获得其中数据
properties.load(inputStream);
// 从properties对象取值
Class.forName(properties.getProperty("driverClass"));
} catch (Exception e) {
System.out.println("加载驱动异常!!" );
e.printStackTrace( );
}
}

/**
* 一般会将关于JDBC配置信息,抽取出来,形成一个配置文件,方便维护
* 文件类型是properties文件,该文件类似map,键值对类型
* 名字 jdbc.properties
* 位置 src/jdbc.properties
* 内容
*/
public static Connection getConnection() {
Connection conn = null;
try{
conn = DriverManager.getConnection(properties.getProperty("url"),properties.getProperty("username") ,properties.getProperty("password") );
} catch (Exception e) {
System.out.println("获得连接出异常!!!" );
e.printStackTrace();
}
return conn;
}


/**
* 关闭所有流
*/
public static void closeAll(Connection conn, Statement s) {
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}

if (s != null) {
try {
s.close();
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}
}

public static void closeAll(Connection conn, Statement s, ResultSet rs){
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}

if (s != null) {
try {
s.close();
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}

if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}
}
}

在src下创建jdbc.properties文件

1
2
3
4
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/java2217?useSSL=false
username=root
password=123456

day13

今日内容

0 复习昨日
1 讲作业
2 数据库连接池(druid)
3 反射
4 改造DBUtil
5 完成CRUD练习

0 复习昨日

1 sql注入

2 预处理语句

3 事务操作

4 DBUtil

1 作业【重要】

利用ORM完成,以下的几个方法非常重要,将来写项目就是这些操作


写项目步骤

  • 搭建环境
    • 创建项目
    • 导入依赖
    • 工具类
    • 数据库对应实体类
  • 开发数据库层的代码
    • 数据库层代码,一般称为dao (data access object)
    • 操作的是User表,所以类UserDao
1
2
3
4
5
6
7
8
9
10
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',
`username` varchar(10) DEFAULT NULL COMMENT '用户名',
`password` varchar(10) DEFAULT NULL COMMENT '密码',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
`createTime` date DEFAULT NULL COMMENT '注册时间',
`money` double(10,2) DEFAULT NULL COMMENT '账户余额',
`sex` int(1) DEFAULT NULL COMMENT '性别 1男2女',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1 根据id从数据库查询出用户信息
public User findUserById(int id)

2 向数据库插入一个用户,返回是否插入成功
public boolean addUser(User user)

3 通过id删除用户数据,返回boolean
public boolean deleteById(int id)
4 设计方法,查询全部数据,返回值是List集合,集合中是全部用户数据
publicList<User> findAll()
5 通过id更改用户数据
public boolean updateById(User user)
注意:根据id更新,即参数User对象中一定有id属性值
更新user表的数据,根据User对象的属性更新,如果属性值是null则不跟更新,不为null就更新对应的字段
返回值是受影响的行数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package com.qf.dao;

import com.qf.model.User;
import com.qf.util.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class UserDao {

/**
* 根据id查用户
*
* @param id
* @return
*/
public User findUserById(int id) {
Connection conn = DBUtil.getConnection( );
PreparedStatement ps = null;
ResultSet rs = null;
User user = null;
try {
ps = conn.prepareStatement("select * from tb_user where id = ?");
ps.setInt(1, id);

rs = ps.executeQuery( );

if (rs.next( )) {
user = new User( );
user.setId(rs.getInt("id"));
user.setPhone(rs.getString("phone"));
user.setUsername(rs.getString("username"));
user.setSex(rs.getInt("sex"));
user.setMoney(rs.getDouble("money"));
user.setPassword(rs.getString("password"));
user.setCreateTime(rs.getDate("createTime"));
}
} catch (Exception e) {
e.printStackTrace( );
} finally {
DBUtil.closeAll(conn, ps, rs);
}
return user;
}

/**
* 查询全部数据,返回值是List集合,集合中是全部用户数据
*/
public List<User> findAll() {
Connection conn = DBUtil.getConnection( );
PreparedStatement ps = null;
ResultSet rs = null;
User user = null;
ArrayList<User> list = new ArrayList<>( );

try {
ps = conn.prepareStatement("select * from tb_user");

rs = ps.executeQuery( );

while (rs.next( )) {
// 每一行数据都需要创建新对象
user = new User( );
user.setId(rs.getInt("id"));
user.setPhone(rs.getString("phone"));
user.setUsername(rs.getString("username"));
user.setSex(rs.getInt("sex"));
user.setMoney(rs.getDouble("money"));
user.setPassword(rs.getString("password"));
user.setCreateTime(rs.getDate("createTime"));

// 将每个对象装入集合
list.add(user);
}
} catch (Exception e) {
e.printStackTrace( );
} finally {
DBUtil.closeAll(conn, ps, rs);
}
return list;
}

/**
* 插入数据
*/
public boolean addUser(User user) {
Connection conn = DBUtil.getConnection( );
PreparedStatement ps = null;
int num = 0;
try {
String sql = "insert into tb_user (id,username,password,phone,createTime,money,sex) values (?,?,?,?,?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1, user.getId( ));
ps.setString(2, user.getUsername( ));
ps.setString(3, user.getPassword( ));
ps.setString(4, user.getPhone( ));
ps.setDate(5, new java.sql.Date(user.getCreateTime( ).getTime( )));
ps.setDouble(6, user.getMoney( ));
ps.setInt(7, user.getSex( ));

num = ps.executeUpdate( );
} catch (Exception e) {
e.printStackTrace( );
} finally {
DBUtil.closeAll(conn, ps);
}
return num > 0 ? true : false;
}

/**
* 删除数据
*/
public boolean deleteById(int id) {
Connection conn = DBUtil.getConnection( );
PreparedStatement ps = null;
int num = 0;
try {
String sql = "delete from tb_user where id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, id);

num = ps.executeUpdate( );
} catch (Exception e) {
e.printStackTrace( );
} finally {
DBUtil.closeAll(conn, ps);
}
return num > 0 ? true : false;
}

/**
* 根据id更新用户
* 更新user表的数据,根据User对象的属性更新,
* 如果属性值是null则不更新,不为null就更新对应的字段
* ---------------------------------------
* update tb_user set username= ?,password = ?,phone = ? ,,,,
* -------------------------------------------
* 因为值为空不为空情况不一样,那么每次更新的sql语句也不太一样,
* 解决方案
* 1) 方案一: (更新全部字段)
* 在传给updateById()的user对象中,属性全部赋值,没有null
* 属性值跟原表一样的,更新了也跟原表没变化
* 属性值跟原表不一样的,就会更新成新值
* 2) 方案二: (更新部分字段)
* 根据传给updateById()的user对象中的属性值判断
* 属性有值,就更新,属性没有值就不更新
* 即SQL语句要动态拼接
*/
// 方案一
public boolean updateById1(User user) {
Connection conn = DBUtil.getConnection( );
PreparedStatement ps = null;
int num = 0;
try {
String sql = "update tb_user set username = ?,password = ?," +
"phone = ?,createTime = ?,money = ? ," +
"sex = ? where id = ?";
ps = conn.prepareStatement(sql);

ps.setString(1, user.getUsername( ));
ps.setString(2, user.getPassword( ));
ps.setString(3, user.getPhone( ));
ps.setDate(4, new java.sql.Date(user.getCreateTime( ).getTime( )));
ps.setDouble(5, user.getMoney( ));
ps.setInt(6, user.getSex( ));
ps.setInt(7, user.getId( ));

num = ps.executeUpdate( );
} catch (Exception e) {
e.printStackTrace( );
} finally {
DBUtil.closeAll(conn, ps);
}
return num > 0 ? true : false;
}

// 方案二
public boolean updateById2(User user) {
Connection conn = DBUtil.getConnection( );
Statement statement = null;
int num = 0;
try {
String sql = "update tb_user set ";
if (user.getUsername( ) != null) {
sql += "username = '" + user.getUsername( ) + "',";
}
if (user.getPassword( ) != null) {
sql += "password = '" + user.getPassword( ) + "' ,";
}
if (user.getPhone( ) != null) {
sql += "phone = '" + user.getPhone( ) + "' ,";
}
// ...继续拼接其他字段,我在此省略
System.out.println("字段拼接完的sql " + sql);
// update tb_user set username= 'aaa',password = '111',
// 截取最后一个,
int index = sql.lastIndexOf(",");
String newSql = sql.substring(0, index);
System.out.println("截取,后的sql " + newSql);

// 最后拼接条件
newSql += " where id = " + user.getId( );
System.out.println("最终的sql " + newSql);

statement = conn.createStatement( );
num = statement.executeUpdate(newSql);

} catch (Exception e) {
e.printStackTrace( );
} finally {
DBUtil.closeAll(conn,statement);
}
return num > 0 ? true : false;
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.qf.test;

import com.qf.dao.UserDao;
import com.qf.model.User;

import java.util.Date;
import java.util.List;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class TestUserDao {

public static void main(String[] args) {
UserDao userDao = new UserDao( );
// User user = userDao.findUserById(2);
// System.out.println(user );

// List<User> list = userDao.findAll( );
// list.forEach(e -> System.out.println(e ));

// User user = new User( );
// user.setId(8);
// user.setUsername("博文");
// user.setPassword("123456");
// user.setCreateTime(new Date());
// boolean isOk = userDao.addUser(user);
// System.out.println("插入是否成功?" + isOk );


// boolean isOk = userDao.deleteById(4);
// System.out.println("删除是否成功 " + isOk );

// User user = new User( );
// user.setId(1);
// user.setUsername("aaaa");
// user.setPassword("123456");
// user.setPhone("11110000");
// user.setMoney(1000);
// user.setSex(1);
// user.setCreateTime(new Date(122,10,21));
// boolean isOk = userDao.updateById1(user);
// System.out.println(isOk );

User user = new User( );
user.setId(2);
user.setUsername("ROOT");
user.setPassword("654321");
boolean isOk = userDao.updateById2(user);
System.out.println(isOk );
}
}

小结

1
2
3
4
5
6
1 DAO是什么
2 关于数据库操作的类,应该放在什么包,什么类中
3 要将数据封装成对象(ORM)
4 学会在编码时通过输出语句确定结果是否符合预期
5 学会DEBUG
6 记住以上5个CRUD思路

2 数据库连接池

目前数据库连接是使用是建立连接,用完直接关闭连接.即需要不断创建和销毁连接.就会消耗系统资源.借鉴线程池的思想,设计出数据库连接池.

在程序初始时,预先创建好指定数量的数据库连接对象,存储连接池。需要用时就去取,用完就放回去即可。就会不会频繁创建和销毁,从而节省系统资源。

使用上的线程池有很多

  • druid (德鲁伊)
  • c3p0
  • dbcp

2.1 Druid数据库连接池

Druid是阿里开源技术,性能很好

使用步骤

  • 导入依赖druid.jar包

  • 创建一个jdbc.properties

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    driverClass=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/java2217?useSSL=false
    username=root
    password=123456
    # ----- 加入druid的一些连接配置
    #<!-- 初始化连接 -->
    initialSize=10
    #最大连接数量
    maxActive=50
    #<!-- 最小空闲连接 -->
    minIdle=5
    #<!-- 超时等待时间以毫秒为单位 60000毫秒/1000等于60秒 -->
    maxWait=5000
  • 修改之前的DBUtil

    1
    2
    3
    4
    5
    6
    7
    public class DBUtil {

    // 创建Properties类对象,专用于操作properties文件
    private static final Properties properties = new Properties();
    // 声明Druid连接池的连接池对象
    // 数据连接,一般称作数据源 dataSource
    private static DruidDataSource dataSource;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 static {

try {
InputStream inputStream = DBUtil.class.getResourceAsStream("/jdbc.properties");
properties.load(inputStream);
// 不需要由我们加载驱动
// 需要给dataSource赋值
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

} catch (Exception e) {
System.out.println("加载驱动异常!!" );
e.printStackTrace( );
}
}

public static Connection getConnection() {
Connection conn = null;
try{
// 不需要我们获得连接
// 而是通过Druid获得
conn = dataSource.getConnection();
} catch (Exception e) {
System.out.println("获得连接出异常!!!" );
e.printStackTrace();
}
return conn;
}
// 后续正常...跟之前一样

}

  • 开始使用

    • 跟之前一样使用

3 反射(reflect)

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对 象,都能够调用它的任意一个方法和属性

这种动态获取的信息以及动态调用对象的方法的功能称为java 语言的反射机制。

反射是在程序运行过程中拿到类的字节码文件,进而获得类中的属性,方法等.

3.1 获得类的字节码文件

  • Object类的方法 getClass()
  • 类的静态属性 Xxx.class
  • Class类的静态方法Class.forName(“xxx”)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 演示获取字节码文件
*/
public static void getClassFile() throws ClassNotFoundException {
// 方式1
Class<?> clazz = Class.forName("com.qf.model.User");

// 方式2
Class<User> clazz2 = User.class;

// 方式3
User user = new User( );
Class<? extends User> clazz3 = user.getClass( );

if (clazz.equals(clazz2) && clazz2.equals(clazz3)) {
System.out.println("是同一个字节码文件" );
} else {
System.out.println("不是" );
}
}

3.2 获得并设置属性(Field)

image-20221123162238900

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* 获得字节码中的属性并操作
*/
public static void getAndSetFields() throws Exception {
Class<User> clazz = User.class;
/**
* Filed getField(String name)
* 通过属性名获得属性对象(只能获得public修饰的属性的属性对象)
* Filed getDeclaredField()
* 通过属性名获得属性对象(获得任意修饰符修饰的属性对象)
*/
// Field id = clazz.getField("id");
// System.out.println(id );

Field id1 = clazz.getDeclaredField("id");
System.out.println(id1 );

/**
* Filed[] getFields( ) 获得public修饰所有属性对象,返回数组
* Filed[] getDeclaredFields( ) 获得所有属性对象,返回数组
*/
Field[] fields = clazz.getFields( );
System.out.println(fields.length );
Field[] declaredFields = clazz.getDeclaredFields( );
System.out.println(declaredFields.length );

// =============================================
// 获得属性名
String name = id1.getName( );
System.out.println("name = "+ name );

// 获得属性访问修饰符
int modifiers = id1.getModifiers( );
System.out.println("访问修饰符: " + modifiers );

// 获得属性值
// 获得哪个对象的该属性值
// 但是不能获得私有属性的值
// 可以通过设置,就可以访问,
id1.setAccessible(true);
User user = new User( );
int value = id1.getInt(user);
System.out.println("id = " + value );

// 设置属性值
// 设置哪个对象的该属性值
id1.setInt( user,11);

System.out.println(user );

}

3.3 获得并设置方法(Method)

image-20221123164801100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 获得字节码中的方法
*/
public static void getAndSeMethod() throws Exception {
Class<User> clazz = User.class;
// 方法有重载,需要通过参数来确定获得哪个方法
Method m1 = clazz.getMethod("m1"); // 获得无参的m1方法
Method m1_ = clazz.getMethod("m1",int.class); // 获得有参的m1(int)方法

// 获得关于方法的所有信息
int count = m1.getParameterCount( );// 参数个数
int count_ = m1_.getParameterCount( );// 参数个数

// 操作方法,让方法执行
// 参数1: 哪个对象的该方法执行
// 参数2: 该方法执行时的参数
Object ret = m1.invoke(new User( ));
System.out.println("m1()执行后的返回值:" + ret );
m1_.invoke(new User(),222);

}

3.4 获得并设置构造方法(Constructor)

image-20221123170511545

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获得字节码中的构造方法
*/
public static void getAndSeConstructor() throws Exception {
Class<User> clazz = User.class;

// 通过参数来获得有参还是无参构造
Constructor<User> constructor = clazz.getConstructor( );

// 构造方法执行,创建对象
User user = constructor.newInstance( );

System.out.println(user );

// 创建字节码的对象,还有另外方法
// 可以通过字节码,直接创建
User user1 = clazz.newInstance( );
}

4 使用反射封装DBUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package com.qf.util;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class DBUtil {

// 创建Properties类对象,专用于操作properties文件
private static final Properties properties = new Properties( );
// 声明Druid连接池的连接池对象
// 数据连接,一般称作数据源 dataSource
private static DruidDataSource dataSource;


static {

try {
InputStream inputStream = DBUtil.class.getResourceAsStream("/jdbc.properties");
properties.load(inputStream);
// 不需要由我们加载驱动
// 需要给dataSource赋值
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

} catch (Exception e) {
System.out.println("加载驱动异常!!");
e.printStackTrace( );
}
}

public static Connection getConnection() {
Connection conn = null;
try {
// 不需要我们获得连接
// 而是通过Druid获得
conn = dataSource.getConnection( );
} catch (Exception e) {
System.out.println("获得连接出异常!!!");
e.printStackTrace( );
}
return conn;
}


/**
* 关闭所有流
*/
public static void closeAll(Connection conn, Statement s) {
if (conn != null) {
try {
conn.close( );
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}

if (s != null) {
try {
s.close( );
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}
}

public static void closeAll(Connection conn, Statement s, ResultSet rs) {
if (conn != null) {
try {
conn.close( );
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}

if (s != null) {
try {
s.close( );
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}

if (rs != null) {
try {
rs.close( );
} catch (SQLException throwables) {
throwables.printStackTrace( );
}
}
}


/**
* 封装查询方法,返回一个对象
* 参数1 执行查询的SQL,预处理的,条件用?占位
* select * from tb_user where id = ? and username = ? and password = ?
* 参数2 结果要封装的类
* 参数3 给?赋值,不定长参数,是数组
* 1,admin,123456
*/
public static <T> T selectOne(String sql, Class<T> t, Object... args) {
Connection conn = getConnection( );
PreparedStatement ps = null;
ResultSet rs = null;
T target = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; args != null && i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}

rs = ps.executeQuery( );
/**
* 创建对象
* 从数据库取出数据,并设置对象属性
*/
while (rs.next( )) {
target = t.newInstance( );
Field[] declaredFields = t.getDeclaredFields( );
for (int i = 0; i < declaredFields.length; i++) {
Field field = declaredFields[i];
Object value = rs.getObject(field.getName( ));

// 破解私有
field.setAccessible(true);

// 给对象的该字段赋值
field.set(target, value);
}

}
} catch (Exception e) {
e.printStackTrace( );
} finally {
closeAll(conn, ps, rs);
}
return target;
}

public static <T> List<T> selectAll(String sql, Class<T> t, Object... args) {
Connection conn = getConnection( );
PreparedStatement ps = null;
ResultSet rs = null;
T target = null;
ArrayList<T> list = new ArrayList<>( );
try {
ps = conn.prepareStatement(sql);
for (int i = 0; args != null && i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}

rs = ps.executeQuery( );
/**
* 创建对象
* 从数据库取出数据,并设置对象属性
*/
while (rs.next( )) {
target = t.newInstance( );
Field[] declaredFields = t.getDeclaredFields( );
for (int i = 0; i < declaredFields.length; i++) {
Field field = declaredFields[i];
Object value = rs.getObject(field.getName( ));

// 破解私有
field.setAccessible(true);

// 给对象的该字段赋值
field.set(target, value);
}
list.add(target);

}
} catch (Exception e) {
e.printStackTrace( );
} finally {
closeAll(conn, ps, rs);
}
return list;
}

/**
* 增删改方法一样
*/
public static boolean update(String sql, Object... args) {
Connection conn = getConnection( );
PreparedStatement ps = null;
int num = 0;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; args != null && i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
num = ps.executeUpdate( );
} catch (Exception e) {
e.printStackTrace( );
} finally {
closeAll(conn, ps);
}
return num > 0 ? true : false;
}
}

day14

今日内容

0 复习昨日

1 maven

2 tomcat

3 创建项目

0 复习昨日

1 单词写5遍
argument 参数 parameter 参数 access 访问 field 字段 invoke 调用 illegal 非法

invalid 无效 column 列 property 属性 DataSource 数据源

2 数据库连接池有啥好处

3 获得字节码文件的方式
Class.forName(“”)
Xxx.class
对象.getClass()

4 封装
隐藏实现的细节,对外提供访问的方法
方法的封装
类的封装
工具类的封装

继承

多态
方法的参数列表是父类
抽象
接口

1 Maven

1.0 引言

之前写项目时,会有不同的问题

  • jar包 管理(项目中有很多jar包)
    • 需要自己找jar包,下载
    • 手动导入项目中
    • jar包版本更新….
    • 占磁盘空间
    • 重复量大
  • 项目结构不规范
    • java代码和配置文件位置不规范
  • ….

1.1 介绍

项目管理工具,统一项目结构,配置文件,依赖,部署,测试等等


Maven这个单词来自于意第绪语(犹太语),意为知识的积累,最初在Jakata Turbine项目中用来简化构建过程。当时有一些项目(有各自Ant build文件),仅有细微的差别,而JAR文件都由CVS来维护。于是希望有一种标准化的方式构建项目,一个清晰的方式定义项目的组成,一个容易的方式发布项目的信息,以及一种简单的方式在多个项目中共享JARs。

1.2 下载

网址 Maven – Download Apache Maven

下载地址 Index of /dist/maven/maven-3 (apache.org)

1.3 安装

1.3.1 解压

特别注意: 尽量不要有中文路径

1
2
3
4
5
解压后有几个文件夹
- bin 运行maven命令的脚本
- boot 运行是需要类库
- conf 配置,有关于maven的配置文件
- lib 运行是需要的jar包
1.3.2 配置环境变量

系统变量创建: MAVEN_HOME 值是maven安装路径

系统变量path添加 %MAVEN_HOME%\bin

1.3.3 测试

打开cmd,输入mvn -v

image-20221124095549254

1.4 仓库

maven项目管理工具,管理依赖(jar包),实现依赖的复用.


maven有仓库,将依赖放入仓库,每个项目都去复用

  • 本地仓库
    • 自己电脑上
    • 需要依赖的时候,会先从本地仓库中,如果找不到就会去中央仓库找,下载到本地仓库
  • 中央仓库
    • 位于国外服务器,包含绝大部分依赖
    • 可能有时候访问比较慢
  • 公共仓库
    • 私服(个人)
    • 阿里云,网易,等等

1.5 Maven配置

1.5.1 修改仓库位置

maven安装好后,默认本地仓库在c盘,要修改为其他地方


修改maven的配置文件(conf\settings.xml)

1
2
<!-- 将53行注释内代码,复制出来,粘贴到55行,修改路径为自己本地仓库位置 -->
<localRepository>D:\repository</localRepository>

ps: 记得保存,ctrl+s

1.5.2 设置镜像

依赖会先从本地仓库找,本地没有会从中央仓库下载到本地仓库,中央仓库访问很慢,所以需要设置国内镜像,访问就很快!

1
2
3
4
5
6
7
8
9
10
11
<!--setting.xml中添加如下配置 146行附近
一定是在开闭标签 <mirrors> </mirrors>中间设置
-->
<mirror>
<id>aliyun</id>
<!-- 中心仓库的 mirror(镜像) -->
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<!-- aliyun仓库地址 以后所有要指向中心仓库的请求,都会指向aliyun仓库-->
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

2 IDEA - MAVEN

2.1 idea关联maven

  • 打开任何一个idea项目

  • file - setting fornew project

    image-20221124105909608

找到build

image-20221124110058057

image-20221124110417317

2.2 创建java项目

  1. 找到maven选项

image-20221124110734298

  1. 设置信息

image-20221124111014498

2.3 java项目结构

image-20221124111329959

1
2
3
4
5
6
7
8
|-项目名
|---src
|------main
|---------java
|---------resources
|------test
|----------java
|---pom.xml

2.4 pom

pom 项目对象模型,这是一个xml文件(ps: xml文件一种文件格式,类似HTML是标签形式的)

pom文件内定义

  • 项目信息
    • 项目名
    • 组织名
    • 版本
    • 打包方式
      • 默认是jar , 普通java项目
      • 可以指定为war, 即web项目
  • 项目依赖
    • 依赖就是jar包
    • 是以坐标的形式展现
  • 构建工具
1
2
3
4
5
6
<!--  项目信息--> 
<groupId>com.qf</groupId>
<artifactId>day45_java</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 打包方式 ,默认是jar,如果是javaweb项目,打包方式是war-->
<packaging>jar</packaging>
1
2
3
4
5
6
7
8
9
<!-- 依赖 -->
<dependencies>
<!-- 具体的jar包依赖坐标 -->
<!-- <dependency>-->
<!-- <groupId></groupId>-->
<!-- <artifactId></artifactId>-->
<!-- <version></version>-->
<!-- </dependency>-->
</dependencies>
1
2
3
4
5
6
<!-- 构建信息 -->
<!-- <build>-->
<!-- <plugins>-->
<!-- <plugin>...</plugin>-->
<!-- </plugins>-->
<!-- </build>-->

2.5 导入依赖

2.5.1 查找依赖

官方提供一中央仓库网站,网站中有所有jar包的依赖信息,就可以搜索依赖

Maven Repository: Search/Browse/Explore (mvnrepository.com)

image-20221124113200709

image-20221124113256197

image-20221124113336192

2.5.2 使用依赖

将复制的依赖坐标,粘贴到pom文件

1
2
3
4
5
6
7
8
<!-- 依赖,就是jar包 -->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>

image-20221124113618648

image-20221124113709676

还可以进入仓库中去查看,是否下载成功

image-20221124113901244

2.6 测试

使用之前jdbc测试

  • 使用maven创建java项目
  • 导入依赖
    • mysql驱动
    • druid.jar包
  • 编码
    • java文件夹写java代码
    • resources 文件夹写配置文件
  • 测试

image-20221124151111639

3 JavaWeb

JavaWeb开发就是

  • 前端页面发请求
  • 后台服务器接收请求,将请求中数据发送到数据库
  • 数据库处理CRUD
  • 数据库处理完再响应给服务器
  • 服务器根据结果再响应数据到浏览器

项目开发的架构

  • C/S (Client / Server)
  • 必须要开发客户端软件(QQ,微信,钉钉,LOL…)
  • 优点: 性能好(画质,交互,流程度),安全度高
  • 一般用于游戏/音视频软件
  • 缺点: 软件更新维护升级很麻烦
  • B/S (Browser / Server)
  • 只需要一个浏览器
  • 一般用于功能不复杂,比如微博/淘宝/京东
  • 优点: 更新维护只需重启服务器
  • 缺点: 图形显示,流畅度,安全性相对比较低
  • 目前我们学习Java是为了开发B/S架构的项目

image-20221124155854858

4 服务器

服务器也称为web服务器,是运行及发布web应用的容器.

只有将开发的项目放到服务器中,才可以通过http请求访问到数据.

常见的web服务器

  • Tomcat 主流,免费,并发量500左右
  • Jetty 效率会比Tomcat高,淘宝用的就这个
  • Resin 新浪在用
  • WebLogic
  • Apache

5 Tomcat

Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。

5.1 下载

官网 Apache Tomcat® - Welcome!

image-20221124161014616

5.2 安装

解压即可使用,特别注意,解压路径中不要有中文路径!

image-20221124161312697

1
2
3
4
5
6
7
bin      放tomcat运行命令
conf 配置文件
lib 运行所需库,jar包等
logs 运行日志
temp 临时文件
webapps 【重要】存放web项目的路径
work 运行时产生文件此处

5.3 启动服务器

进入安装路径的bin目录下,执行(双击)startup.bat命令

image-20221124162030294

5.4 访问服务器

服务器是在本地,所以访问ip是localhost,tomcat端口默认是8080,即完整服务器路径

http://localhost:8080/index.jsp

image-20221124162304974

5.5 关闭服务器

只需要关闭服务器黑窗口

5.6 特别说明

现在进行的操作,只是证明tomcat装好可以使用,

等后续需要idea配置tomcat进行启动,访问,停止

6 IDEA - Tomcat

IDEA关联Tomcat是要为每个web项目关联服务器

6.1 maven创建javaweb项目

6.1.1 使用模板创建【推荐】

image-20221124163159728

image-20221124163256132

模板创建并不完整

image-20221124164022334

手动补全目录

补上src/test目录

image-20221124164149737

image-20221124164200066

补上src/main/java目录

image-20221124164304380

补上src/main/resources目录

图略

完整结构如下

image-20221124164424614

6.1.2 不使用模板创建javaweb项目

像创建java项目一样,创建javaweb项目

image-20221124164851765

image-20221124164938024

这样创建出的是java项目,改造成javaweb项目

image-20221124165117145

image-20221124165203477

但是这样创建的web文件夹位置跟maven规范的不一致

image-20221124165543862

这样不推荐

6.1.3 不使用模板,也不使用框架支持

这种方式是纯手动改造java项目为javaweb项目

image-20221124165808463

image-20221124165845936





手动创建webapp目录,并在在webapp目录下创建WEB-INF目录,并在其下创建web.xml文件,内容如下

1
2
3
4
5
6
7
<?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_3_1.xsd"
version="3.1">

</web-app>

最后,在webapp目录下,创建一个index.jsp文件(jsp文件,理解为html)

image-20221124170408237





在项目结构(project structure)中配置该webapp路径

image-20221124170638574

image-20221124171006193

6.2 web项目设置Tomcat(部署项目)

image-20221124171433551

image-20221124171633043

image-20221124171901517

image-20221124171913608

image-20221124171958130

image-20221124172057168

day15

今日内容

0 复习昨日
1 Servlet基础
1.1 Servlet介绍
1.2 第一个Servlet
1.3 流程分析
1.4 使用细节
1.5 映射细节
1.6 生命周期
2 HttpServlet
2.1 HTTP请求、响应、状态码
2.2 GET和POST的区别
2.3 HttpServlet

0 复习昨日

1 maven创建-java项目结构
2 maven创建-javaweb项目结构
3 tomcat的端口 8080
ip: 地址,通过地址找到这台电脑/服务器
端口: 应用程序的编号,通过端口找到app

4 tomcat服务器部署项目启动后,访问路径是?
协议://ip:端口/项目名/页面
http://localhost:8080/day45_web/index.html
http://localhost:8080/index.html

5 addres localhost:8080 is already in use 什么意思
端口占用,解决方案:
1) 端口查杀
netstat -ano | findstr 8080
taskkill /f /pid 进程号
2) 改端口号
(我们自己设计端口,最好是8000以上
3) artifact 工件/项目
dependencies 依赖,复数
dependency 依赖,单数

1 Servlet

1.1 介绍

javaweb开发,就是需要服务器接收前端发送的请求,以及请求中的数据,经过处理(jdbc操作),然后向浏览器做出响应.


我们要想在服务器中写java代码来接收请求,做出响应,我们的java代码就得遵循tomcat开发规范


因此Tomcat提供了开发的规范,就是servlet.

Servlet就是运行在服务器上的程序,可交互式的接收服务器的请求,并可以做出响应


总结Servlet的作用:

  • 运行在服务器,是一个服务器端的程序
  • 接收客户端请求,向客户端做出响应
  • 动态网页(jsp)

1.2 第一个Servlet程序

1.2.1 创建web项目

image-20221125095804303

image-20221125095900779

补全目录结构

image-20221125100044388

1.2.2 pom依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.qf</groupId>
<artifactId>day46_servlet</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- java项目打包方式是jar包 -->
<!-- web项目打包方式是war包 -->
<packaging>war</packaging>


<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<!-- 引入servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 引入jsp依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
</project>

1.2.3 编写Servlet

  • 实现javax.servlet.Servlet接口
  • 重写方法
  • 在核心方法service()里面完成接收请求,做出响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.qf.servlet;

import javax.servlet.*;
import java.io.IOException;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class MyServlet1 implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {}

@Override
public ServletConfig getServletConfig() {
return null;
}

/**
* 核心方法,服务,在这个方法中可以完成接收请求,做出响应
* @param req 用来处理请求的对象
* @param res 用来处理响应的对象
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 通过请求对象,可以获得请求的ip
String ip = req.getRemoteAddr( );
System.out.println("ip = "+ip );

// 响应,
// res.getWriter()获得字符输出流
// .writer() 写出到浏览器字符(中文可能乱码)
res.getWriter().write("i'm Response,Hello");
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {}
}

1.2.4 配置Servlet

因为服务器中会有很多servlet,浏览器发请求如何确定访问哪一个servlet?

此时就需要做一个映射: 请求路径和servlet类的映射,即发出的请求由哪个servlet类来处理


配置需要写在webapp/WEB-INF/web.xml中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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_3_1.xsd"
version="3.1">
<!-- 在这里写路径和servlet映射 -->
<servlet>
<!-- servlet的名字,任意 -->
<servlet-name>servlet1</servlet-name>
<!-- servlet的路径 -->
<servlet-class>com.qf.servlet.MyServlet1</servlet-class>
</servlet>

<!-- 请求路径映射 -->
<servlet-mapping>
<!-- 该路径映射的servlet名 -->
<servlet-name>servlet1</servlet-name>
<!-- 请求路径的模板,一定要/开头 -->
<url-pattern>/s1</url-pattern>
</servlet-mapping>
</web-app>

浏览器发出请求,经过web.xml中配置的信息,

  • 匹配url-pattern>/s1</url-pattern,有该路径则正常访问,无该路径报404
  • 通过servlet-name找到servlet类
  • 再通过servlet-class,找到servlet类路径
  • 该servlet就可以执行service()

1.2.5 部署项目

web项目已经开发完毕,将项目部署到服务器Tomcat

配置Tomcat

image-20221125103951611

部署项目

image-20221125104134483

image-20221125104144365

image-20221125104255911

启动

image-20221125104500192

发出请求

http://localhost:8080/day46/s1

image-20221125104906466

day16

今日内容

0 复习昨日

1 接收请求

2 处理响应

0 复习昨日

HTTP请求中

  • 请求行
    • 请求方法,请求路径
  • 请求头
    • 页面信息
  • 请求正文
    • 请求的数据

HTTP响应中

  • 响应行
    • 状态码 信息
  • 响应头
    • 页面信息
  • 响应正文
    • 要给浏览器的内容

1 接收请求

浏览器发出请求,经过web.xml映射匹配,找到Servlet对应的方法(doGet/doPost),接收请求数据,可以接收请求中的请求行,请求头,请求正文

  • 浏览器发出请求
    • a/form/ajax
  • 经过web.xml映射匹配
    • web.xml(8行代码)
  • doGet/doPost
    • 前端是get请求,就重写doGet
    • 前端是post请求,就重写doPost
  • 如何接收数据
    • 通过HttpServletRequest对象处理

需求: html页面中写一个表单,发送请求,后台服务器接收所有请求数据

1.1 编写页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
表单提交数据,一定要有name属性,
后台服务器,就是name获得值
------------------------
点击提交,会以method指定的请求方式,将数据发送到action指定后台服务器
action路径,建议是 /项目名/路径
-->
<form action="/day47/req" method="get">
用户名 <input type="text" name="username"><br>
密码 <input type="password" name="password"><br>
性别 <input type="radio" name="sex" value="1">
<input type="radio" name="sex" value="2"><br>
技能<input type="checkbox" name="skill" value="Java">Java
<input type="checkbox" name="skill" value="JavaScript">JavaScript
<input type="checkbox" name="skill" value="SSM">SSM<br>
学历<select name="xueli">
<option value="gaozhong">高中</option>
<option value="dazhuan">大专</option>
<option value="benke">本科</option>
</select>
<input type="submit" value="提交">
</form>

</body>
</html>

1.2 编写Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.qf.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class MyServlet extends HttpServlet {

/**
*
* @param req 处理请求的对象
* @param resp 处理响应的对象
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 请求方法
String method = req.getMethod( );
System.out.println("method = "+method);

// 获得请求路径
String requestURI = req.getRequestURI( );// 资源标识符
StringBuffer requestURL = req.getRequestURL( ); // 资源定位符
System.out.println("requestURI = " + requestURI);
System.out.println("requestURL = " + requestURL);

System.out.println("--------------------------------------" );

// 接收请求头【了解】
String host = req.getHeader("Host");
System.out.println("host = " + host);

Enumeration<String> keys = req.getHeaderNames( );
while (keys.hasMoreElements()) {
String key = keys.nextElement( );
String value = req.getHeader(key);
System.out.println(key + " : " + value);
}
System.out.println("--------------------------------------" );
// 接收请求数据【重点】
// 接收请求数据,无论单选,还是下拉框等都是getParameter(name);
// name是前端标签name属性的值
String username = req.getParameter("username");
String password = req.getParameter("password");
String sex = req.getParameter("sex");
String xueli = req.getParameter("xueli");

System.out.println("username = " + username);
System.out.println("password = " + password);
System.out.println("sex = " + sex);
System.out.println("xueli = " + xueli);
// 复选框
// 如果一个没选,数组没有创建,是null,不是长度为0
String[] skills = req.getParameterValues("skill");
// 空指针异常,就是使用空对象调用属性和方法
for (int i = 0; skills != null && i < skills.length; i++) {
System.out.println("skill["+(i+1)+"] = " + skills[i]);
}

}
}

1.3 配置web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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_3_1.xsd"
version="3.1">

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

<servlet>
<servlet-name>servlet</servlet-name>
<servlet-class>com.qf.servlet.MyServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>servlet</servlet-name>
<!--此处不需要写项目名,只需要写请求路径-->
<url-pattern>/req</url-pattern>
</servlet-mapping>
</web-app>

1.4 部署项目

1.5 启动测试

2 做出响应

做出响应是通过HttpServletResponse对象

  • 响应行
    • 状态码
  • 响应头
    • 响应信息,其中有一个cookie后续会用到,以及编码格式
  • 响应正文
    • 向浏览器展现的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 响应状态码
// 200 是成功, 302 重定向 404 资源未找到 500 服务器错误
// 一般不用设置,为自动响应
// resp.setStatus(200);

// 设置响应头
// resp.setHeader("key","value");
// 指定浏览器如何解析响应的内容,解决响应乱码
resp.setContentType("text/html;charset=utf-8");


// 向浏览器响应内容(响应正文)
PrintWriter out = resp.getWriter( );
out.write("<html>");
out.write(" <head>");
out.write(" <title>这是响应</title>");
out.write(" </head>");
out.write(" <body>");
out.write(" <div style='background-color:red;width:500px;height:500px;font-size:50px'>");
out.write(" 这是响应,欢迎"+username);
out.write(" </div>");
out.write(" </body>");
out.write("</html>");

3 乱码解决

请求乱码

1
req.setCharacterEncoding("utf-8");

响应乱码

1
resp.setContentType("text/html;charset=utf-8");

day17

今日内容

周一
0 复习上周
1 本周计划
2 MVC和三层架构
3 Login案例
4 请求转发
5 重定向

0 复习昨日

1 jdbc五大步骤
1) 注册驱动(反射)
2) 获得连接
3) 获得执行sql对象
4) 执行SQL
5) 关流
2 什么是SQL注入
通过SQL关键词,在执行SQL时出现不正常的情况
3 PreparedStatement怎么使用,有什么特点
怎么使用? 之前拼接sql参数的地方,现在使用?占位,经过预处理后,再给?处赋值
有什么特点? 1) 执行时不需要再给executeQuery()传参数
2) 可以避免SQL注入的问题
3) 向?赋值的时候,自动给字符串拼接单引号
mybatis # PreparedStatement ,字符串’’
$ Statement
4 什么是servlet
servlet是运行在服务上的程序
servlet主要功能是: 接收请求,做出响应

5 Http请求方式有哪些
get
post
6 Http请求报文都有哪些内容
请求头,请求行,请求正文(数据)

针对不同的请求方式,后台有哪些请求方法?
doGet()
doPost()
7 后台接收请求内容的方法有哪些
req.getMethod()
req.getRequestURL()
req.getRequestURI()
req.getParameter(name属性的值);//获得请求数据
req.getParameterValues();

8 前端如何发送数据
form表单,标签得设置name属性

1
2
3
4
5
<form action="/day48/login" method="get">
<input type="text" name="username" />
<input type="text" name="password" />
<input type="submit" value="提交" />
</form>

​ ajax
​ a标签

1
<a href="/day48/login?username=root&pwd=555">登录</a>

9 前端后后台之间如何映射?
通过web.xml配置8行代码

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>c.f.s.MyServlet1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet1</servlet-name>
<url-parttern>*.do</url-parttern>
</servlet-mapping>

1 MVC和三层架构

通过Login案例,一个LoginServlet中

  • 接收请求

  • 完成JDBC操作

  • 根据结果做出响应

    以上这种开发模式,不好,不便于后期迭代维护


在开发中有一个思想:”分而治之”

MVC思想

  • M model/模型
    • 模型主要是指javabean,有一些java类
    • 比如封装数据的类,User类
    • 比如其他功能类,UserService,UserDao
  • V view/视图
    • 视图就是页面,
    • 比如JSP/HTML
    • 为了展现数据
  • C controller/控制器
    • 控制器控制整个流程的走向
    • 控制页面跳转

三层架构: 是指开发中编码时项目结构,主要是指将不同的功能代码再细分

  • 控制层
    • servlet主要做控制
    • 控制页面跳转
  • 业务层
    • service层
    • 主要处理业务逻辑
  • 数据访问层
    • Dao层
    • 主要与数据库交互

2 Login案例

需求: HTML页面中输入框用户名和密码,登录

  • 登录成功给出提供,欢迎
  • 登录不成功,给出提示,用户名或密码错误

2.1 搭建环境

数据库环境

1
2
3
4
5
6
7
8
9
10
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户编号',
`username` varchar(10) DEFAULT NULL COMMENT '用户名',
`password` varchar(10) DEFAULT NULL COMMENT '密码',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
`createTime` date DEFAULT NULL COMMENT '注册时间',
`money` double(10,2) DEFAULT NULL COMMENT '账户余额',
`sex` int(1) DEFAULT NULL COMMENT '性别 1男2女',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;

项目环境

  • 创建maven-web项目

  • 补全项目结构

  • 导入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <dependencies>
    <!-- servlet -->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    </dependency>

    <!-- servlet-jsp -->
    <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.1</version>
    </dependency>

    <!-- mysql驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    </dependency>

    <!-- druid连接池 -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
    </dependency>
    </dependencies>
  • 项目必备的java包和类

    • 工具包和工具类
    • 实体类
    • 包结构

2.2 页面

登录页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<form action="/day48/login" method="post">
用户名<input type="text" name="username"><br>
密码<input type="password" name="password"><br>
<input type="submit" value="登录"><br>
</form>
</div>
</body>
</html>

2.3 UserServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.qf.servlet;

import com.qf.model.User;
import com.qf.service.UserService;
import com.qf.service.impl.UserServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 控制层
*/
public class UserLoginServlet extends HttpServlet {

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 防止乱码
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");

// 1 接收请求
String username = req.getParameter("username");
String password = req.getParameter("password");

// 2 调用业务层处理业务
UserService userService = new UserServiceImpl( );
User user = userService.login(username, password);

// 3 根据结果做出响应
PrintWriter out = resp.getWriter( );
if (user != null) {
// 响应登录成功
// 应该是跳转一个页面来展现数据,而不是直接作出响应
// 但是现在还没学,暂时还是使用手动响应
out.write("<html>");
out.write("<body>");
out.write("<h1>");
out.write("欢迎"+user.getUsername()+"登录");
out.write("</h1>");
out.write("</body>");
out.write("</html>");
} else {
// 响应登录不成功
out.write("<html>");
out.write("<body>");
out.write("<h1>");
out.write("用户名或密码错误!");
out.write("</h1>");
out.write("</body>");
out.write("</html>");
}
}
}

2.4 UserService

一般开发时,会将UserService以及UserDao设计成接口+实现类的形式

  • 可以先设计接口,规定项目的功能
  • 接口还可以松耦合,实现多态,易于代码扩展

UserService接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qf.service;

import com.qf.model.User;

import java.util.List;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 用户的业务层处理
*/
public interface UserService {

User login(String username, String password);

// List<User> findAll();

// boolean deleteUserById(int id);

}

UseServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.qf.service.impl;

import com.qf.dao.UserDao;
import com.qf.dao.impl.UserDaoImpl;
import com.qf.model.User;
import com.qf.service.UserService;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc impl包,只用于存放实现类
* 所有的实现类都应该是接口名+Impl来命名
* 例如: UserServiceImpl
* 通过这个名字,要得到两个信息
* 1) 有一个接口UserService
* 2) 有一个类UserServiceImpl
* -----------------------------
* Service层,是业务层,处理业务逻辑
*/
public class UserServiceImpl implements UserService {

@Override
public User login(String username, String password) {
// 1 业务逻辑处理
// 但是今天这个需求没有什么业务,就可以不做

// 2 调用数据访问层操作数据库
UserDao userDao = new UserDaoImpl();
User user = userDao.login(username, password);

// 业务层还可以对数据库返回的结果再处理

return user;
}
}

2.5 UserDao

UserDao接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.qf.dao;

import com.qf.model.User;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public interface UserDao {

User login(String username, String password);

}

UserDaoImpl实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.qf.dao.impl;

import com.qf.dao.UserDao;
import com.qf.model.User;
import com.qf.util.DBUtil;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class UserDaoImpl implements UserDao {

@Override
public User login(String username, String password) {
String sql = "select * from tb_user where username = ? and password = ?";
User user = DBUtil.selectOne(sql, User.class, username, password);
return user;
}
}

这里使用了DBUtil,不习惯使用的话,也可以使用原始的JDBC自己操作

3 请求转发

请求对象HttpServletRequest

  • 请求的转发(将请求转发到其他的servlet)
  • 跳转页面
  • 请求域(存取数据)

请求转发的总结

  • 请求转发地址栏不动
  • 请求转发是服务器行为,是服务器内部动作
  • 浏览器只有一次请求
  • 可以当做请求域,数据可以在Servlet之间共享

3.1 请求转发

需求: 发出请求/a 映射AServlet,利用请求转发,将请求转发到BServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.qf.servlet;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class AServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

System.out.println("AServlet.doGet..." );


// 请求转发
// path: 就是要转发的Servlet对应的映射路径
// RequestDispatcher dispatcher = req.getRequestDispatcher("/b");
// 执行转发
// dispatcher.forward(req,resp);
// 路径不需要写成 /项目名/b
req.getRequestDispatcher("/b").forward(req,resp);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.qf.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class BServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

System.out.println("BServlet.doGet..." );

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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_3_1.xsd"
version="3.1">

<!-- 映射AServlet -->
<servlet>
<servlet-name>aServlet</servlet-name>
<servlet-class>com.qf.servlet.AServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>aServlet</servlet-name>
<url-pattern>/a</url-pattern>
</servlet-mapping>

<!-- 映射BServlet -->
<servlet>
<servlet-name>bServlet</servlet-name>
<servlet-class>com.qf.servlet.BServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>bServlet</servlet-name>
<url-pattern>/b</url-pattern>
</servlet-mapping>
</web-app>

3.2 请求域

请求域是指: HttpServletRequest对象相当于是容器,存取数据,可以在请求转发的几个类中共享数据.

  • 存储数据 req.setAttribute(key,value)
  • 取出数据 req.getAttribute(key)

请求域作用以及场景: 在多个Servlet请求转发的时候,用来传递数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.qf.servlet;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class AServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

System.out.println("AServlet.doGet..." );

// 请求域
// 存储数据到请求域
req.setAttribute("username","jack");

// 请求转发
// path: 就是要转发的Servlet对应的映射路径
// RequestDispatcher dispatcher = req.getRequestDispatcher("/b");
// 执行转发
// dispatcher.forward(req,resp);
// 路径不需要写成 /项目名/b
req.getRequestDispatcher("/b").forward(req,resp);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.qf.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class BServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

System.out.println("BServlet.doGet..." );

// 请求域中取出数据
String username = (String) req.getAttribute("username");

System.out.println("username = " + username);
}
}

3.3 跳转页面

image-20221128162553136

4 重定向

重定向是HttpServletResponse对象完成一个动作

  • 可以将请求重新跳转至其他Servlet
  • 可以跳转页面

重定向总结:

  • 重定向是浏览器动作
  • 重定向地址栏会有变化
  • 是发出两次请求
  • 请求域中的数据在重定向后不能共享(因为是两次请求)

需求:

需求: 发出请求/a 映射AServlet,利用重定向,将请求重新发送请求到BServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class AServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

System.out.println("AServlet.doGet..." );

// 请求域
// 存储数据到请求域
req.setAttribute("username","jack");

// 请求转发
// path: 就是要转发的Servlet对应的映射路径
// RequestDispatcher dispatcher = req.getRequestDispatcher("/b");
// 执行转发
// dispatcher.forward(req,resp);
// 路径不需要写成 /项目名/b,是因为请求转发是服务器动作
// /b,即从服务器根路径开始访问,服务器的根路径自带项目名
// http://localhost:8080/day48/b
//req.getRequestDispatcher("/b").forward(req,resp);

// 请求转发可以跳转页面
// req.getRequestDispatcher("/404.html").forward(req,resp);


// 重定向
// 重定向是浏览器行为,发出/b请求,那就是从浏览器的根路径发出请求
// 浏览器的根路径是端口: http://localhost:8080/b
String contextPath = req.getContextPath( ); // 获得项目名 /day48
System.out.println("contextPath = " + contextPath);

// resp.sendRedirect(contextPath+"/b");
resp.sendRedirect(contextPath+"/404.html");
}
}

5 注解

JDK1.5后出现的技术,注解(Annotation),是一种注释,给程序注释然后程序运行给JVM中的java代码看的

  • @Override

注解文件既不是类也不是接口

5.1 创建注解文件

image-20221128172739864

5.2 元注解

注解的注解就是元注解

5.2.1 @Target

@Target 目标,用来规定注解能用在什么位置

位置 ElementType
包上 PACKAGE
类/接口/数组/枚举上 TYPE
成员变量/局部变量 FIELD/LOCAL_VARIABLE
方法/构造方法 METHOD/CONSTRUCTOR

5.2.2 @Retention

保留,指注解保留到什么时候,或者说叫保留至什么时候生效

ps: 如果是自定义注解,一般是为了通过反射技术读取注解,所以要定义保留策略为RUNTIME

保留策略 解释
SOURCE 源码阶段有效
CLASS 编译后class中有效
RUNTIME 运行时生效

5.3 注解参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
// 注解的参数
// 数据类型 参数名();
// 一旦设置了参数,那么在使用注解时就必需给注解参数赋值
// 除非给参数设置了默认值
int a() default 0;

// 当注解的参数名value,使用时可以省略
String value() default "";

String[] values() default "";

}

注解的参数都是为了通过反射技术去读取到注解参数中的值

5.4 实际应用

Servlet开发中也支持使用注解,大大提高开发效率

  • @WebServlet 注解,可以取代web.xml中[经典8行]代码

day18

今日内容

0 复习昨日
1 Cookie
2 Session
3 Filter

0 复习昨日

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1 MVC和三层架构是什么?
MVC
三层架构: controller,service,dao

2 请求转发有什么特点
req.getRequestDispatcher(path).forward(req,resp);
功能: 1) 将请求转发到其他servlet
2) 当做域对象存取数据
req.setAttribute(key,value)
req.getAttribute(key)
3) 跳转页面
特点:
1) 地址栏不变
2) 请求转发是服务器动作
3) 一次请求
4) 在一次请求中,请求域是可以共享
3 重定向有什么特点
resp.sendRedirect(location);
功能: 1) 跳转Servlet
2) 跳转页面
特点:
1) 地址栏会改变
2) 重定向是浏览器动作
3) 两次请求
4) 在重定向中,请求域是不可以共享

1 关于路径

​ 关于请求时出的路径

  • 路径中加/,就是从根路径请求

  • 路径中不加/,就是从当前路径请求

  • 请求转发是服务器动作,服务器的根路径是项目名,/b请求,其实是

    http://localhost:8080/项目名/b

  • 重定向是浏览器动作,浏览器的根路径是端口,/b请求,其实是

    http://localhost:8080/b

2 IDEA热部署

换句话就是,可以实现修改代码不重启服务器.

设置idea的Tomcat为这样

image-20221129095612050

设置完之后,修改前端页面,只需要鼠标离开代码,idea就会自动更新前端代码


设置完之后,修改后端代码,只需要点击下面地方,就可以服务器中的代码

image-20221129100901138

3 会话技术

javaweb开发中会话就是指客户端与服务器的一次会话交互. 比如使用浏览器访问淘宝,那就是开启会话,关闭浏览器会话结束.


在访问淘宝网站时,经常提醒你登录,登录完成后,访问所有的淘宝的网页,以及功能(下单,查订单,付款,聊天等等…)都不需要登录.

那么,淘宝怎么知道是[你]在操作,或者说淘宝怎么知道是登录了还是没登录呢?

淘宝网站会记录登录信息.


记录会话中数据的技术就是会话技术

HTTP协议是无状态,不能保存信息

会话技术是指:

  • cookie
    • 浏览器技术
  • session
    • 服务端技术

Cookie是浏览器技术,用来存储一小段数据.(存储4kb或者8kb)


通过浏览器访问某个网站时,后台服务器创建了Cookie,通过响应头返回给浏览器,浏览器就存储了Cookie,后续每次访问中请求头中都会自动带上cookie访问服务器.

image-20221129144809097

4.1 创建cookie

创建Cookie

  • Cookie cookie = new Cookie(String key,String value);
  • 创建cookie的构造方法只有一个有参构造
  • 参数都是String类型

  • 默认创建的Cookie,
    • 到期时间是保存至浏览器关闭
    • 保存的路径是当前项目下

通过方法设置

  • 保存时间 setMaxAge(时间值)
    • -1 保存至浏览器关闭
    • 0 销毁
    • 秒数
  • 保存路径 setPath(String path)

【特别说明:cookie的名字,值,路径一致才是同一个cookie】

4.2 响应给浏览器

resp.addCookie(cookie)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.qf.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 创建,响应cookie
*/
@WebServlet("/ck1")
public class TestCookieServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 创建Cookie
Cookie cookie = new Cookie("username","admin");

// 设置时间
// 0 销毁
// -1 保存至浏览器关闭(默认的)
// 指定保存的秒数
cookie.setMaxAge(60 * 60 * 24 * 365);

// 默认存储到当前项目下
// cookie.setPath("/");

// 响应给浏览器
resp.addCookie(cookie);
}
}

4.3 获得Cookie

一旦创建cookie,后续每次请求服务器,请求头中都会自动带上cookie,那么就可以使用req对象获得cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.qf.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 获得Cookie
*/
@WebServlet("/ck2")
public class TestCookie2Servlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 没有获得单个cookie的方法,直接获得所有cookie
Cookie[] cookies = req.getCookies( );
for (int i = 0; cookies != null && i < cookies.length; i++) {
Cookie cookie = cookies[i];
// 获得
String name = cookie.getName( );
System.out.println("name = " + name);

String value = cookie.getValue( );
System.out.println("value = " + value);

int maxAge = cookie.getMaxAge( );
System.out.println("maxAge = " + maxAge);

String path = cookie.getPath( );
System.out.println("path = " + path);
}
}
}

4.4 cookie的路径问题

cookie的路径在创建时默认是当前路径下

其实路径,就是cookie的使用范围,只有在指定的范围路径内才可以读取的cookie


演示1

  • 第一个Cookie1Servlet,设置2个cookie,不设置路径(默认当前项目名)
  • 创建另外两个Servlet分别取得这个两个cookie
    • UserServlet,请求路径/user/ck
    • AdminServlet,请求路径/admin/ck
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@WebServlet("/create2CK")
public class Cookie1Servlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

/**
* 默认路径是项目名,即/day49
* 也就是说,只有请求是在/day49/xxx/xx都可以获得这些cookie
*/
Cookie userCookie = new Cookie("user", "user_ck");
Cookie adminCookie = new Cookie("admin", "admin_ck");

resp.addCookie(userCookie);
resp.addCookie(adminCookie);

}
}

结果: UserServlet和AdminServlet能获得所有的cookie


演示2

  • 第一个Cookie1Servlet,设置2个cookie,设置了不同的路径
  • 另外两个Servlet分别取得这个两个cookie
    • UserServlet,请求路径/user/ck
    • AdminServlet,请求路径/admin/ck
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WebServlet("/create2CK")
public class Cookie1Servlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

/**
* 默认路径是项目名,即/day49
* 也就是说,只有请求是在/day49/xxx/xx都可以获得这些cookie
* -------------------------------------------------
* 设置了路径,/day49/user
* 也就是说,只有请求是在/day49/user/xx 才可以获得这些cookie
*/
Cookie userCookie = new Cookie("user", "user_ck");
userCookie.setPath(req.getContextPath()+"/user");


Cookie adminCookie = new Cookie("admin", "admin_ck");
adminCookie.setPath(req.getContextPath()+"/admin");

resp.addCookie(userCookie);
resp.addCookie(adminCookie);
}
}

结果: UserServlet的请求路径是/day49/user/ck,只获得了/day49/user/下面的cookie

AdminServlet的请求路径是/day49/admin/ck,只获得了/day49/admin/下面的cookie


总结: 因为路径不同,cookie的范围不同,导致cookie取值范围不同.所以为了保证大部分时候都能取出cookie,好多cookie都将路径设置成了/,即所有请求都可以获得到该cookie

4.5 cookie保存中文问题

1 cookie的key就直接不能使用中文

2 value中文会乱码


cookie存储中文时如果乱码可以使用URLEncoder编码,URLDecoder解码

1
2
3
4
5
6
7
8
9
// 如果存储要中文,可以对中文编码
Cookie cookie2 = new Cookie(
URLEncoder.encode("用户名","UTF-8"),URLEncoder.encode("老王","UTF-8"));


// 取出时需要解码
String name = URLDecoder.decode(name1, "UTF-8");
String value = URLDecoder.decode(value1, "UTF-8");
System.out.println(name+"="+value );

知识点小问

1
2
3
1 cookie是用来干什么的?
2 谁创建,谁存储?
3 如何获取?是谁携带cookie?在哪里携带?

5 Session【重点】

Session也是用于保存会话数据的技术之一。Session是服务器端技术,用于记录用户的状态。

  • Session是基于Cookie

  • 服务器会为每一个会话创建一个session对象

  • 第一次访问服务器时,会自动创建session,还会创建cookie,并且将sessionid存储到cookie响应到浏览器, key=JSESSIONID , value=22jkhj3451jk345

  • 后续再向服务器发送请求,就会携带cookie,后台服务器就会根据JSESSIONID 找到session,就可以获得其中的数据

image-20221129161530328

5.1 创建session

1
2
3
4
5
// 创建session
// 如果这次请求没有session,其实就是请求cookie中没有JSESSIONID
// 就会创建session对象
HttpSession session = req.getSession( );
System.out.println("sessionid = " + session.getId() );

5.2 Session存取数据

1
2
3
4
5
6
7
HttpSession session = req.getSession( );
System.out.println("sessionid = " + session.getId() );

/**
* session可以当做域对象存取数据
*/
session.setAttribute("account","qwer");
1
2
3
4
5
6
7
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession( );
String account = (String) session.getAttribute("account");

System.out.println("Session2Servlet.account = " + account);
}

5.3 Session域和Request域

Request域中的数据,只在一次请求中有效

Session域中的数据,一次中会话中有效,无论是请求转发还是重定向

5.4 Session域数据清除

1
2
3
4
5
// 清除session数据
// session.removeAttribute();// 删除一个数据

// 销毁session
session.invalidate();

6 记录登录状态案例

需求: 项目中有登录页面(index.html),个人中心页面(info.html),添加用户页面(add.html)

实现目标: 只有登录才可以访问info.html,add.html

实现步骤:

1. **登录成功后将登录信息存入session**
1. **设置拦截器,拦截请求判断有没有登录信息**
1. **有登录信息就放行**
1. **没有登录登录信息就跳转至首页重新登录**
1. **在个人中心有退出登录,会销毁session**

登录成功后将登录信息存入session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@WebServlet("/login")
public class LoginServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");

// 假设用户名是admin,密码是123456
if ("admin".equals(username) && "123456".equals(password)) {
// 登录成功,将信息放入session
HttpSession session = req.getSession( );
session.setAttribute("user",username);
// 登录成功,跳转个人中心
resp.sendRedirect(req.getContextPath()+"/info.html");
} else {
// 登录失败,跳转登录页面
resp.sendRedirect(req.getContextPath()+"/index.html");
}
}
}

设置拦截器,拦截请求判断有没有登录信息

有登录信息就放行

没有登录登录信息就跳转至首页重新登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 该拦截器,拦截的路径
@WebFilter("/*")
public class LoginFilter implements Filter {

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

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;

String requestURI = req.getRequestURI( );

System.out.println("requestURI = " + requestURI);
if (requestURI.contains("info") || requestURI.contains("add")) {
HttpSession session = req.getSession( );
Object user = session.getAttribute("user");
if (user == null) {
resp.sendRedirect(req.getContextPath()+"/index.html");
return;
}
}
// 放行
chain.doFilter(req,response);
}

@Override
public void destroy() {

}
}

退出登录,销毁session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 退出登录,销毁session
HttpSession session = req.getSession( );
session.removeAttribute("user");
session.invalidate();

// 回到首页
resp.sendRedirect(req.getContextPath()+"/index.html");
}
}

7 拦截器/过滤器(Filter)

拦截请求,可以使用@WebFilter指定拦截的路径


开发步骤

  • 创建类
  • 实现Filter接口
  • 重写方法(init(),doFilter(),destroy())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 执行拦截请求的方法
* @param request 处理请求
* @param response 处理响应的
* @param chain 拦截器链
* chain 放行请求到下一个拦截器,如果没有下一个拦截器,就放行到对应的资源
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

chain.doFilter(request,response);
}


在使用注解开发中,如果有多个拦截器,顺序是按照拦截器的名字的首字母顺序

应用场景:

  1. 拦截请求,判断用户登录
  2. 拦截请求,设置编码格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@WebFilter("/*")
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
}

@Override
public void destroy() {

}
}

day19

今日内容

0 复习昨日
1 session存储登录状态
2 使用拦截器改造
3 Ajax
4 FastJson

0 复习昨日

1 服务器的根路径: 项目名
项目名/login
谁能从服务器发出请求? 请求转发

浏览器的根路径:端口
谁能从浏览器发出请求?如果通过浏览器发请求?
a,form,ajax,重定向,手动在地址栏输入请求

2 cookie是谁创建? 后台服务器创建,通过响应返回浏览器,储存在浏览器
由谁携带? 后续每次向该服务器发请求时会在请求头中携带cookie
cookie保存时间:
-1 是关闭浏览器时销毁
0 直接销毁
具体秒数 指定时间销毁
cookie的路径是一种cookie作用范围,在该路径范围内可以取出cookie

3 session什么时候创建?
当需要使用session当域对象存储数据时,就getSession();获得session

4 session域有什么特点
session域在一次会话中有效,无论请求转发还是重定向都可以取出该数据

5 拦截器如何使用
创建类,实现Filter接口
重写方法init(),doFilter(),destroy()
doFilter()执行拦截

// 放行到下一个拦截器
chain.doFilter(req,resp);
// 没有拦截器,放行到对应的资源

/login —> LoginServlet,完成登录
/ck1 —> CookieServlet,
/index.html —> 页面

1 session存储登录状态

需求: 做到登录认证.

目前,服务器中有关于用户操作的几个Servlet,

  • UserLoginServlet –> /user/login
  • UserAddServlet —> /user/add
  • UserDeleteServlet –> /user/delete

这些Servlet有对应的映射路径,要保证只有登录后才可以进行注册/删除

实现思路

  • 执行登录时,如果登录成功,将登录信息放入session
  • 在真正执行注册/删除等功能前,先判断有没有登录
  • 其实就是判断session中有没有登录的信息
  • 执行退出时,销毁session

登录,登录成功放入session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@WebServlet("/user/login")
public class UserLoginServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

String username = req.getParameter("username");
if ("admin".equals(username)) {
// 登录成功,信息放入session
HttpSession session = req.getSession( );
session.setAttribute("username",username);
System.out.println("UserLoginServlet...登录成功" );
} else {
System.out.println("UserLoginServlet...登录不成功,用户名或密码错误" );
}

}
}

添加用户,执行前判断登录状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@WebServlet("/user/add")
public class UserAddServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 在执行功能前,先判断有没有登录
HttpSession session = req.getSession( );
Object username = session.getAttribute("username");
if (username != null) {
System.out.println("已经登录过,可以执行UserAddServlet...添加用户" );

} else {
System.out.println("没有登录,没有权限操作!!" );
}


}
}

删除用户,执行前判断登录状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet("/user/delete")
public class UserDeleteServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession( );
Object username = session.getAttribute("username");
if (username != null) {
System.out.println("已经登录过,可以执行UserDeleteServlet...删除用户" );
} else {
System.out.println("没有登录,没有权限操作!!" );
}
}
}

退出登录,销毁登录状态

1
2
3
4
5
6
7
8
9
10
11
@WebServlet("/user/logout")
public class UserLogoutServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession( );
session.invalidate();

System.out.println("退出登录!销毁登录状态!" );
}
}

2 使用拦截器改造

场景: 服务器中有很多处理请求的servlet,项目要求有登录认证.所以我们每个Servlet执行功能前都要对登录信息判断,那就会有很多次登录判断的代码编写,重复性很高!


因为以上操作不高效,代码重复率太高! 解决方案: 拦截器

  • 使用拦截器,拦截请求,对请求进行判断,如果有登录信息就放行
  • 没有登录信息就响应到首页,提示

改造后面的Servlet,不再进行判断session

1
2
3
4
5
6
7
8
@WebServlet("/user/add")
public class UserAddServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("已经登录过,可以执行UserAddServlet...添加用户" );
}
}

添加拦截器,执行拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.qf.session;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
@WebFilter("/user/*")
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) request;
String uri = req.getRequestURI( );

// 将登录请求放行
if (uri.contains("/login")) {
chain.doFilter(request,response);
return;
}

// 其他请求判断登录信息
HttpSession session = req.getSession( );
Object username = session.getAttribute("username");
if (username != null ) {
// 不为空,就说明登录,就放行
chain.doFilter(request,response);
} else {
System.out.println("没有登录,不能操作,没有权限!!" );
// 跳转登录页面,让其重写登录!
// 也可以跳转404,提示没有权限
}

}

@Override
public void destroy() {}
}

LoginServlet,LogoutServlet和之前一样,不用改动

3 Ajax

3.1 介绍

Ajax即Asynchronous Javascript And XML(异步JavaScript和XML

使用Ajax技术网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面,这使得程序能够更快地回应用户的操作。

3.2 语法

使用JavaScript原生操作ajax很麻烦,我们一般使用JQuery封装好的ajax操作,很简单

(jquery对象等价于$)

1
2
3
4
5
6
7
$.get(url,[data],[function],[type]);
/* 就可以向服务器发送一个get请求
url: 服务器地址
data: 向服务器发送的数据,json形式 {k:v}
function: 请求成功的回调函数,是后台服务器返回的数据
type: 返回的内容的格式
*/
1
2
3
4
5
6
7
$.post(url,[data],[function],[type]);
/* 就可以向服务器发送一个post请求
url: 服务器地址
data: 向服务器发送的数据,json形式 {k:v}
function: 请求成功的回调函数,是后台服务器返回的数据
type: 返回的内容的格式
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$.ajax({
url:"路径",
type:"GET", // 获得POST,默认是get
data:""|{}, // data是发送到服务器的数据,可以是字符串,也可以对象
// "username=admin&password=123456"
// {username:"admin",password:"123456"}
contentType: "application/x-www-form-urlencoded", // 默认,适合大多数情况
// 发送到服务器的内容的格式,也可以改成application/json
dataType: "json",// 服务器返回的数据类型格式,不设置会自动识别
success:function(ret){
// 向服务器请求成功,就会此处,成功回调函数
// ret就是服务器返回的数据
},
error: function(){
// 向服务器请求失败,就会此处,失败回调函数
}
});

3.3 测试

3.3.1 $.get

需求: 分别使用$.get , $.post , $.ajax发送请求,后台servlet接收请求,并做出响应


第一步: 项目得引用jquery.js

image-20221130113404727

第二步: 写页面,页面中使用ajax发请求

1
2
3
4
5
6
7
8
<button onclick="getRequest()">发送$.get请求</button> <br><br><br>
<script src="/jquery-2.1.0.js"></script>
<script>
function getRequest(){
$.get("http://localhost:8080/ajax/req",{username:"gegege"},function (ret){
console.log("ret",ret)
});
}

第三步: 后台接收请求,并响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@WebServlet("/ajax/req")
public class AjaxGetServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 接收参数
String username = req.getParameter("username");
System.out.println("$.get发送的数据: username = " + username );

// 可以处理业务逻辑
// ...

// 响应
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter( );
out.write("这是后台的响应~~~");
}
}


第四步: 将数据响应给回调函数

3.3.2 $.post

前端发请求

1
2
3
4
5
6
function postRequest(){
$.post("/ajax/req",{password:"12345"},function (ret){
console.log(ret.code)
console.log(ret.msg)
})
}

后台接收请求并响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@WebServlet("/ajax/req")
public class AjaxReqServlet extends HttpServlet {

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

String password = req.getParameter("password");
System.out.println("$.post发送的数据: password = " + password );

// 直接响应给浏览器的就是json
resp.setContentType("application/json;charset=utf-8");

PrintWriter out = resp.getWriter( );
// out.write("这是后台的响应~~~");
// 一般响应一个JSON数据
out.write("{\"code\":200,\"msg\":\"POST请求成功\"}");
}
}
3.3.3 $.ajax

前端发请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ajaxRequest(){
$.ajax({
url:"/ajax/req2",
// data:"username=aaaaa&password=AAAA", // 数据格式是字符串形式
data:{username:"root",password: "ROOT"}, // 数据格式是对象形式
type:"POST", // 方式是get或者post,不写默认是get
success:function (ret){
if (ret.code == 200) {
alert(ret.msg);
}
},
error:function (){
alert("服务器正忙,请稍后再试~")
}
})
}

后端接收请求,并作出响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@WebServlet("/ajax/req2")
public class AjaxReq2Servlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 接收参数
String username = req.getParameter("username");
String password = req.getParameter("password");

System.out.println("$.ajax-GET发送的数据: username = " + username+",password = " + password );

// 可以处理业务逻辑
// ...

// 直接响应给浏览器的就是json
resp.setContentType("application/json;charset=utf-8");

PrintWriter out = resp.getWriter( );
// out.write("这是后台的响应~~~");
// 一般响应一个JSON数据
out.write("{\"code\":200,\"msg\":\"请求成功\"}");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 接收参数
String username = req.getParameter("username");
String password = req.getParameter("password");

System.out.println("$.ajax-POST发送的数据: username = " + username+",password = " + password );

// 可以处理业务逻辑
// ...

System.out.println(1/0 );

// 直接响应给浏览器的就是json
resp.setContentType("application/json;charset=utf-8");

PrintWriter out = resp.getWriter( );
// out.write("这是后台的响应~~~");
// 一般响应一个JSON数据
out.write("{\"code\":200,\"msg\":\"请求成功\"}");
}
}

ps: 以上doGet和doPost代码是一模一样,其实可以省略这么写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@WebServlet("/ajax/req2")
public class AjaxReq2Servlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 接收参数
String username = req.getParameter("username");
String password = req.getParameter("password");

System.out.println("$.ajax-POST发送的数据: username = " + username+",password = " + password );

// 可以处理业务逻辑
// ...

System.out.println(1/0 );

// 直接响应给浏览器的就是json
resp.setContentType("application/json;charset=utf-8");

PrintWriter out = resp.getWriter( );
// out.write("这是后台的响应~~~");
// 一般响应一个JSON数据
out.write("{\"code\":200,\"msg\":\"请求成功\"}");
}
}

3.4 响应json问题

使用方式1: 后台响应json字符串,前端需要解析为json对象再使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@WebServlet("/ajax/get")
public class AjaxGetServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 接收参数
String username = req.getParameter("username");
System.out.println("$.get发送的数据: username = " + username );

// 可以处理业务逻辑
// ...

// 响应
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter( );
// 响应JSON字符串
out.write("{\"code\":200,\"msg\":\"请求成功\"}");
}
}
1
2
3
4
5
6
7
8
  $.get("http://localhost:8080/ajax/get",{username:"gegege"},function (ret){
// 如果后台返回的是JSON字符串,可以使用解析方法,解析为JSON对象
var json = JSON.parse(ret);
console.log(json)
console.log(json.code)
console.log(json.msg)
});
}

使用方式2: 后台设置响应内容为json,前端直接使用json对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@WebServlet("/ajax/get")
public class AjaxGetServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 接收参数
String username = req.getParameter("username");
System.out.println("$.get发送的数据: username = " + username );

// 可以处理业务逻辑
// ...

// 直接响应给浏览器的就是json
resp.setContentType("application/json;charset=utf-8");

PrintWriter out = resp.getWriter( );
// 响应一个JSON数据
out.write("{\"code\":200,\"msg\":\"请求成功\"}");
}
}
1
2
3
4
5
6
7
8
  $.get("http://localhost:8080/ajax/get",{username:"gegege"},function (ret){
console.log("ret",ret)
console.log(typeof ret) // 获得数据类型,object
console.log(ret.code); // 后台返回的json,可以直接使用
console.log(ret.msg);

});
}

3.5 BUG: JS引入失败

因为服务器部署时没有将js文件加载,

  • 删除target文件夹
  • 修改tomcat配置为

image-20221130114848855

4 案例

4.1 注册

需求: 注册时,写完用户名,提示该用户名是否存在可否注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
思路:
1 前端页面,输入框
2 当失去焦点时触发函数
3 函数内发出ajax请求,要携带输入框的数据到服务
7 回调函数接收到服务器的数据
根据状态码做出对应的dom操作
后端
4 接收请求,接收请求中的数据
5 service --> dao ---> jdbc
6 返回数据,如果数据库查到数据,说明已经注册过,响应500
如果数据库没有查到,说明没有注册过,响应200
数据库
别忘了加入mysql驱动,数据库连接池
model+servlet+service(impl)+dao(impl)

4.2 回显数据

需求: 展现数据时,在输入完用户id后,下方的个人信息内容直接自动补全

1
2
3
4
5
6
7
8
9
10
11
整个思路: 通过id查用户,返回一个用户对象,在前端展现
前端
1 页面表格,很多个输入框
2 输入完id,触发失去焦点函数
3 发送ajax请求,将id发送服务器
6 回调函数接收服务器返回的数据,操作dom补全表格数据

后端
4 接收请求,接收数据
servlet --> service --> dao
5 将查询的结果响应回ajax

5 FastJson

以后工作中前后端数据的交互都是以JSON形式.

1
2
3
4
5
{
code:200,
msg:"OK",
data:{}
}

再封装一个java对象,对应与json格式

1
2
3
4
5
6
7
8
public class ResultData {

private int code;
private String msg;
private Object data;

// set get..
}

目前使用手动拼接json字符串,很麻烦,不好用!!


FastJson专业也用于转换JSON(阿里巴巴开源)

  • 字符串转json
  • 对象转json
  • 集合转json
  • json转字符串,对象,集合,数组等

项目中引入依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>

在需要转json的地方使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.qf.ajax;

import com.alibaba.fastjson.JSON;
import com.qf.model.User;
import com.qf.service.UserService;
import com.qf.service.impl.UserSericeImpl;
import com.qf.util.ResultData;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
@WebServlet("/user")
public class UserFindServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获得请求数据
String idStr = req.getParameter("id");
int id = Integer.parseInt(idStr);

// 调用业务层查询数据
UserService userService = new UserSericeImpl();
User user = userService.findUserById(id);

// 封装返回给前端的数据
ResultData resultData = new ResultData( );
resultData.setCode(200);
resultData.setMsg("OK");
resultData.setData(user);
// 使用工具类转json字符串
String jsonStr = JSON.toJSONString(resultData);
// 响应
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter( );
out.write(jsonStr);
}
}

day20

今日内容

0复习

复习

这一阶段: javaweb,学习写项目

  • 前端
  • 后端
  • 数据库

数据库

  • SQL语句
  • 动态SQL
  • DBUtil
    • JDBC五大步骤

前端: 展现数据和收集数据

  • 展现数据
    • 数据何时加载
      • 情况1: 页面加载时去查数据
      • 情况2: 点击某些按钮(a标签,form表单)
      • 其他情况
    • 通过什么手段查数据
      • 核心思想就是通过http请求
      • a标签,href=”路径”
      • form,action=”路径”
      • $.get,$.post,$.ajax其中的url=”路径”
      • ifram src=”路径”
    • 数据又如何展现
      • 前后端交互使用json.后端返回json
      • 从json中把数据取出,通过操作dom,展现数据
  • 收集数据
    • 主要是form
    • 发请求到后台

后端

  • maven : 项目管理工具

    • 管理项目结构

      1
      2
      3
      4
      5
      6
      7
      8
      9
      |-项目名
      |---src
      |------main
      |---------java
      |---------resources
      |---------webapp
      |------test
      |---pom.xml
      |---target
    • 管理项目依赖

      • 导包
  • tomcat

    • 服务器,运行web项目的容器
    • idea关联tomcat
    • 部署项目到tomcat
    • 运行
    • 访问(按照tomcat关联和部署时候的路径)
    • 访问出现404
      • 检查tomcat路径是否正确
      • 检查访问页面是不是不存在
      • 检查访问请求是不是没有对应的url-partten映射路径
      • 检查部署target中是不是没生效
      • 清空idea缓存重启idea
      • 重新打包部署
  • servlet

    • servlet是可以运行在服务器上的一个程序
    • servlet主要作用: 接收请求,做出响应
    • 前端发送get请求,重写doGet(),前端发送post请求,重写doPost()
    • 使用request对象接收请求
    • 使用response对象做出响应
  • 关于request

    • 可以接收请求数据 req.getParameter(“name”)
    • 可以当做域对象,存取数据(在一次请求转发的时候,请求域中的数据可以共享)
    • 请求转发(服务器的内部动作,可以将一个请求转发到另外 一个servlet),还可以转发到页面
      • 路径不变,服务器动作,是一次请求
  • 关于response

    • 可以向浏览器响应一些内容
      • PrinterWriter out = resp.getWriter()
      • out.writer(“xxx”);
      • 一般用于响应json
    • 重定向可以将一个请求重新到另外 一个servlet),还可以重新跳转到页面
      • resp.sendRedirect()
      • 路径会变,浏览器动作,是两次请求

写项目的架子: mvc思想和三层架构

  • servlet : 控制层,跟前端交互,控制接收请求,调用业务层处理业务,做出响应
  • service: 处理业务逻辑
    • impl
  • dao : 处理数据库
    • impl

要复习

框架标签

dom操作(获得dom,dom取值赋值)

js/jq函数+事件

前端F12调试工具

1
2
3
4
5
text("<span style='color:red'>sss</span>") 添加的内容全部是字符串
html("<span style='color:red'>sss</span>") 如果添加的有标签时,会解析成对应的标签效果
text(),html()它是给开闭标签之间设置内容,如果之前有内容会覆盖
------
append("<span style='color:red'>sss</span>"),在开闭标签之间内容的后面追加,且会解析为标签效果

day21

今日内容

0 复习昨日

1 layui

0 引言

我们已经学完

  • Java基础

  • web服务器(tomcat+servlet)

  • 数据库,jdbc

  • html,css,js,jq

  • 项目管理工具maven


以上这些技术,已经可以写项目,写B/S项目

写页面太慢了,有点丑

所以要使用现成的,提供好的,前端组件或模块 —> 拿来即用

Layui

1 介绍

layui(谐音:类 UI) 是一套开源的Web UI解决方案,采用自身经典的模块化规范,并遵循原生HTML/CSS/JS的开发方式,常适合网页界面的快速开发。layui 区别于那些基于MVVM 底层的前端框架,它更多是面向后端开发者,无需涉足前端各种工具,只需面对浏览器本身,让一切所需要的元素与交互。

2021年9月,layui 官网发布公告称,layui 官网 2021 年 10 月 13 日 进行下线,届时,包括新版下载、文档和示例在内的所有框架日常维护工作,将全部迁移到 Github 和 Gitee。


虽然没有官网,但是”网友”都自己又重新部署了一些网站,内容是layui一模一样

Layui - 经典开源模块化前端 UI 框架 (winxapp.cn)

Layui - 经典开源模块化前端 UI 组件库 (layuion.com)

通过官网得知,layui框架的主要内容是[页面元素],[内置模块]两部分

  • 页面元素: 理解为一些静态html+css样式组件,(拿走即用)
  • 内置模块
    • layui 定义了一套更轻量的模块规范
    • 将一些特殊功能,比如一些动态效果,封装成了一个一个的函数,一个功能就是一个模块,每个模块有对应的名字
    • 用的时候需要加载这些函数或者说模块,然后再使用对应的功能

2 环境搭建

2.1 下载

Layui - 经典开源模块化前端 UI 组件库 (layuion.com)

image-20221202094709019

2.2 解压

将上一步下载的压缩包,解压得到如下结构文件

1
2
3
4
5
6
|-layui
|---css
|------modules
|------layui.css // 核心样式文件
|---font
|---layui.js // 核心js库

2.3 项目搭建

开发前端页面,工具可以使用HBuilder,VSCode,IDEA.

为了配合后面写项目,今天就使用IDEA来开发

2.3.1 创建javaweb项目
  • 创建maven-web项目
  • 暂时不用导入依赖(因为不写java代码)
  • 配置tomcat,部署项目
2.3.2 导入layui资源

静态资源(html/css/js/各种图片)放在项目webapp下

image-20221202100026861
2.3.3 页面中引入layui

只需要在页面中引入

  • layui.css
  • layui.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入layui.css核心样式 -->
<link href="/layui/css/layui.css" rel="stylesheet">
</head>
<body>

<!-- 在此处写主要内容 -->


<!-- 引入layui.js核心 -->
<script src="/layui/layui.js"></script>
</body>
</html>

3 入门使用

演示: 使用模块弹出层,使用页面样式button

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 引入layui.css核心样式 -->
<link href="/layui/css/layui.css" rel="stylesheet">
</head>
<body>

<!-- 在此处写主要内容 -->
<button type="button" class="layui-btn">一个标准的按钮</button>
<a href="" class="layui-btn">一个可跳转的按钮</a>

<!-- 引入layui.js核心 -->
<script src="/layui/layui.js"></script>
<!-- layui模块 -->
<script>
layui.use(['layer'], function(){
var layer = layui.layer;

layer.msg('Hello World - layui');
});
</script>
</body>
</html>

4 页面元素

页面元素: 理解为一些静态html+css样式组件,(拿走即用)

4.1 布局

layui有栅格系统,采用业界比较常见的 12 等分规则

布局容器

  • class=”layui-container” 水平居中,两边有留白
  • class=”layui-fluid” 铺满全屏

栅格系统要配合容器使用,先定义容器,容器中定义行,行内定义列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>布局</title>
<!-- 引入layui.css核心样式 -->
<link href="/layui/css/layui.css" rel="stylesheet">
</head>
<body>
<!-- 在此处写主要内容 -->
<!-- 1先写容器(layui-container : 水平居中) -->
<div class="layui-container" style="background-color: red;height: 300px">

<!--2 容器内写行
行 layui-row
-->
<div class="layui-row" style="background-color: #00f7de;height: 200px;">
<!--3 行内写列
layui-col-md*
总共12列
-->
<div class="layui-col-md9" style="background-color: #01aaed">
9/12
</div>
<div class="layui-col-md3" style="background-color: #009688">
3/12
</div>
</div>

</div>

<hr>

<!-- 1先写容器
layui-fluid : 铺满全屏
-->
<div class="layui-fluid" style="background-color: red;height: 300px">

<!--2 容器内写行-->
<div class="layui-row" style="background-color: #00f7de;height: 200px;">
<!--3 行内写列-->
<div class="layui-col-md9" style="background-color: #01aaed">
9/12
</div>
<div class="layui-col-md3" style="background-color: #009688">
3/12
</div>
</div>

</div>
<hr>
<div class="layui-container" style="height: 300px">

<!--2 容器内写行
layui-col-space10 单元格间距
-->
<div class="layui-row layui-col-space10" style="height: 150px;">
<!--3 行内写列-->
<!-- <div class="layui-col-md4" style="background-color: #01aaed;height: 150px;">-->
<!-- 1/3-->
<!-- </div>-->

<!-- 偏移量layui-col-md-offset4 -->
<div class="layui-col-md4 layui-col-md-offset4" style="background-color: #009688;height: 150px;">
1/3
</div>
<!-- <div class="layui-col-md4" style="background-color: #01aaed;height: 150px;">-->
<!-- 1/3-->
<!-- </div>-->
</div>

<!--2 容器内写行-->
<div class="layui-row" style="background-color: #00f7de;height: 150px;">
<!--3 行内写列-->
<div class="layui-col-md8" style="background-color: #01aaed;height: 150px;">
2/3
</div>
<div class="layui-col-md4" style="background-color: #009688;height: 150px;">
1/3
</div>
</div>

</div>

<!-- 引入layui.js核心 -->
<script src="/layui/layui.js"></script>
</body>
</html>

4.2 图标

通过对一个内联元素(一般推荐用 i标签)设定 *class=”layui-icon”*,来定义一个图标,然后对元素加上图标对应的 font-class,即可显示出你想要的图标,譬如:

1
2
3
4
5
6
<div style="border: #01aaed 2px solid;height: 300px;width: 300px">

<i class="layui-icon layui-icon-face-smile" style="font-size: 30px; color: #FF5722;"></i>
<br>
<i class="layui-icon layui-icon-heart-fill" style="font-size: 60px; color: #009688;"></i>
</div>

4.3 按钮

任意HTML元素设定*class=”layui-btn”,建立一个基础按钮。通过追加格式layui-btn-{type}*的class来定义其它按钮风格。

1
2
3
4
5
6
7
8
<button class="layui-btn">按钮</button>
<a class="layui-btn">a标签,链接</a>
<i class="layui-btn">i标签</i>
<button class="layui-btn layui-btn-primary">按钮-原始</button>
<button class="layui-btn layui-btn-normal">按钮-normal</button>
<button class="layui-btn layui-btn-warm">按钮-warm</button>
<button class="layui-btn layui-btn-danger">按钮-danger</button>
<button class="layui-btn layui-btn-disabled">按钮-layui-btn-disabled</button>

4.4 表单

在一个容器中设定 class=”layui-form” 来标识一个表单元素块,通过规范好的HTML结构及CSS类,来组装成各式各样的表单元素,并通过内置的 form模块 来完成各种交互。

依赖加载模块:form (请注意:如果不加载form模块,select、checkbox、radio等将无法显示,并且无法使用form相关功能)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表单</title>
<link href="/layui/css/layui.css" rel="stylesheet">
</head>
<body>

<div class="layui-container">

<div class="layui-row">

<div class="layui-col-md6 layui-col-md-offset3">

<form class="layui-form" action="">
<div class="layui-form-item">
<label class="layui-form-label">输入框</label>
<div class="layui-input-block">
<input type="text" name="username" required lay-verify="required" placeholder="请输入标题" autocomplete="off"
class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码框</label>
<div class="layui-input-inline">
<input type="password" name="password" required lay-verify="required" placeholder="请输入密码" autocomplete="off"
class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">辅助文字</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">选择框</label>
<div class="layui-input-block">
<select name="city" lay-verify="required">
<option value=""></option>
<option value="0">北京</option>
<option value="1">上海</option>
<option value="2">广州</option>
<option value="3">深圳</option>
<option value="4">杭州</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">复选框</label>
<div class="layui-input-block">
<input type="checkbox" name="like[write]" title="写作">
<input type="checkbox" name="like[read]" title="阅读" checked>
<input type="checkbox" name="like[dai]" title="发呆">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">开关</label>
<div class="layui-input-block">
<input type="checkbox" name="switch" lay-skin="switch">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">单选框</label>
<div class="layui-input-block">
<input type="radio" name="sex" value="男" title="男">
<input type="radio" name="sex" value="女" title="女" checked>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">文本域</label>
<div class="layui-input-block">
<textarea name="desc" placeholder="请输入内容" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
</div>

</div>

</div>

<script src="/layui/layui.js"></script>
<script>
// 加载form模块,渲染出select、checkbox、radio等效果
layui.use('form',function (){
// 取出form对象
var form = layui.form;
})
</script>
</body>
</html>

4.5 导航

导航一般指页面引导性频道集合,多以菜单的形式呈现,可应用于头部和侧边,是整个网页画龙点晴般的存在。面包屑结构简单,支持自定义分隔符。


千万不要忘了加载 element模块。虽然大部分行为都是在加载完该模块后自动完成的,但一些交互操作,如呼出二级菜单等,需借助element模块才能使用

通过对导航追加CSS背景类,让导航呈现不同的主题色

1
2
3
4
//如定义一个墨绿背景色的导航
<ul class="layui-nav layui-bg-green" lay-filter="">

</ul>
  • layui-bg-cyan(藏青)、layui-bg-molv(墨绿)、layui-bg-blue*(艳蓝)

水平、垂直、侧边三个导航的HTML结构是完全一样的,不同的是:

垂直导航需要追加class:layui-nav-tree
侧边导航需要追加class:layui-nav-tree layui-nav-side

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>导航</title>
<link href="/layui/css/layui.css" rel="stylesheet">
</head>
<body>
<ul class="layui-nav layui-bg-green layui-nav-tree layui-nav-side" lay-filter="">
<li class="layui-nav-item"><a href="">最新活动</a></li>
<li class="layui-nav-item layui-this"><a href="">产品</a></li>
<li class="layui-nav-item"><a href="">大数据</a></li>
<li class="layui-nav-item">
<a href="javascript:;">解决方案</a>
<dl class="layui-nav-child"> <!-- 二级菜单 -->
<dd><a href="">移动模块</a></dd>
<dd><a href="">后台模版</a></dd>
<dd><a href="">电商平台</a></dd>
</dl>
</li>
<li class="layui-nav-item"><a href="">社区</a></li>
</ul>
<script src="/layui/layui.js"></script>
<script>

// 【一定注意:加载element模块】
layui.use('element',function (){
var element = layui.element;
})
</script>
</body>
</html>

4.6 表格

现在使用的是table的样式,是静态效果,要想有动态效果,比如加载数据,表格排序需要使用table模块

代码: 看文档

5 内置模块

内置模块

  • layui 定义了一套更轻量的模块规范
  • 将一些特殊功能,比如一些动态效果,封装成了一个一个的函数,一个功能就是一个模块,每个模块有对应的名字
  • 用的时候需要加载这些函数或者说模块,然后再使用对应的功能

使用模块的语法

  • 要先加载模块,layui 通过 use 方法加载模块
  • 再获得对应的模块对象
  • 在use方法的回调中完成业务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
// 1 加载模块,可以一次加载多个模块,此时使用数组
layui.use(['form','layer','table'],function(){
// 2获得对应的模块对象
var form = layui.form;
var layer = layui.layer;
var table = layui.table;

// 3 完成一些业务
form.on('submit(formDemo)',function(){
return false;
})

layer.msg();
layer.confirm();
layer.alert();

table.render()
});
</script>

5.1 弹出层

弹出层,模块名layer

弹出层方法

  • layer.open(options)

  • layer.msg(content,options,end)

  • layer.alert(content,options,end)

  • layer.confirm(content,options,yes,end)

layer.confirm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script src="/layui/layui.js"></script>
<script>
// 1 加载模块
layui.use('layer',function (){
// 2 获得对象
var layer = layui.layer;

// 3 使用
// layer.msg("这是layer弹出")
layer.confirm("是否删除?",{
area: ['500px', '300px'],
title:"删除???",
skin:"layui-layer-molv",
shade: [0.5, '#393D49'],
shadeClose:false
},function (index){
console.log("确定删除")
// 将来发ajax请求,后台删除数据
layer.close(index);
},function (index){
console.log("取消删除")
layer.close(index);
});

})
</script>

layer.open(options)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
layer.open({
type:0, // 0 默认框
content:"这是一个open弹框",
title:"open",
area:['500px', '300px'],
yes:function (index){
console.log("确定...")
layer.close(index) // 关闭弹出层
},
cancel:function (index){
console.log("取消...")
layer.close(index)
}
})

layer.open(options) 弹出一个页面,例如添加页面

1
2
3
4
5
layer.open({
type:2, // 2 iframe框架层
content:"http://localhost:8080/02bd.html", // 框架内显示的页面
area:['500px','600px']
})

5.2 日期

模块名: laydate

  • 写一个input输入框,设置一个id
  • 加载模块,获得对象laydate
  • 使用laydate对象,渲染日期框效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>

<input id="test">

<script src="/layui/layui.js"></script>
<script>

layui.use('laydate',function (){
var laydate = layui.laydate;

laydate.render({
elem:"#test", // 绑定元素
type:"datetime", // 选择的日期类型
range:false, // 是否开启范围选择
format:"yyyy年MM月dd日" // 日期格式
})

})

</script>
</body>

5.3 表格

模块加载名称:table

用于在表格中对数据进行一系列动态化的操作

使用步骤

  • 写一个空table标签,设置id
  • 加载table模块,获得table对象
  • 开始渲染

5.3.1 入门演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表格</title>
<link href="/layui/css/layui.css" rel="stylesheet">
</head>
<body>

<div class="layui-container">
<div class="layui-row">
<div class="layui-col-md6 layui-col-md-offset3">
<table id="test"></table>
</div>
</div>
</div>


<script src="/layui/layui.js"></script>
<script>

layui.use("table",function (){
var table = layui.table;
table.render({
elem:"#test",
height:312,
url:"https://www.fastmock.site/mock/4aa2a624e009051fe726c20c2419c09b/layuidata/api/users",
page:true,
cols:[[
{field:"id",title:"编号",width:90,sort:true},
{field:"username",title:"用户名"},
{field:"sex",title:"性别"},
{field:"sign",title:"签名"},
{field:"experience",title:"经验"}
]]
})
})
/**
* 【非常重要】【非常重要】【非常重要】
* 1 table渲染数据的前提,后台返回的数据格式一定是json
* 2 且json格式必须是
* {
"code": 0,
"msg": "",
"count": 1000,
"data": [{}, {}]
}
,如果不是,那么就需要通过parseData来转换
3 code必须是0时,认为是成功,才会渲染出效果
*/

</script>
</body>
</html>

5.3.2 后台servlet接口

ps: 后台接口,指的是前后端交互对接的口子,接口.也就是指后端控制层代码.

需求: 前端使用layui.table模块,加载后台数据并在前端展现.


后台代码编写,就是三层架构(servlet+service+dao),返回一个json数据

三层架构的代码不再粘贴

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ResultData {

private int code;
private String msg;
private Object data;

public static ResultData fail(){
ResultData resultData = new ResultData( );
resultData.setCode(500);
resultData.setMsg("失败");
return resultData;
}

public static ResultData ok(){
ResultData resultData = new ResultData( );
resultData.setCode(200);
resultData.setMsg("成功");
return resultData;
}

public static ResultData ok(Object data){
ResultData resultData = new ResultData( );
resultData.setCode(200);
resultData.setMsg("成功");
resultData.setData(data);
return resultData;
}

// setter getter...
}

5.3.3 转换返回的数据格式

table 组件默认规定的数据格式为:

1
2
3
4
5
6
{
"code": 0, // 返回0才是成功
"msg": "",
"count": 1000, // 数据条数,分页中使用
"data": [{}, {}]
}

接口返回的数据格式并不一定都符合 table 默认规定的格式,比如我们自己的返回值

1
2
3
4
5
{
"code": 200, // 后台java返回200是成功
"msg":"",
"data": [{},{}]
}

那么需要借助 parseData 回调函数将其解析成 table 组件所规定的数据格式

1
2
3
4
5
6
7
8
9
10
11
12
13
table.render({
elem: '#demp'
,url: ''
,parseData: function(res){ //res 即为原始返回的数据
return {
"code": res.code == 200 ? 0 : -1, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.data.length, //解析数据长度
"data": res.data //解析数据列表
};
}
//,…… //其他参数
});

5.3.4 数据模板

table模板内,有一个参数templet,可以将后台返回的数据再转换成指定的数据返回给table

templet 函数,有一个参数 d(包含当前行数据及特定的额外字段)。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
table.render({
elem:"#test",
height:312,
url:"http://localhost:8080/user/list",
page:true,
cols:[[
{field:"id",title:"编号",width:90,sort:true},
{field:"username",title:"用户名",align:"center"},
{field:"password",title:"密码"},
{field:"phone",title:"手机号"},
{field:"createTime",title:"注册时间",templet:function (d){
// console.log("d ==>" ,d);
let now = new Date(d.createTime);
let year = now.getFullYear();
let month = now.getMonth() +1;
let day = now.getDate();
return year+"年"+month+"月"+day+"日";
}},
{field:"money",title:"余额",sort: true},
{field:"sex",title:"性别",templet:function (d){
return d.sex == 1 ? '男':'女';
}}
]],

5.3.5 分页数据

table.render中设置 page:true,开启分页


page参数还可以设置为laypage模块中的参数分页组件文档 - Layui (layuion.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
table.render({
elem:"#test",
height:312,
url:"http://localhost:8080/user/list",
// page:true,
page:{
count: 2000,
limit: 2,
limits:[2,4,6,8,10],
first:"首页",
last:"尾页"
}
}

table.render在渲染数据时,发送请求时会自动拼接出分页参数

?page=1&limit=10 意思是指,从第一页展现,每页展现10条

5.3.6 操作按钮

layui.table提供两种方案来给表格提供操作按钮

  • 通过toolbar,指定按钮脚本
  • 通过templet,自己拼接

image-20221203105641196

方案一:toolbar

1)设置按钮脚本

2)使用toolbar引用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- 按钮脚本 
注意,id能使用短横线,btn-script
-->
<script type="text/html" id="btnScript">
<a class="layui-btn layui-btn-xs layui-btn-warm">编辑</a>
<a class="layui-btn layui-btn-xs layui-btn-danger">删除</a>
</script>

<script type="text/javascript" src="/layui/layui.js"></script>
<script type="text/javascript">

layui.use("table",function (){
var table = layui.table;
table.render({
elem:"#test",
height:312,
url:"http://localhost:8080/user/list",
cols:[[
// ....
{
title:"操作",toolbar:"#btnScript"
}
]],
})
})
</script>

方案二: templet自己拼接

1
2
3
4
5
6
7
{
title:"操作",templet:function (d){
let str = "<a class=\"layui-btn layui-btn-xs layui-btn-warm\">编辑</a>";
str += " <a class=\"layui-btn layui-btn-xs layui-btn-danger\">删除</a>";
return str;
}
}

5.3.7 操作按钮事件

layui有自己的事件处理方式

  • 给table标签设置过滤器 lay-filter=”随意名”

    • <table class="layui-table" lay-filter="test"></table> 
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52

      - 设置事件源

      - 在按钮标签中设置 lay-event="自己随便写的事件名"

      - 绑定事件

      - table.on('事件(filter)')
      - table.on('toolbar(test)', function(obj){}

      ```html
      <table id="test" lay-filter="myTableFilter"></table>
      <script type="text/javascript">
      layui.use(["table","layer","jquery"],function (){
      var table = layui.table;
      var layer = layui.layer;
      var $ = layui.jquery;
      table.render({
      elem:"#test",
      height:312,
      url:"http://localhost:8080/user/list",
      page:true,
      cols:[[
      {field:"id",title:"编号",width:90,sort:true},
      // ....
      {
      title:"操作",templet:function (d){
      let str = "<a class=\"layui-btn layui-btn-xs layui-btn-warm\" lay-event='edit'>编辑</a>";
      str += " <a class=\"layui-btn layui-btn-xs layui-btn-danger\" lay-event='delete'>删除</a>";
      return str;
      }
      }
      ]]
      // 给tool,即操作按钮绑定事件
      table.on('tool(myTableFilter)',function (obj){
      // console.log("obj ==>" , obj) // obj 是事件触发的对象,内含当前行数据以及自定义事件名
      if (obj.event == 'edit') {
      // 发送ajax请求
      // $.ajax()
      layer.msg("更新");
      } else if (obj.event == "delete") {
      layer.confirm("删除?",function (index){
      console.log("确定删除!")
      layer.close(index);
      },function (index){
      console.log("取消删除!")
      layer.close(index);
      });
      }
      })
      })
      </script>

需要注意的!!!!

table.on(‘tool(myTableFilter)’

tool是固定的事件名,不能乱改

myTableFilter是table标签中filter的名字


需要使用ajax发送请求的话,

1)项目中需要加入jquery.js文件

2)文件中需要使用script引用jquery文件

3)layui加载jquery模块

4)获得jquery对象就可以操作了

image-20221203114542470

day22

今日内容

0 复习昨日

1 layui-CRUD练习

0 复习昨日

1 开发流程

crud练习

  • 需求分析
    • 增删改查 + 分页 + 模糊查询
  • 数据库
    • tb_user
  • 技术
    • servlet+jdbc
    • mysql
    • html+css+js+jq+ajax+layui
  • 工具
    • idea,Navicat,浏览器

  • 搭建环境
    • 依赖
    • 工具类
    • 实体类
    • 配置文件
  • 前端
  • 后端

2 搭建环境

2.1 创建项目

image-20221205150239332 image-20221205150305605

image-20221205150425082

2.2 导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
</dependencies>

2.3 配置文件

jdbc.properties,放在main/resources下

1
2
3
4
5
6
7
8
9
10
11
12
13
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/java2217?useSSL=false
username=root
password=123456
# ----- 加入druid的一些连接配置
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 60000毫秒/1000等于60秒 -->
maxWait=5000

2.4 包结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|-src
|---main
|------java
|---------com.qf
|---------com.qf.model
|---------com.qf.servlet
|---------com.qf.service
|---------com.qf.service.impl
|---------com.qf.dao
|---------com.qf.dao.impl
|---------com.qf.util
|---------com.qf.filter
|------resources
|------webapp
-pom.xml

image-20221205151050184

2.5 实体类,工具类

1
2
3
4
5
6
7
8
9
10
11
public class User {

private int id;
private String username;
private String password;
private String phone;
private Date createTime;
private double money;
private int sex;
// setter getter
}

DBUtil

1
...

ResultData (符和前后端交互使用的json格式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.qf.util;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 封装的结果数据
* --> 符合layui默认模板格式
* {
* "code": 0,
* "msg": "",
* "count": 1000,
* "data": [{}, {}]
* }
*/
public class ResultData {

private int code; // 0是成功,其他都是不成功
private String msg;
private int count;
private Object data;

public ResultData() {
}


public static ResultData ok(Object data) {
ResultData resultData = new ResultData( );
resultData.setCode(0);
resultData.setMsg("成功");
resultData.setData(data);

return resultData;
}

public static ResultData ok(Object data,int count) {
ResultData resultData = new ResultData( );
resultData.setCode(0);
resultData.setMsg("成功");
resultData.setData(data);
resultData.setCount(count);
return resultData;
}

public static ResultData fail() {
ResultData resultData = new ResultData( );
resultData.setCode(-1);
resultData.setMsg("失败");
return resultData;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}
}

2.6 拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.qf.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 编码格式+内容类型过滤器
*/
@WebFilter("/*")
public class EncodingContentTypeFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

request.setCharacterEncoding("UTF-8");
// 直接固定,每个响应都是json
response.setContentType("application/json;charset=UTF-8");

// 放行
chain.doFilter(request,response);
}

@Override
public void destroy() {}
}

2.7 前端环境

  • layui资源
  • jquery资源

image-20221205152951506

3 功能开发

3.1 首页

需求: 打开index.html展现所有用户数据

前端页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<link href="/layui/css/layui.css" rel="stylesheet">
</head>
<body>


<div class="layui-container">

<div class="layui-row">
<div class="layui-col-md3 layui-col-md-offset5">
<h1>用户管理系统</h1>
</div>
</div>

<div class="layui-row">

<div class="layui-col-md10 layui-col-md-offset1">
<!-- 表格容器 -->
<table id="userTable"></table>
</div>
</div>
</div>


<script src="/layui/layui.js"></script>
<script>

layui.use(["table"],function (){
let table = layui.table;

table.render({
elem:"#userTable",
url:"/user/list",
cols:[[
{field: "id", title: "ID", sort: true,width:80},
{field: "username", title: "用户名",width:100},
{field: "password", title: "密码",width:100},
{field: "phone", title: "手机号",width:120},
{field: "money", title: "余额", sort: true,width:100},
{field: "sex", title: "性别", sort: true,width:80,templet:function(obj){
// console.log(obj.sex)
return obj.sex == 1? '男':'女';
}},
{field: "createTime", title: "注册时间", sort: true,templet:function (obj) {
let date = new Date(obj.createTime);
let year = date.getFullYear();
let month = date.getMonth() + 1;
if (month < 10) {
month = "0"+month;
}
let day = date.getDate();
if (day < 10) {
day = "0"+day;
}
return year+"-"+month+"-"+day;
}},
]]
})
})
</script>
</body>
</html>

后端代码

UserListServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.qf.servlet;

import com.alibaba.fastjson.JSON;
import com.qf.service.UserService;
import com.qf.service.impl.UserServiceImpl;
import com.qf.util.ResultData;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
@WebServlet("/user/list")
public class UserListServlet extends HttpServlet {

private UserService userService = new UserServiceImpl();

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 接收参数

// 调用业务层
ResultData resultData = userService.findAll();

// 响应
String jsonString = JSON.toJSONString(resultData);
resp.getWriter().write(jsonString);
}
}

UserService&UserServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface UserService {
ResultData findAll();
}
-----
public class UserServiceImpl implements UserService {

private UserDao userDao = new UserDaoImpl();

@Override
public ResultData findAll() {
List<User> list = userDao.findAll();

// 后续还要改动..

return ResultData.ok(list);
}
}

UserDao&UserDaoImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface UserDao {
List<User> findAll();
}

----------------------------------------
public class UserDaoImpl implements UserDao {
@Override
public List<User> findAll() {

List<User> list = DBUtil.selectAll("select * from tb_user", User.class);

return list;
}
}

3.2 添加

前端

  1. 设置添加按钮

  2. 绑定js事件,弹出层

  3. 弹出层内加载user-add.html

  4. user-add.html里面写表单样式

  5. 绑定提交按钮,点击提交触发ajax事件,发送请求及数据

  6. 在user-add.html关闭弹出层

  7. 在index.html中等弹出层销毁后,再重新加载数据

后端

  1. 接收数据,完成处理(servlet+service+dao)
  2. 作出响应

3.3 操作按钮

1
2
3
4
5
6
7
{
title:"操作",templet:function (d){
let str = "<a class=\"layui-btn layui-btn-xs layui-btn-warm\">编辑</a>";
str += " <a class=\"layui-btn layui-btn-xs layui-btn-danger\">删除</a>";
return str;
}
}

image-20221206092044027

3.4 删除

前端

  1. 点击删除按钮,弹出确认框

    1. 给table设置lay-filter,并且给两个操作按钮设置lay-event
    2. 给表格绑定工具栏事件 table.on(“tool(过滤器)”,function(obj){})
    3. 通过event判断具体事件
    4. layer弹出确认框
  2. 点击取消,不删除,关闭弹出层

  3. 点击确定,使用ajax发送请求,携带当前行的id,执行删除

  4. 关闭弹出层,重新加载表格数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 表格点击事件
    table.on("tool(userTableFilter)",function (obj) {
    // console.log("obj-event ==>",obj)
    if (obj.event == "delete") {
    layer.confirm("确定删除吗?",{icon: 3, title:'提示'},function (index){
    // 发请求执行删除
    // console.log("id ==>",obj.data.id)
    $.get("/user/delete",{id:obj.data.id},function (ret) {
    if (ret.code == 0) {
    layer.msg("删除成功!")
    } else {
    layer.msg("删除失败!")
    }
    });
    layer.close(index);
    table.reload("userTable")
    },function (index){
    layer.msg("取消删除!")
    layer.close(index);
    })
    }else if(obj.event == "edit") {
    // 更新操作...
    }
    });

后端

  1. 接收请求,接收id
  2. servlet <–> service <–> dao
  3. 响应

3.5 更新

3.5.1 更新弹出层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
else if(obj.event == "edit") {
layer.open({
type:2,
content:"user-edit.html",
// 新建user-edit.html页面(复制的user-add.html)
area: ["400px", "500px"],
title:"更新用户",
end:function (){
// 弹出层销毁时,重新加载表格数据
// js方法
// location.reload()
// layui方法
// 官方文档:https://layuion.com/docs/modules/table.html#reload
table.reload("userTable")
}
})
}

后续还要改动上面代码,现在只是先弹出更新层

3.5.2 更新页面回显数据

弹出层出现后,在表单内回显数据

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
else if(obj.event == "edit") {
layer.open({
type:2,
content:"user-edit.html",
area: ["400px", "500px"],
title:"更新用户",
success:function(layero, index){
// 弹出层提供一个方法,可以获得弹出的那个层的dom对象
// 官方文档: https://layuion.com/docs/modules/layer.html#layer.getChildFrame
let body = layer.getChildFrame("body", index);
// body是user-edit.html页面的内容
let userData = obj.data;
body.find("input[name=username]").val(userData.username)
body.find("input[name=password]").val(userData.password)
body.find("input[name=phone]").val(userData.phone)
body.find("input[name=money]").val(userData.money)
let date = new Date(userData.createTime);
let year = date.getFullYear();
let month = date.getMonth() + 1;
if (month < 10) {
month = "0"+month;
}
let day = date.getDate();
if (day < 10) {
day = "0"+day;
}
let createTime = year+"-"+month+"-"+day;
body.find("input[name=createTime]").val(createTime)
// 根据性别判断,给标签添加checked属性
// checked=true,默认选中
// console.log("sex = ",userData.sex)
/**
* 以下代码,并没有给form中radio设置上选择效果,为什么?
* 表单中,select、checkbox、radio需要layui来渲染完成,即页面加载完,渲染效果已经完成
* 所以后续再去改动效果,没有直接生效,怎么解决?
* 设置定时,等这边回显完,user-edit再渲染
*/
body.find('input[name=sex][value=1]').attr('checked',userData.sex == 1 ? true : false);
body.find('input[name=sex][value=2]').attr('checked',userData.sex == 2 ? true : false);

},
end:function (){
// 弹出层销毁时,重新加载表格数据
// js方法
// location.reload()
// layui方法
// 官方文档:https://layuion.com/docs/modules/table.html#reload
table.reload("userTable")
}
})
}

user-edit.html

1
2
3
4
5
6
7
8
9
10
11
12
layui.use(["form","laydate","jquery","layer"],function (){
let form = layui.form;
let laydate = layui.laydate;
let $ = layui.jquery;
let layer = layui.layer;

// 等index页面,完成input回显后,这边再渲染表单
setTimeout(function (){
form.render();
},50)
// 其他代码略...
});
3.5.3 执行更新

user-edit.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 绑定提交事件
form.on("submit(formDemo)",function (obj) {
$.ajax({
url:"/user/edit",
type:"POST",
data:obj.field,
success:function (ret) {
// console.log("ret ==>",ret)
if (ret.code == 0) {
// 把弹出层关闭
// 官方文档: https://layuion.com/docs/modules/layer.html#layer.getFrameIndex
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
} else {
layer.msg("添加失败")
}
},
error:function (){
layer.msg("服务器正忙!")
}
})
return false;
})

后台

servlet–service-dao

3.6 分页

前端

table.render在渲染数据时,设置page:true,开启分页发送

请求时会自动拼接出分页参数?page=1&limit=10 意思是指,从第一页展现,每页展现10条


也可以通过设置limit:n,来设置每页多少条数据

1
2
limit:3,
limits:[3,6,9,12],

后端

1
2
3
4
5
6
7
1 修改后台UserListServlet中代码,接收分页数据
2 修改后台UserService和UserServiceImpl的findAll(),添加参数int pageNo,int pageSize

3 修改后台UserDao和UserDaoImpl的findAll(),添加参数int pageNo,int pageSize
--------
发现分页的页数不对,需要后台查询数据条数返回给前端

数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
已知条件:
当前页 pageNo
页面大小 pageSize
*/
-- 第1页
select * from tb_user limit 0 , 3
-- 第2页
select * from tb_user limit 3 , 3
-- 第3页
select * from tb_user limit 6 , 3
-- 第n页
select * from tb_user limit (pageNo-1)*pageSize, pageSize

3.7 模糊查询

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="layui-row">
<div class="layui-col-md9 layui-col-md-offset2">
<form class="layui-form layui-form-pane" action="">
<div class="layui-form-item">
<div class="layui-input-inline">
<select name="field">
<option value="">---请选择---</option>
<option value="username">用户名</option>
<option value="phone">手机号</option>
</select>
</div>
<div class="layui-input-inline">
<input type="text" name="keyword" placeholder="请输入搜索关键词" class="layui-input">
</div>
<div class="layui-input-inline">
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="search">搜索</button>
<button class="layui-btn layui-btn-warm" lay-submit lay-filter="findAll">查询全部</button>
</div>
</div>
</form>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<script>
var table;
var $ ;
var layer;
var form;

layui.use(["table","jquery","layer","form"],function (){
table = layui.table;
$ = layui.jquery;
layer = layui.layer;
form = layui.form;

// 搜索框点击提交事件
form.on("submit(search)",function (obj) {
console.log(obj)
let field = obj.field.field;
let keyword = obj.field.keyword;
// 调用方法,渲染表格
renderTable(field,keyword);

return false;
})

// 查询全部点击
form.on("submit(findAll)",function (obj) {
// 调用方法,渲染表格
renderTable();
return false;
})

// 删除,更新回显,,代码省略

// 用户列表渲染
renderTable();
})


// 抽取出渲染表格的方法
function renderTable(field,keyword) {
// 只是查询全部,url应该是http://localhost:8080/user/list?page=1&limit=10
// 模糊查询,url应该是http://localhost:8080/user/list?field=xx&keyword=xx&page=1&limit=10

let url = "http://localhost:8080/user/list"
if (field != undefined && keyword != undefined && field != '' && keyword != '') {
url += "?field="+field+"&keyword="+keyword;
}

// console.log("url --> ",url)

table.render({
elem:"#userTable",
url:url,
page:true,
limit:3,
limits:[3,6,9,12],
cols:[[
{field: "id", title: "ID", sort: true,width:80},
{field: "username", title: "用户名",width:100},
{field: "password", title: "密码",width:100},
{field: "phone", title: "手机号",width:120},
{field: "money", title: "余额", sort: true,width:100},
{field: "sex", title: "性别", sort: true,width:80,templet:function(obj){
// console.log(obj.sex)
return obj.sex == 1? '男':'女';
}},
{field: "createTime", title: "注册时间", sort: true,templet:function (obj) {
let date = new Date(obj.createTime);
let year = date.getFullYear();
let month = date.getMonth() + 1;
if (month < 10) {
month = "0"+month;
}
let day = date.getDate();
if (day < 10) {
day = "0"+day;
}
return year+"-"+month+"-"+day;
}},
{
title:"操作",templet:function (d){
let str = "<a lay-event=\"edit\" class=\"layui-btn layui-btn-xs layui-btn-warm\">编辑</a>";
str += " <a lay-event=\"delete\"class=\"layui-btn layui-btn-xs layui-btn-danger\">删除</a>";
return str;
}
}
]]
})
}
</script>

后端

1
2
3
4
5
6
主要是改动findAll()方法,传入pageNo,pageSize,field,keyword参数
所以设计了map,承载所有参数

-----
Dao层,要动态拼接sql (要判断)

数据库

1
select * from tb_user where ??? like '%???%'  limit x,y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 动态sql
select * from tb_user where phone like '%xxx%' limit x,y
select * from tb_user where 'phone' like '%1%' limit 0,3

-- 没有关键词
select * from tb_user limit 0,3
-- 有关键词
-- select * from tb_user where 'phone' like '%1%' limit 0,3
select * from tb_user where username like '%邱%' limit 0 , 3
select * from tb_user where phone like '%222%' limit 0 , 3

select * from tb_user limit 0 , 3

select count(*) count from tb_user
select count(*) count from tb_user where phone like '%222%'

day23

今日内容

0 复习昨日

1 了解项目开发流程

2 项目需求

3 设计数据库

4 编码

0 复习昨日

1 jdbc 五大步骤
1)加载驱动
2)获得连接
3)获得执行sql对象
4)执行sql
5)关流

2 Statement使用方式
自己写完整的sql语句,如果有参数,都要自己拼接sql
使用statement调用方法executeQuery(sql),executeUpdate(sql)

3 PreparedStatement使用方式
自己写sql语句,如果有参数,用?占位
conn.preparedStatement(sql)进行预处理
对?赋值
ps.executeUpdate(),executeQuery()

4 它俩区别
处理语句,需要自己写完整sql(需要拼接),容易sql注入
预处理语句,sql不需要拼接,避免sql注入

5 写出分页sql语句,假设每页5条,第三页的数据
select * from 表名 limit (pageNo - 1) * pageSize,pageSize
select * from 表名 limit 10,5

6 画出后台代码的执行流程(三层架构中的数据流转)

1 了解项目开发流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1.公司部门的组成
人事部门HR
技术部门(研发部/IT部/java组/h5组/c组/ui组/产品)
行政部门
财务部门
市场部门
运营部门
总经理
老板/董事/CEO

2.项目部人员的组成
各种开发人员: UI/前端/后端(java/c/Python/c++/android/Object-c)
测试/实施/运维

3.项目的来源(怎么接项目)
客户(人脉/广告效应/朋友推荐/...)

4.可行性方法分析

5.=======立项========
6.报价表以及项目周期

7.合同签订
(首付30%,中期(30%),后期(30%),交付使用,(10%))

8.人员分配
中等项目(2月) 1个UI 1个前端 4个后台(1Android 1IOS 2Java) 1个测试

9.=======项目开发=======
10.原型图(甲方/乙方),思维导图
11.需求文档-接口文档
12.技术选型,架构设计,数据库设计(*) 几张表 表中多少字段 字段约束 表和表之间的关系 ,产品设计
13.项目编码,单元测试
14.测试(测试环境 公司测试,内部测试)
15.产品使用说明书【专业】
16.上线,项目部署

2 项目需求

详见文档

3 数据库设计

设计一个独立的库: apartment2217

image-20221207113947865

建表(一个模块一张表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
-- ----------------------------
-- Table structure for activity
-- ----------------------------
DROP TABLE IF EXISTS `activity`;
CREATE TABLE `activity` (
`id` int(11) NOT NULL COMMENT '活动id',
`time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '活动时间',
`subject` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '活动主题',
`intr` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '活动内容介绍',
`address` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '活动地址',
`price` double(10,2) DEFAULT NULL COMMENT '活动费用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='活动表';

-- ----------------------------
-- Records of activity
-- ----------------------------
INSERT INTO `activity` VALUES ('1', '2022-11-20 17:19:21', '相亲', '单身青年相亲', '同乐公园', '100.00');


-- ----------------------------
-- Table structure for contract
-- ----------------------------
DROP TABLE IF EXISTS `contract`;
CREATE TABLE `contract` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`num` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '合同号',
`hid` int(11) DEFAULT NULL COMMENT '关联房屋id',
`lid` int(11) DEFAULT NULL COMMENT '关联租户id',
`time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '合同签订时间',
`startTime` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '合同生效起始时间',
`endTime` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '合同失效时间',
`totalMoney` double(255,0) DEFAULT NULL COMMENT '合同总金额',
`payType` int(11) DEFAULT NULL COMMENT '1月付 2半年付 3年付',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='合同表';

-- ----------------------------
-- Records of contract
-- ----------------------------
INSERT INTO `contract` VALUES ('1', '2022112001', '1', '1', '2022-11-20 16:49:00', '2022-11-21 16:49:02', '2022-11-22 16:49:05', '180', '1');

-- ----------------------------
-- Table structure for house
-- ----------------------------
DROP TABLE IF EXISTS `house`;
CREATE TABLE `house` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`address` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '房屋地址',
`floor` int(11) COLLATE utf8_bin DEFAULT NULL COMMENT '楼层',
`roomNum` int(11) DEFAULT NULL COMMENT '房间号',
`area` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '面积',
`dir` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '朝向',
`deco` int(11) DEFAULT NULL COMMENT '装修类型 1毛坯 2精装',
`air` int(11) DEFAULT NULL COMMENT '是否双气 1是 2否',
`price` double(10,2) DEFAULT NULL COMMENT '价格',
`rentStatus` int(11) DEFAULT NULL COMMENT '出租状态 1已出租 2未出租 3停止出租',
`image` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '房屋图片路径' ,
`addTime` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '添加时间',
`updateTime` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`status` int(11) DEFAULT NULL COMMENT '房屋状态 1正常 2已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='房屋表';

-- ----------------------------
-- Records of house
-- ----------------------------
INSERT INTO `house` VALUES ('1', '老代庄10号', 1, '102', '30平', '南', '1', '1', '1980.00', '2', null, '2022-11-23 10:46:08', '2022-11-23 00:00:00',1);
INSERT INTO `house` VALUES ('2', '航海路60号', 2, '209', '65平', '西', '2', '2', '2000.00', '3', null, '2022-11-23 10:48:44', '2022-11-23 00:00:00',1);
INSERT INTO `house` VALUES ('3', '二七万达', 1, '3', '56平', '东', '3', '1', '13000.00', '1', null, '2022-11-25 16:13:16', '2022-11-25 16:13:16',1);
INSERT INTO `house` VALUES ('4', '海为科技园', 1, '3', '200平', '北', '1', '2', '300.00', '3', null, '2022-11-25 16:13:13', '2022-11-25 16:13:13',1);
INSERT INTO `house` VALUES ('5', '台胞小区', 1, '8', '90平', '东北', '2', '1', '40.00', '1', null, '2022-11-25 16:13:12', '2022-11-25 16:13:12',1);

-- ----------------------------
-- Table structure for lessee
-- ----------------------------
DROP TABLE IF EXISTS `lessee`;
CREATE TABLE `lessee` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '租户姓名',
`tel` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '手机号',
`sex` int(11) DEFAULT NULL COMMENT '性别 1男 2女',
`np` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '籍贯',
`idCard` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '身份证号码',
`addTime` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户表';

-- ----------------------------
-- Records of lessee
-- ----------------------------
INSERT INTO `lessee` VALUES ('1', '张三', '110', '1', '郑州', '4101010110101', '2022-11-20 16:38:15');

-- ----------------------------
-- Table structure for logi
-- ----------------------------
DROP TABLE IF EXISTS `logi`;
CREATE TABLE `logi` (
`id` int(11) NOT NULL COMMENT '主键ID',
`name` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '后勤人员姓名',
`idCard` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '后勤人员身份证号',
`tel` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '后勤人员手机',
`sex` int(11) DEFAULT NULL COMMENT '1男 2女',
`addTime` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`salary` double(10,2) DEFAULT NULL COMMENT '工资',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='后勤人员表';

-- ----------------------------
-- Records of logi
-- ----------------------------

-- ----------------------------
-- Table structure for maintain
-- ----------------------------
DROP TABLE IF EXISTS `maintain`;
CREATE TABLE `maintain` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`hid` int(11) DEFAULT NULL COMMENT '关联房屋id',
`loid` int(11) DEFAULT NULL COMMENT '关联后勤人员id',
`time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '维修时间',
`result` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '维修结果',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='维修表';

-- ----------------------------
-- Records of maintain
-- ----------------------------

-- ----------------------------
-- Table structure for rent
-- ----------------------------
DROP TABLE IF EXISTS `rent`;
CREATE TABLE `rent` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`hid` int(11) DEFAULT NULL COMMENT '关联房屋id',
`lid` int(11) DEFAULT NULL COMMENT '关联租户id',
`price` double(10,2) DEFAULT NULL COMMENT '缴纳房租',
`payTime` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='房租表';

-- ----------------------------
-- Records of rent
-- ----------------------------
INSERT INTO `rent` VALUES ('1', '1', '1', '180.00', '2022-11-20 16:56:02');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`password` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`sex` int(11) COLLATE utf8_bin DEFAULT NULL COMMENT '1男 2女',
`realname` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '真实姓名',
`hiredate` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '入职时间',
`status` int(11) DEFAULT NULL COMMENT '1正常 2离职',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用户表';

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'admin', '123456', 1,'邱世举', '2022-11-22 16:22:54', '1');
INSERT INTO `user` VALUES ('2', 'zs', '123456', 2,'张三', '2022-11-22 16:23:12', '1');
INSERT INTO `user` VALUES ('3', 'ls', '123456',2, '李四', '2022-11-22 16:23:30', '1');

4 编码

4.1 前端脚手架

可以快速搭建一个前端界面,

  • X-admin(目前使用这个)
  • Layui mini

这两个脚手架都是基于Layui改造的


如果使用?

看上哪个效果,就赋值粘贴哪个页面,根据实际需求改动页面内容

4.2 环境搭建

  • 创建项目
  • 导入依赖
  • 创建对应包结构
  • 创建对应实体类
  • 工具类,配置文件
  • 加入前端静态资源
    • 在webapp下,创建static文件夹
    • 把前端脚手架资源拷贝到static下
    • 另外单独导入jquery.js
  • 配置tomcat
  • 部署项目
image-20221207154713480

4.3 登录页

  • 在webapp下创建login.html
  • 复制xadmin脚手架的代码
  • 改动css.js的引入路径
  • web.xml设置欢迎页

4.4 登录功能

前端

  1. 登录表单提交事件

  2. 获得用户名+密码数据

  3. 发ajax请求,到后台

  4. ajax的回调函数中判断是否登录成功

  5. 登录失败,弹框提示

  6. 登录成功,跳转页面到首页

后端

  1. 后台servlet接收请求 <–> service <–> dao
  2. 如果查询到,要存储登录状态(session)
  3. 响应

4.5 首页样式跳转

前端

  1. 登录成功后跳转首页

4.6 展现房屋数据

  1. 登录成功,跳转至首页后,立即展现房屋列表
  2. 是通过iframe的src指定路径,会自动发请求加载list页面
  3. list页面,通过layui-table渲染数据

5 模糊搜索

要实现的是多条件搜索,关键词和字段有多套(比如,地址字段对应地址的内容|楼层字段,对应楼层内容….)

  1. 使用json优化渲染表格的方法参数
  2. 使用table.render字段参数where,优化拼接路径
  3. 后台要根据关键词动态拼接sql

6 多表联查

业务需求: 查询合同时,需要关联查询房屋和租户,

1
select c.*,h.address,l.name from contract c, house h,lessee l where c.hid = h.id and c.lid = l.id

因为查询到的数据,不止有合同信息,还要关联房屋,租户信息,如何封装查询到数据?此时一个单独的合同类不能够展现全部数据,怎么解决?

实体类中展现的数据不够,就加?

加哪? 一般不直接加在原实体类,还是创建一个扩展类ContractVO

还有另外的一种思路:”分而治之”

分开一个一个查

  1. 先查询且只查询合同
  2. 根据上一步查询得到的结果房屋id,租户id
  3. 根据hid查房屋
  4. 根据lid查租户

7 下拉框回显

复选框/单选框选中是添加 checked

下拉框选中是添加 selected

8 批量删除

登录成功后,主页右上角人名应该是当前登录的人.

是login.html登录成功,跳转到index.html

需要在index.html展现登录成功的人名

人名数据在哪?人名在login.html登录成功的回调函数中

那么,数据如何从login.html传递到index.html?

  • 其中一个方案是cookie,注意是前端js操作cookie
  • 在login.html将数据存储到cookie
  • 在index.html从cookie中取出
  • 操作dom显示数据

官方文档JavaScript Cookies (w3school.com.cn)