目录
[一、Read 方法返回 -1 的问题](#一、Read 方法返回 -1 的问题)
[二、JDBC 优化](#二、JDBC 优化)
[1. 创建配置文件](#1. 创建配置文件)
[2. 创建工具类](#2. 创建工具类)
[3. 简化 JDBC 的步骤](#3. 简化 JDBC 的步骤)
[1. 创建 LoginServlet 类](#1. 创建 LoginServlet 类)
[2. 把登录功能的代码放到 LoginServlet 类](#2. 把登录功能的代码放到 LoginServlet 类)
[3. 创建LoginServlet 对象,调用service方法](#3. 创建LoginServlet 对象,调用service方法)
[1. 每个 servlet 的 service 方法都是一样的,如何优化?](#1. 每个 servlet 的 service 方法都是一样的,如何优化?)
[2. 如何再进一步优化 Servlet](#2. 如何再进一步优化 Servlet)
一、Read 方法返回 -1 的问题
在 上次的基础上,我们需要解决一下 read 读取到 -1 导致报错的问题
我们需要知道,为什么会读取到 -1?什么情况下会读取到 -1
read 方法返回 -1 有以下几种情况
-
客户端 Socket 关闭(Socket.close)
-
客户端关闭输出流,客户端在关流的时候,还多了一个往服务器写结束标记的动作,结束标记 -1
-
读取超时抛出异常,java 中的 Socket 默认没有超时限制
-
读取文件时,到了文件的末尾,表示没有数据可读,也会返回 -1
那么如何解决呢?
很简单,判断读取到的是不是 -1 ,如果是 -1 直接 return 不用往下执行了
二、JDBC 优化
当前数据库连接信息都写在了代码里面,相对于硬编码,如果以后连接信息更改了,比如数据库连接地址,数据库密码等,那么所有关于JDBC 获取数据库连接的代码都需要修改
解决方案:
生成配置文件
通过Properties类去解析配置文件
创建一个DBPropUtils工具类来获取配置文件的信息
1. 创建配置文件
配置数据库连接信息
2. 创建工具类
获取配置文件的数据库连接信息
java
package com.shao.Utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
public class DBPropUtil {
private static HashMap<String, String> propMap = new HashMap<String, String>();
static {
Properties prop = new Properties();
try {
// 读取数据库配置文件
// config 包名
// DBConnection.properties 配置文件名
prop.load(new FileInputStream("config" + File.separator + "DBConnection.properties"));
// 获取所有的键
Set<Object> keySet = prop.keySet();
Iterator<Object> it = keySet.iterator();
// 遍历 键的 Set 集合
while (it.hasNext()) {
// 把键转成字符串类型
String key = (String) it.next();
// 获取键对应的值
String value = prop.getProperty(key);
// 把键值对 添加到 map集合中
propMap.put(key, value);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 对外提供一个接口,通过key 获取对应的值
public static String getProp(String key) {
return propMap.get(key);
}
}
3. 简化 JDBC 的步骤
创建 数据库连接类 获取连接,释放资源的方法也可以放到这个类里,这样可以简化响应类的代码
java
package com.shao.Utils;
import java.sql.*;
public class DBConnectUtil {
/**
* 静态代码块,当类被加载时,就会执行代码块,且只执行一次
* */
static {
try {
// 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 对外提供的方法,获取数据库连接
*/
public static Connection getConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection(
DBPropUtil.getProp("url"),
DBPropUtil.getProp("user"),
DBPropUtil.getProp("password"));
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
/**
* 释放资源
*/
public static void releaseSource(Connection connection, PreparedStatement pstmt, ResultSet resultSet) {
// 释放连接
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
//关闭预编译对象
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
//关闭结果集
try {
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
在响应类中调用方法获取数据库连接
测试
三、修改密码
登录功能有了,我们再练习一个修改密码的功能
和登录功能类似,因为我们封装了 JDBC 的获取数据库连接的操作,简化了JDBC,所以获取连接只需要调用工具类的方法就可以了
java
Connection connection = null;
PreparedStatement pstmt = null;
int result = 0;
try {
// 3. 获取数据库连接
connection = DBConnectUtil.getConnection();
// 4. 获取可执行对象
// 定义 SQL 语句
String SQL = "UPDATE train.users SET password = ? WHERE account = ?";
pstmt = connection.prepareStatement(SQL);
// 设置占位符的值
String account = httpRequest.getRequestBodyParams().get("account");
String password = httpRequest.getRequestBodyParams().get("password");
pstmt.setString(1, password);
pstmt.setString(2, account);
// 5. 执行sql语句,获取结果
result = pstmt.executeUpdate();
// 6. 结果处理
responseDTO responseDTO = null;
if (result > 0) {
responseDTO = new responseDTO(200, null, "修改成功");
} else {
responseDTO = new responseDTO(201, null, "修改失败");
}
// 调用方法返回数据
send(JSON.toJSONBytes(responseDTO));
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 7. 释放资源
// 因为 更新,添加,删除 不需要结果集,所以不需要 resultSet,不用释放资源
DBConnectUtil.releaseSource(connection, pstmt, null);
}
优化返回数据
我们现在返回的数据是字符串类型的,然后转成字节数组,这样的话,数据响应到客户端还是字符串格式,不方便解析数据,所以需要把要响应的数据转成 JSON 格式
如何转成 JSON 格式呢?
- 添加第三方 jar 包
- 使用
转成 JSON 格式,然后转成字节数组,因为可以直接转成字节数组,为了方便,我们把响应方法里接收数据的参数也改成字节数组类型
创建修改密码的页面
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改密码</title>
<script src="../static/js/jquery-3.5.1.min.js"></script>
</head>
<body>
<div>
<div class="content">
<input type="text" class="text-input" name="account" placeholder="请输入账号">
<input type="text" class="text-input" name="password" placeholder="请输入新密码">
<button type="button" class="submit-button">修改密码</button>
</div>
<div class="msg">
<span class="success-msg"></span>
</div>
</div>
</body>
<style>
.content {
margin: 0 auto;
width: 300px;
}
.msg {
margin: 0 auto;
width: 300px;
}
.text-input {
width: 200px;
height: 30px;
margin: 10px;
padding: 5px;
border: 1px solid #ccc;
}
.submit-button {
width: 100px;
height: 30px;
margin: 10px;
padding: 5px;
border: 1px solid #ccc;
cursor: pointer;
border-radius: 5px;
box-shadow: 0 0 1px #ccc;
}
</style>
<script>
document.querySelector('.submit-button').onclick = function () {
console.log("点击了修改按钮")
let username = document.querySelector('input[name="account"]').value;
let password = document.querySelector('input[name="password"]').value;
let data = {
account: username,
password: password,
}
$.ajax({
url: 'http://127.0.0.1:8080/ChangePassword',
type: 'POST',
data: data,
success: function (data) {
console.log(data)
data = JSON.parse(data)
if (data.statusCode === 200) {
document.querySelector('.success-msg').innerHTML = data.msg;
} else {
document.querySelector('.success-msg').innerHTML = data.msg;
}
}
})
}
</script>
</html>
注意 ❗❗❗
因为修改密码的页面有引用到 static 文件夹下的 js 文件夹的 jquery 文件,当页面在游览器打开后,会自动请求 jquery 文件,当请求到达后端时,我们给请求资源的路径加了 webs/pages 的前缀,这样的话,就会在 webs/pages/static/js 文件夹下找 jquery 文件,路径不对,所以获取不到
解决方案是,只保留 webs/ 前缀即可,这样资源的路径就正确了
测试
在游览器的地址栏中输入 http://127.0.0.1:8080/pages/changePassword.html
因为删除了 pages 前缀,所以请求 pages 文件夹下的资源文件都需要加上 pages
四、优化响应动态资源
现在的结构是响应动态资源的代码都写在了响应类里,这样的话,当后面加了很多功能,响应类的代码就会很多,一是不好维护,二是不利于协同开发
那怎么解决呢?
解决方案是一个功能做成一个 servlet
1. 创建 LoginServlet 类
2. 把登录功能的代码放到 LoginServlet 类
java
package com.shao.Servlet;
import com.alibaba.fastjson2.JSON;
import com.shao.Utils.DBConnectUtil;
import com.shao.Utils.responseDTO;
import com.shao.net.HttpRequest;
import com.shao.net.HttpResponse;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class LoginServlet {
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet resultSet = null;
responseDTO responseDTO = null;
public void service(HttpRequest request, HttpResponse response) {
if (request.getRequestMethod().equals("GET")) {
doGet(request, response);
} else if (request.getRequestMethod().equals("POST")) {
doPost(request, response);
}
}
public void doGet(HttpRequest request, HttpResponse response) {
try {
// 3. 获取数据库连接
connection = DBConnectUtil.getConnection();
// 4. 获取可执行对象
String SQL = "select count(*) from train.users where account = ? and password = ?";
pstmt = connection.prepareStatement(SQL);
// 设置占位符的值
String account = request.getRequestBodyParams().get("account");
String pwd = request.getRequestBodyParams().get("password");
pstmt.setString(1, account);
pstmt.setString(2, pwd);
// 5. 执行sql语句,获取结果集
resultSet = pstmt.executeQuery();
// 6. 结果处理
if (resultSet.next() && resultSet.getInt(1) > 0) {
responseDTO = new responseDTO(200, null, "登录成功");
} else {
responseDTO = new responseDTO(201, null, "登录失败,请检查账号和密码");
}
//调用方法返回数据
response.send(JSON.toJSONBytes(responseDTO));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7. 释放资源
DBConnectUtil.releaseSource(connection, pstmt, resultSet);
}
}
public void doPost(HttpRequest request, HttpResponse response) {
responseDTO = new responseDTO(400, null, "不支持POST提交方法");
response.send(JSON.toJSONBytes(responseDTO));
}
}
3. 创建LoginServlet 对象,调用service方法
五、作业
1. 每个 servlet 的 service 方法都是一样的,如何优化?
2. 如何再进一步优化 Servlet
把Servlet 进一步划分为 Servlet 层,Service 业务逻辑层和 Dao 数据访问层