【学习笔记】手写 Tomcat 五
目录
一、优化 Servlet
创建一个抽象类
继承抽象类
二、三层架构
业务逻辑层
数据访问层
1. 在 Dao 层操作数据库
2. 调用 Dao 层,实现业务逻辑功能
3. 调用 Service 层,响应数据
测试
三、数据库连接池
1. 手写数据库连接池
2. 创建数据库连接池的类
3. 初始化连接池
4. 在 Dao 层获取数据库连接
5. 归还数据库连接
作业
1. 3w1h的方式,了解单例模式
2. 了解其他连接池
一、优化 Servlet
昨天,我们把响应动态资源的功能放到了 Servlet,一个 Servlet 就是一个功能
当 Servlet 多了之后,我们会发现,每个 Servlet 里都有 service 方法,那有什么方法可以简化呢?
这里就可以使用 Java 的三大特性之一 继承
创建一个抽象类
定义普通的 service 方法,和 doGet , doPost 抽象方法
继承抽象类
在每个 Servlet 中继承这个抽象类,然后就可以删除 service 方法了
二、三层架构
我们把功能都写在了 Servlet,这样有几个缺点,一是不够低耦合 高内聚,二是不方便协同开发等
我们可以使用三层架构来解决这个问题
三层架构是一种设计思想,分为表示层,业务逻辑层和数据访问层
表示层 Servlet:负责处理用户的请求和响应用户需要的数据
业务逻辑层 Service:负责业务上逻辑的处理,也是表示层和数据层之间的桥梁,
数据访问层 Dao:操作数据库,进行增删改查,并将结果传给业务逻辑层
三层架构区分层次的目的是为了 “高内聚,低耦合”。开发人员分工更明确
业务逻辑层
创建 UserService 接口,用于专门处理用户相关的业务逻辑,比如用户登录,注册,修改密码等
创建接口的实现类,在实现类里实现业务相关逻辑
为什么要创建接口,再创建实现类去实现接口呢?直接创建类,在类里实现业务逻辑不行吗?
当然可以,不过面向接口有很多优点。比如解耦,当实现类需要改变,不会影响 Servlet 层。还有规范化等优点
数据访问层
创建 UserDao 接口,并创建实现类,和业务逻辑层同理
在接口定义抽象方法
1. 在 Dao 层操作数据库
在实现类实现接口,并把 Servlet 层的关于数据库操作的代码放到实现类里
package com.shao.Dao.Impl;import com.shao.Dao.UserDao;
import com.shao.Utils.DBConnectUtil;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class UserDaoImpl implements UserDao {@Overridepublic int Login(String account, String password) {Connection connection = null;PreparedStatement pstmt = null;ResultSet resultSet = null;int result = 0;try {// 3. 获取数据库连接connection = DBConnectUtil.getConnection();// 4. 获取可执行对象String SQL = "select count(*) from train.users where account = ? and password = ?";pstmt = connection.prepareStatement(SQL);// 设置占位符的值pstmt.setString(1, account);pstmt.setString(2, password);// 5. 执行sql语句,获取结果集resultSet = pstmt.executeQuery();// 6. 结果处理if (resultSet.next() && resultSet.getInt(1) > 0) {// 表示可以查询到数据result = 1;}} catch (Exception e) {e.printStackTrace();} finally {// 7. 释放资源DBConnectUtil.releaseSource(connection, pstmt, resultSet);}// 返回结果给业务逻辑层return result;}
}
2. 调用 Dao 层,实现业务逻辑功能
在 Service 层调用 Dao 层,并实现业务逻辑功能
package com.shao.Service.Impl;import com.shao.Dao.Impl.UserDaoImpl;
import com.shao.Dao.UserDao;
import com.shao.Service.UserService;
import com.shao.Utils.responseDTO;public class UserServiceImpl implements UserService {// 创建一个UserDao的实现类对象private UserDao userDao = new UserDaoImpl();@Overridepublic responseDTO Login(String account, String password) {// 调用UserDao的Login方法int login = userDao.Login(account, password);// 判断是否登录成功if (login == 1) {return new responseDTO(200, null, "登录成功");} else {return new responseDTO(201, null, "账号或密码错误");}}
}
3. 调用 Service 层,响应数据
在 Servlet 层调用 Service 层,然后把业务逻辑层返回的数据响应到客户端
package com.shao.Servlet;import com.alibaba.fastjson2.JSON;
import com.shao.Service.Impl.UserServiceImpl;
import com.shao.Service.UserService;
import com.shao.Utils.responseDTO;
import com.shao.net.HttpRequest;
import com.shao.net.HttpResponse;public class LoginServlet extends BaseServlet {responseDTO responseDTO = null;public void doGet(HttpRequest request, HttpResponse response) {// 获取请求参数String account = request.getRequestBodyParams().get("account");String pwd = request.getRequestBodyParams().get("password");// 调用 Service 层的Login方法UserService userService = new UserServiceImpl();responseDTO = userService.Login(account, pwd);// 响应数据response.send(JSON.toJSONBytes(responseDTO));}public void doPost(HttpRequest request, HttpResponse response) {responseDTO = new responseDTO(400, null, "不支持POST提交方法");response.send(JSON.toJSONBytes(responseDTO));}
}
测试
三、数据库连接池
在测试的时候,我们可能会发现,第一次登录的时候,数据响应会比较慢,第二次之后就比较快了
这是因为第一次登录需要与数据库进行连接,第二次访问时,数据库已经准备好了,所以比较快
而且,当用户很多时,发起一次请求就需要建立一个数据库连接,不用了就断开连接,这样比较消耗资源,也比较耗时
那如何解决这个问题呢?
解决方案是使用数据库连接池,在系统空闲的时候,创建好数据库连接,当用户发起请求后就从连接池取出一条连接使用,当用完了再把连接放到连接池中,这样就节约了资源,也提升了效率
数据库连接池可以使用市面上的,比如Druid,Apache DBCP 等
1. 手写数据库连接池
这里我们手写一个简单的数据库连接池
在设计数据库连接池之前,我们需要确定,这个连接池一个就可以了,所以连接池的类就只能有一个实例(对象),这就可以用到单例的设计模式。使用哪种方式创建单例模式,如果使用饿汉式,需要提前加载好连接池
2. 创建数据库连接池的类
package com.shao.Utils;import java.sql.Connection;
import java.util.LinkedList;public class DBConnectPool {// 定义一个双向链表,存放连接private static final LinkedList<Connection> connectionPool = new LinkedList<>();// 连接池最大连接数private static final int maxSize = 10;// 定义一个成员变量,存储单例对象private static DBConnectPool dbConPool;// 静态代码块,在类加载时,就会执行代码块,且只执行一次// 调用工具类获取连接,然后添加到连接池中static {for (int i = 0; i < maxSize; i++) {Connection connection = DBConnectUtil.getConnection();if (connection != null) {connectionPool.add(connection);}}}// 私有构造方法,防止外部实例化private DBConnectPool() {}// 对外提供一个接口,用于获取DBConnectPool对象public static DBConnectPool getInstance() {synchronized (DBConnectPool.class) {if (dbConPool == null) {dbConPool = new DBConnectPool();}return dbConPool;}}// 获取连接public Connection getConnection() {// 从连接池删除一个连接,然后把连接返回// 以同步的方法获取连接,保证线程安全synchronized (DBConnectPool.class) {Connection poll = connectionPool.poll();return poll;}}// 释放连接public void releaseConnection(Connection connection) {if (connection == null) {return;}//把连接放回连接池synchronized (DBConnectPool.class) {connectionPool.add(connection);}}
}
3. 初始化连接池
用饿汉式创建连接池,所以需要在启动 Tomcat 的时候就加载好连接池
4. 在 Dao 层获取数据库连接
5. 归还数据库连接
在数据库连接工具类的释放资源的方法中可以删掉释放数据库连接的参数,改为使用连接池的归还连接的方法