Matrix42

不饱食以终日,不弃功于寸阴

JDBC之: 数据库连接池(八)

什么情况下使用连接池?

对于一个简单的数据库应用,由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。但是对于一个复杂的数据库应用,情况就完全不同了。频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈。

使用连接池的好处

  1. 连接复用。通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。

  2. 对于共享资源,有一个很著名的设计模式:资源池。该模式正是为了解决资源频繁分配、释放所造成的问题的。把该模式应用到数据库连接管理领域,就是建立一个数据库连接池,提供一套高效的连接分配、使用策略,最终目标是实现连接的高效、安全的复用。

连接池的实现

数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。

外部使用者可通过 getConnection 方法获取连接,使用完毕后再通过 close 方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。

Java 中有一个 DataSource 接口, 数据库连接池就是 DataSource 的一个实现

下面我们自己实现一个数据库连接池:

首先实现 DataSource, 这里使用 BlockingQueue 作为池 (只保留了关键代码)

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Logger;

public class MyDataSource implements DataSource {

    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //这里有个坑
    //MySQL用的是5.5的
    //驱动用的是最新的
    //连接的时候会报The server time zone value '�й���׼ʱ��'
    // is unrecognized or represents more than one time zone
    //解决方法:
    //1.在连接串中加入?serverTimezone=UTC
    //2.在mysql中设置时区,默认为SYSTEM
    //set global time_zone='+8:00'
    private String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC";
    private String user = "root";
    private String password = "123456";

    private BlockingQueue<Connection> pool = new ArrayBlockingQueue<>(3);

    public MyDataSource() {
        initPool();
    }

    private void initPool() {
        try {
            for (int i = 0; i < 3; i++) {
                pool.add(new MyConnection(
                        DriverManager.getConnection(url, user, password), this));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /*
    从池中获取连接
     */
    @Override
    public synchronized Connection getConnection() throws SQLException {
        try {
            return pool.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("get connection failed!");
    }

    public BlockingQueue<Connection> getPool() {
        return pool;
    }

    public void setPool(BlockingQueue<Connection> pool) {
        this.pool = pool;
    }
}

实现自己的连接, 对原生连接进行封装, 调用 close 方法的时候将连接放回到池中

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

public class MyConnection implements Connection {

    //包装的连接
    private Connection conn;
    private MyDataSource dataSource;

    public MyConnection(Connection conn, MyDataSource dataSource) {
        this.conn = conn;
        this.dataSource = dataSource;
    }

    @Override
    public Statement createStatement() throws SQLException {
        return conn.createStatement();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return conn.getAutoCommit();
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        conn.setAutoCommit(autoCommit);
    }

    @Override
    public void commit() throws SQLException {
        conn.commit();
    }

    @Override
    public void rollback() throws SQLException {
        conn.rollback();
    }

    @Override
    public void close() throws SQLException {
        //解决重复关闭问题
        if (!isClosed()) {
            dataSource.getPool().add(this);
        }
    }

    @Override
    public boolean isClosed() throws SQLException {
        return dataSource.getPool().contains(this);
    }
}

main 方法

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class Main {
    public static void main(String[] args) {
        DataSource source = new MyDataSource();
        try {
            Connection conn = source.getConnection();
            Statement st = conn.createStatement();
            st.execute("INSERT INTO USER (name,age) values('bob',12)");
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

常用数据库连接池

参考资料

常用数据库连接池 (DBCP、c3p0、Druid) 配置说明

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注

3 × 2 =