JDBC 连接池介绍和简单实现

连接池技术

简介

前面写了 JDBC 技术,为了在 java 应用中访问数据库,就需要使用 JDBC,但是每次建立 Connection 实例是需要开销的,每次 SQL 操作都要进行 Connection 的开关,是很消耗资源,多次或并发进行,开销更大,且属于无意义浪费,所以连接池技术出现。

就是预先准备好多个 Connection 连接实例,这些开销是必须要的,但是在每次使用时只允许一个线程调用,结束后并不关闭连接,而是将其放到连接池中设为公共,等待下一次调用。

结构介绍

完整的连接池是由连接池和连接池管理两部分组成,这样我们并不需要直接对连接池进行操作,我们只需要调用管理类的方法,因为我们使用者关注的应该是业务逻辑,只需要拿取 Connection 实例以及通过管理类设置相关连接池参数即可。

连接池结构

简单来说就是多个 Connection 实例,存储在容器中,有相关的属性,并且对外提供设置方法。

核心思想就是对 Connection 连接实例的复用。并且提供了安全性,稳定性,便利性。

也许多个不同的数据库厂商的性质不同决定了连接池的实现有不同,但是原理都是池结构,只是开发者的实现方式不一样。

image-20220808182604917

常见属性

  • initSize:初始化时的连接数。
  • maxSize:连接池中最大连接数。
  • minSize:连接池中最小连接数。
  • size:连接池中连接数。
  • maxTime:连接空闲超时时间。
  • name:连接池名称(可选),一般场景是一个管理类管理多个不同连接池用于区分的。

剩下的属性可能需要自己按照具体场景添加或者在管理类添加更好。

在实际业务场景中还是流行使用业界流行的连接池框架,每个框架特色不同,根据实际业务来取舍。

但是我们不能只是使用,我们当然要来理解一下别人优秀框架的基本原理和优秀的框架设计,帮助我们进行框架功能分析,即使理解不是很透彻,但是需要有这样一个意识,只有看别人优秀的作品才能吸取自己没有的优点,从而反思自己。

连接池实现

这里就对连接池进行一个简陋的实现,主要是理解流程。

不考虑在多线程情况下进行。

// 简陋的数据库连接池
public class JdbcPool {
    private static final int poolInitNum = 10; // 初始化默认池大小为10
    private LinkedList<Connection> pool = null; // 用链表形式作为连接池的数据结构,方便存取
    private static final int poolMax = 20;
    private final String user;
    private final String pwd;
    private final String url;

    public void getPoolNum(){
        System.out.println("连接池:-->"+pool+"内连接数量:"+pool.size());
    }

    public JdbcPool(String user, String pwd, String url) {
        this.user = user;
        this.pwd = pwd;
        this.url = url;
    }


    /**
     * 创建连接池 连接池的数据结构用链表来表示
     */
    public void createPool() throws SQLException {
        // 保证连接池没有被创建
        if (pool != null) {
            return;
        }
        // 创建连接池
        pool = new LinkedList<>();
        initConnection();
    }

    /**
     * 初始化连接池
     */
    private void initConnection() throws SQLException {
        for (int i = 0; i < poolInitNum; i++) {
            pool.add(newConnection());
        }
    }

    /**
     * 创建一个新的Connection并返回
     *
     * @return Connection
     */
    private Connection newConnection() throws SQLException {
        return DriverManager.getConnection(url, user, pwd);
    }

    /**
     * 从连接池中获取一个连接实例
     *
     * @return 连接池中第一个connection
     */
    public Connection getConnection() throws SQLException {
        if (pool.size() > 0) {
            return pool.removeFirst();
        } else {
            return newConnection();
        }
    }

    /**
     * 放回一个connection至连接池尾部,如果池中连接数达到poolMax则不能在放入连接池,必须销毁该connection
     *
     * @param conn connection
     */
    public void returnConnection(Connection conn) throws SQLException {
        if (pool!=null){
            if (pool.size() < poolMax) {
                pool.add(conn);
            } else {
                conn.close();
            }
        }else {
            conn.close();
        }

    }

    /**
     * 关闭连接池
     */
    public void closePool() throws SQLException {
        // 断开所有Connection
        for (int i = pool.size() - 1; i >= 0; i--) {
            Connection connection = pool.remove(i);
            connection.close();
        }
        pool = null;
    }
}
复制代码

以上主要是看看基本的属性,以及 Connection 连接实例在容器中的存取,回收方法。

篇幅原因只能进行简写,并没有将其按规范进行层层封装了,正确的连接池肯定不是我这样写的,应该按照连接池的基本思想,考虑多个场景进行详细设计并实现。

连接池管理类实现

// 数据库连接池工具类 保证唯一一个数据库连接池对象,使用抽象类保证不能实例化
public abstract class JdbcPoolUtil {
    private static String user = "";
    private static String pwd = "";
    private static String url = "";
    private static JdbcPool pool = null;

    static {
        Properties properties = new Properties();// 配置文件工具类,获取jdbc.properties文件的数据库连接配置信息
        InputStream is = DbUtil.class.getClassLoader().getResourceAsStream("jdbc_config.properties");
        System.out.println(is);
        try {
            properties.load(is);
            user = properties.getProperty("user");
            pwd = properties.getProperty("pwd");
            url = properties.getProperty("url");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try{
                if (is!=null){
                    is.close();
                    System.out.println("数据库连接参数读取完毕!");
                }else {
                    System.out.println("数据库连接失败!");
                }
            }catch (IOException e){
                e.printStackTrace();
            }finally {
                System.out.println(url);
                System.out.println(user);
                System.out.println(pwd);
            }

        }
    }

    /**
     * 得到数据库连接池对象
     * @return JdbcPool
     */
    public static JdbcPool getJdbcPool() {
        if (pool == null) {
            pool = new JdbcPool(user, pwd, url);
            try {
                pool.createPool();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return pool;
    }

    /**
     * 关闭数据库连接池
     */
    public static void closeJdbcPool(){
        try {
            pool.closePool();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

复制代码

上面管理类,只是极为简略的实现,严格来说并不能称为管理类,管理类应该注重对连接池和里面连接实例的管理,更甚者可能管理多个不同的连接池。所以需要学到后面框架,先熟练使用,巩固基础,再尝试看博客讲解,分析其中设计的每个模块关系,梳理整体架构,这样我们的思路才会更加清晰和完善。

小结

因为只是简单的实现基本效果,没有考虑动态代理、多线程安全、超时策略、资源回收策略等详细功能,同时也是自己学艺不精,只学到了浅层次,对于这些都有想法但是具体实现就摆了。

可能在复习完 Java 所有基础和面试题之后,秋招一个稍好点的公司,再去深入学习原理。

保持不骄不躁,时常反思的心态,一直学下去。

猜你喜欢

转载自juejin.im/post/7129788051706150949