【学习笔记】手写 Tomcat 五

目录

[一、优化 Servlet](#一、优化 Servlet)

创建一个抽象类

继承抽象类

二、三层架构

业务逻辑层

数据访问层

[1. 在 Dao 层操作数据库](#1. 在 Dao 层操作数据库)

[2. 调用 Dao 层,实现业务逻辑功能](#2. 调用 Dao 层,实现业务逻辑功能)

[3. 调用 Service 层,响应数据](#3. 调用 Service 层,响应数据)

测试

三、数据库连接池

[1. 手写数据库连接池](#1. 手写数据库连接池)

[2. 创建数据库连接池的类](#2. 创建数据库连接池的类)

[3. 初始化连接池](#3. 初始化连接池)

[4. 在 Dao 层获取数据库连接](#4. 在 Dao 层获取数据库连接)

[5. 归还数据库连接](#5. 归还数据库连接)

作业

[1. 3w1h的方式,了解单例模式](#1. 3w1h的方式,了解单例模式)

[2. 了解其他连接池](#2. 了解其他连接池)


一、优化 Servlet

昨天,我们把响应动态资源的功能放到了 Servlet,一个 Servlet 就是一个功能

当 Servlet 多了之后,我们会发现,每个 Servlet 里都有 service 方法,那有什么方法可以简化呢?

这里就可以使用 Java 的三大特性之一 继承

创建一个抽象类

定义普通的 service 方法,和 doGet , doPost 抽象方法

继承抽象类

在每个 Servlet 中继承这个抽象类,然后就可以删除 service 方法了

二、三层架构

我们把功能都写在了 Servlet,这样有几个缺点,一是不够低耦合 高内聚,二是不方便协同开发等

我们可以使用三层架构来解决这个问题

三层架构是一种设计思想,分为表示层,业务逻辑层和数据访问层

表示层 Servlet:负责处理用户的请求和响应用户需要的数据

业务逻辑层 Service:负责业务上逻辑的处理,也是表示层和数据层之间的桥梁,

数据访问层 Dao:操作数据库,进行增删改查,并将结果传给业务逻辑层

三层架构区分层次的目的是为了 "高内聚,低耦合"。开发人员分工更明确

业务逻辑层

创建 UserService 接口,用于专门处理用户相关的业务逻辑,比如用户登录,注册,修改密码等

创建接口的实现类,在实现类里实现业务相关逻辑

为什么要创建接口,再创建实现类去实现接口呢?直接创建类,在类里实现业务逻辑不行吗?

当然可以,不过面向接口有很多优点。比如解耦,当实现类需要改变,不会影响 Servlet 层。还有规范化等优点

数据访问层

创建 UserDao 接口,并创建实现类,和业务逻辑层同理

在接口定义抽象方法

1. 在 Dao 层操作数据库

在实现类实现接口,并把 Servlet 层的关于数据库操作的代码放到实现类里

java 复制代码
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 {

    @Override
    public 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 层,并实现业务逻辑功能

java 复制代码
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();
    
    @Override
    public 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 层,然后把业务逻辑层返回的数据响应到客户端

java 复制代码
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. 创建数据库连接池的类

java 复制代码
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. 归还数据库连接

在数据库连接工具类的释放资源的方法中可以删掉释放数据库连接的参数,改为使用连接池的归还连接的方法

作业

1. 3w1h的方式,了解单例模式

2. 了解其他连接池

相关推荐
亦枫Leonlew9 分钟前
三维测量与建模笔记 - 3.3 张正友标定法
笔记·相机标定·三维重建·张正友标定法
考试宝12 分钟前
国家宠物美容师职业技能等级评价(高级)理论考试题
经验分享·笔记·职场和发展·学习方法·业界资讯·宠物
测开小菜鸟14 分钟前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity1 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天1 小时前
java的threadlocal为何内存泄漏
java
caridle1 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^2 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋32 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花2 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端2 小时前
第六章 7.0 LinkList
java·开发语言·网络