Java的JDBC连接池底层封装

目录

一、JDBC回顾

1.1 六个基本步骤

1.2 问题的引出

二、ORM框架

三、JDBC连接池性能优化

3.1 JDBC连接池在创建连接时优化

3.2 将连接(桥梁)和状态进行绑定

1、MyConnection

3.3 设计连接池用于装连接

1、ConnectionPool

四、JDBC连接池性能的测试

五、将JDBC整体优化

5.1 问题的产生

5.2 问题解决方案

1、单例的连接池对象

2、添加配置文件

3、多个人同时访问

4、防止多余的线程出现null

5、代理模式

(1)静态代理模式

(2)缺省适配器模式

5.3 完整的JDBC封装代码

1、MyConnection

2、ConnectionPool

3、ConfigReader

4、SystemBusyException

5、TestThread

6、TestThreadMain


一、JDBC回顾

1.1 六个基本步骤

package test;
​
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
​
public class TestMain {
    public static void main(String[] args) throws Exception {
        //JDBC全称----JAVA DataBase Connectivity
        //1.导包
        //2.加载驱动类
        Class.forName("com.mysql.cj.jdbc.Driver");
        //3.获取连接,只是一个桥梁,负责让驱动与数据库连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useSSL=false","root","12345678");
        //4.获取状态参数(流)
        //PreparedStatement预处理流
        PreparedStatement pstat = connection.prepareStatement("select * from users");
        //5.执行操作
        //增删改---executeUpdate()
        //查询---executeQuery()
        //ResultSet底层---Set<Map<String,Object>>
        ResultSet rs = pstat.executeQuery();
        //6.访问下一个rs中的数据,遍历
        while(rs.next()) {
            System.out.print(rs.getInt("id"));
            System.out.print(rs.getString("uname"));
            System.out.print(rs.getString("psw"));
            System.out.print(rs.getString("email"));
            System.out.print(rs.getDate("birthday"));
            System.out.print(rs.getDouble("sal"));
            System.out.println();
        }
        //7.关闭资源
        rs.close();
        pstat.close();
        connection.close();
    }
​
}

1.2 问题的引出

1、JDBC代码流程写在哪个层次中?----DAO数据读写持久化层

2、DAO层中通常的方法什么样子?----对于数据库的增删改查操作

3、DAO方法中该写什么样的代码?----纯粹的JDBC流程 + SQL执行语句

4、DAO层次中的每一个方法里都存在JDBC流程(6步),流程大体上是一样的。所以DAO层次中的代码冗余问题就很多。------封装,也就是(ORM框架)

5、我们所说的JDBC连接池封装,其实是对我们现在的流程进行优化;我们现在所写的六个基本步中是存在很多问题的;而我们最后学的将整个JDBC封装是一个ORM框架;

二、ORM框架

1、ORM框架(Object Relationship Mapping对象关系映射)

(1)对象---domain

(2)关系---对应(映射)

2、ORM框架封装以后的最终效果,我们不用写JDBC流程了,只写SQL + 那些问号值;我们DAO层次中的方法没有执行体,只有方法的壳 + 注解(写SQL)

3、在ORM框架中讲解。

三、JDBC连接池性能优化

3.1 JDBC连接池在创建连接时优化

1、我们发现每次运行TestMain中的JDBC六步时都会有几秒卡顿,要进行优化

package test;

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

public class TestMain {
    public static void main(String[] args) throws Exception {
        //JDBC全称----JAVA DataBase Connectivity
        //1.导包
        //2.加载驱动类
        long t1 = System.currentTimeMillis();
        Class.forName("com.mysql.cj.jdbc.Driver");
        //3.获取连接,只是一个桥梁,负责让驱动与数据库连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest?useSSL=false","root","12345678");
        long t2 = System.currentTimeMillis();
        //4.获取状态参数(流)
        //PreparedStatement预处理流
        PreparedStatement pstat = connection.prepareStatement("select * from users");
        //5.执行操作
        //增删改---executeUpdate()
        //查询---executeQuery()
        //ResultSet底层---Set<Map<String,Object>>
        ResultSet rs = pstat.executeQuery();
        //6.访问下一个rs中的数据,遍历
        while(rs.next()) {
            System.out.print(rs.getInt("id"));
            System.out.print(rs.getString("uname"));
            System.out.print(rs.getString("psw"));
            System.out.print(rs.getString("email"));
            System.out.print(rs.getDate("birthday"));
            System.out.print(rs.getDouble("sal"));
            System.out.println();
        }
        //7.关闭资源
        rs.close();
        pstat.close();
        connection.close();
        long t3 = System.currentTimeMillis();
        System.out.println(t2-t1);//794
        System.out.println(t3-t2);//127
    }

}

2、我们认为t1 和 t2 之间的时间是创建连接的时间,t2 和 t3 之间的时间是整个执行的时间

3、发现毫秒差很大,原因:

(1)DAO层的代码冗余问题

(2)JDBC流程中有一个性能卡顿,是在创建连接这个地方(由于每次进行数据操作都要去创建连接,每操作一次数据库,就会创建一次连接)

(3)解析:这个Connection就好比是一个桥梁,假如有5个人要过桥,但是此时并没有桥,每一个人都会先创建一个桥,然后过去之后再把桥炸了,然后第二个人想过桥会在创建一个桥过去之后在把桥炸了;所以每个人都会有创建桥的这个过程,里面增加了很多冗余。

4、如何提高创建连接的性能?

(1)假设我们创建5个连接,5个连接不销毁,留着复用。5个连接存在哪里?---集合(也就是连接池),还需要对集合进行管理

(2)需要将每一个连接身上绑定一个状态,连接类不是我们自己写的(Connection不是我们自己写的类),所以我们要创建一个新对象来将连接绑定状态。

3.2 将连接(桥梁)和状态进行绑定

1、MyConnection

1、MyConnection:描述一个真实连接和一个状态的关系,将一个真实可用连接和一个状态封装(绑定)在一起形成一个新的对象

(1)我们首先要在MyConnection类中创建连接和状态,最好是属性,创建属性的目的是为了描述对象的特征和状态;

//创建连接
private Connection conn;
//创建状态(false表示连接空闲--可用,true表示连接已被其他人占用--不可用)默认false;
private boolean used = false;

(2)上面创建了两个属性,发现conn连接这个属性它是没有完成连接操作的。所以我们创建一个普通块,目的是为了给我们创建的conn赋予一定的能力,就是为了我们每次new MyConnection的时候,能得到这个连接,这里的连接指的是让Java代码与数据库连接(桥梁);

{
        //我们为什么不直接private Connection conn = DriverManager.getConnection();因为 DriverManager.getConnection();是有异常的
        //这两行代码不能给属性,因为属性是不能抛异常的,而且属性这一行无法完成
        Class.forName("");
        conn = DriverManager.getConnection();
}

(3)那么问题来了,属性不是一个,因为每次new 都会创建新的属性,然后完成一次类加载,然后在创建桥梁,可是我们的类加载只需创建一次就够了啊,所以还需要创建一个静态块,目的为了让类加载执行一次

static {
        //由于每new一次就执行一次普通块,所以把类加载放在静态块中
        Class.forName("");
	}

(4)连接需要获取get,让外界在创建对象时可以获取MyConnection对象中的连接(桥梁)getConn,让用户能获取到这个桥梁;used状态需要来回切换,所以还有set/get方法

 //让外界能获取到连接(桥梁)
    public Connection getConn() {
        return conn;
    }
	//used状态需要来回切换
    public boolean isUsed() {//boolean的get方法,名字是is开头的---规约
        return used;
    }
    public void setUsed(boolean used) {
        this.used = used;
    }

(6)完整版

package pool;

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

/**
 * @Description: TODO 描述一个真实连接和一个状态的关系
*                     将一个真实可用连接(桥梁)和一个状态封装在一起形成一个新的对象MyConnection
 * @Author: 曹宇希
 * @Date: 2023/11/30 12:20
 * @Version: 1.0
 * @Company: 版权所有
 */
public class MyConnection {
    private static String className = "com.mysql.cj.jdbc.Driver";
    private static String url = "jdbc:mysql://localhost:3306/jdbctest?serverTimezone=GMT%2B8";
    private static String user = "root";
    private static String password = "12345678";

    //连接
    private Connection conn;
    //状态(false表示连接空闲--可用,true表示连接已被其他人占用--不可用)默认false
    private boolean used = false;

    //一个静态块,目的为了让类加载执行一次
    static {
        try{
            Class.forName(className);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //一个普通块,目的是为了给属性赋值
    {
        try{
            //桥梁
            conn = DriverManager.getConnection(url,user,password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
	//让外界能获取到这个桥梁
    public Connection getConn() {
        return conn;
    }
    //used状态需要来回切换
    public boolean isUsed() {
        return used;
    }
    public void setUsed(boolean used) {
        this.used = used;
    }
}

3.3 设计连接池用于装连接

1、ConnectionPool

1、我们设计的连接池类,这个类需要将好多连接存储起来---属性(集合),存储以后当然需要将连接获取到并给用户拿去使用---池子进行管理

2、创建一个属性,这个属性用于当作我们的连接池,那么一个大池子我们用什么存储好?---集合,并且这个集合还可以进行遍历查找,所以采用List集合;那么这个集合里面应该就存储着我们自己创建的连接

private List<MyConnection> pool = new ArrayList();

3、那么连接存储完了,我们还需要给pool连接池赋值,那么应该在哪里赋值?怎么赋值?创建一个普通块;每次类加载的时候就会给连接池装入连接

{
        //在ArrayList连接池中创建5个连接
        for (int i = 1; i <= 5; i++) {
            pool.add(new MyConnection());
        }
}

4、设计方法来获取连接池中的连接,返回值是Connection 还是用 MyConnection?问题来了,我们创建的连接给谁用?--用户,用户需要获取连接池的连接,用完连接之后还需要关闭资源,而这个资源我们不能真正的关闭,需要操作资源状态,用完之后还需要还回来(需要切换状态---释放连接)能切换状态的也就是我们自己创建的MyConnection

(1)MyConnection中的getConn方法是为了让其他类获取连接

public MyConnection getMC(){
    MyConnection result = null;
    for (MyConnection mc:pool) {
        if (!mc.isUsed()) {//这个连接可用
            mc.setUsed(true);//先把这个连接占为己有
            result = mc;//然后存起来
            break;
        }
    }
    return result;
}

5、完整版

package pool;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description: TODO 这是我们设计的连接池类,这个类需要将好多连接存储起来--属性(集合)
 * 存储以后当然需要将连接获取到并给用户拿去使用--池子进行管理
 * @Author: 曹宇希
 * @Date: 2023/12/1 8:17
 * @Version: 1.0
 * @Company: 版权所有
 */
public class ConnectionPool {
    //属性--大池子 List集合存储着我们自己创建的连接
    private List<MyConnection> pool = new ArrayList();
    //给pool连接池当中的连接赋值,那么在哪里赋值,怎么赋值,普通块
    {
        for (int i = 1; i <= 5; i++) {
            pool.add(new MyConnection());
        }
    }
    //方法--获取连接  返回值Conn,我们获取连接给谁用?
    //用户需要操作状态,用户用完了以后,不能关闭,用完之后还需要还回来(需要切换状态---释放)
    //能切换状态的也就是我们自己创建的MyConnection
    public MyConnection getMC(){
        MyConnection result = null;
        for (MyConnection mc:pool) {
            if (!mc.isUsed()) {//这个连接可用!mc.isUsed() 其实就是mc.isUsed()==false
                mc.setUsed(true);//先把这个连接占为己有
                result = mc;//然后存起来
                break;
            }
        }
        return result;
    }

}

四、JDBC连接池性能的测试

1、最基本的封装完毕了,那该怎么用?该怎么测试出第二次没有创建连接呢?

public class TestMain {
    public static void main(String[] args) throws Exception{
//   TODO 第一次:创建连接和查询
        //1.导包
        //2.加载连接池对象
        long t1 = System.currentTimeMillis();
        ConnectionPool connectionPool = new ConnectionPool();
        //3.获取连接
        MyConnection mc = connectionPool.getMC();//我们自己包装的类,这个类里面才有连接
        Connection conn = mc.getConn();//通过类获取连接
        long t2 = System.currentTimeMillis();
        //4.状态参数
        PreparedStatement pstat = conn.prepareStatement("select * from users");
        ResultSet rs = pstat.executeQuery();
        //5.rs做业务
        while (rs.next()) {
            System.out.println(rs.getString("uname"));
        }
        //6.关闭资源
        pstat.close();
        rs.close();
        mc.setUsed(false);//不能conn.close(),因为我们是自己创建的连接,需要修改连接的状态,不是真的关闭,而是释放
        long t3 = System.currentTimeMillis();
        System.out.println("创建连接的时间:" + (t2-t1));
        System.out.println("执行的时间" + (t3-t2));
//  TODO 第二次:不需要创建连接了,直接查询
        long t4 = System.currentTimeMillis();
        //3.获取连接
        MyConnection mc2 = connectionPool.getMC();
        Connection conn2 = mc2.getConn();
        long t5 = System.currentTimeMillis();
        //4.状态参数
        PreparedStatement pstat2 = conn2.prepareStatement("select * from users");
        ResultSet rs2 = pstat2.executeQuery();
        //5.rs做业务
        while (rs2.next()) {
            System.out.println(rs2.getString("uname"));
        }
        //6.关闭资源
        pstat.close();
        rs.close();
        mc.setUsed(false);
        long t6 = System.currentTimeMillis();
        System.out.println("创建连接的时间:" + (t5-t4));
        System.out.println("执行的时间" + (t6-t5));
    }
}

2、解决的问题

(1)节省了时间

(2)连接池对象,发现单例的就够了

(3)发现有很多信息是写死的,采用配置文件会更好

(4)现在我们只有5个连接,如果6个人过来会怎么样?两个人抢到了同一个连接,所以是个线程安全的问题--线程安全--我们加个锁

(5)如果锁上了,保证连接是唯

  • 35
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
这是一个简单的基于JavaJDBC连接池示例。它使用了一个Vector来存储连接对象。在对象池初始化时,它会创建一定数量的连接对象。在需要使用连接时,它会从对象池中获取一个连接对象,使用完毕后将其返回给对象池。 ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Vector; public class JDBCConnectionPool { private String url, username, password, driverClassName; private int maxConnections; private Vector<Connection> connectionPool; public JDBCConnectionPool(String url, String username, String password, String driverClassName, int maxConnections) { this.url = url; this.username = username; this.password = password; this.driverClassName = driverClassName; this.maxConnections = maxConnections; connectionPool = new Vector<Connection>(); } public synchronized Connection getConnection() { Connection connection = null; if (connectionPool.size() > 0) { connection = connectionPool.remove(0); } else { connection = newConnection(); } return connection; } public synchronized void returnConnection(Connection connection) { if (connection != null && connectionPool.size() < maxConnections) { connectionPool.add(connection); } else { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } private Connection newConnection() { Connection connection = null; try { Class.forName(driverClassName); connection = DriverManager.getConnection(url, username, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return connection; } } ``` 示例用法: ```java JDBCConnectionPool pool = new JDBCConnectionPool( "jdbc:mysql://localhost:3306/mydb", "root", "password", "com.mysql.jdbc.Driver", 10); Connection connection = pool.getConnection(); // Use connection... pool.returnConnection(connection); ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A五花肉~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值