JDBC/InvocationHandler动态代理实现数据库连接池、数据源

Java的JDBC编程中,数据库的连接和关闭是十分耗费资源的。当对数据库的访问不是很频繁时,可以在每次访问数据库时建立一个连接,用完之后关闭。但是,对于一个复杂的数据库应用,频繁的建立、关闭连接,会极大的减低系统性能,造成瓶颈。所以可以使用数据库连接池来达到连接资源的共享,使得对于数据库的连接可以使高效、安全的复用。

MyDataSource 实现数据库连接池

通过自定义数据库了连接MyConnection(包裹了真正的Connection),使用一个LinkedList存放MyConnection,当一个数据库连接使用完成后,重新添加到LinkedList中,实现连接的复用。数据库连接池初始化时,构造多个数据库连接,虽然此时比较耗时,但是能够实现连接池的复用,提高效率。

package com.jdbc.datasource;

import java.sql.*;
import java.util.LinkedList;

/**
 * 数据库连接池
 * 自定义实现数据源
 *
 */
public class MyDataSource {

    private static final String URL = "jdbc:postgresql://localhost:5432/db";
    private static final String USER = "postgres";
    private static final String PASSWORD = "root";

    private static final int INI_COUNT = 5; // 初始连接数
    private static final int MAX_COUNT = 10;    // 最大连接数
    public int curCount= 0; // 当前连接数

    // add remove频繁,LinkedList 效率由于 ArrayList
    LinkedList<Connection> connsPool = new LinkedList<Connection>();

    /**
     *  初始构造多个数据库连接
     */
    public MyDataSource() {
        try {
            for (int i = 0; i < INI_COUNT; i++) {
                this.connsPool.add(this.createConnection());
                curCount++;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建连接
     * @return
     * @throws SQLException
     */
    public Connection createConnection() throws SQLException {
        Connection realConn = DriverManager.getConnection(URL, USER, PASSWORD);
        // MyConnection conn = new MyConnection(this, realConn);
        /*** Connection 的代理类,绑定真正的Connection,拦截 close()方法 ***/
        MyConnectionHandler pHandler = new MyConnectionHandler(this);
        return pHandler.bind(realConn);
    }


    /**
     * 释放
     * @param conn 数据库连接
     */
    public void free(Connection conn) {
        this.connsPool.addLast(conn);
    }

    /**
     * 获取连接
     * @return
     * @throws SQLException
     */
    public Connection getConnection() throws SQLException {
        Connection conn = null;
        /*** 同步加锁 ***/
        synchronized (connsPool) {
            if (this.connsPool.size() > 0) {
                conn = this.connsPool.removeFirst();
                return conn;
            } else if (curCount < MAX_COUNT) { // 连接池里面没有连接,且当前连接数没有达到最大连接
                this.curCount++;                // 创建新连接
                conn = this.createConnection();
                return conn;
            }
            throw new SQLException(" 连接池里已无可用连接 ... ");
        }
    }
}

MyConnection 实现 Connection接口方式

将自定义类MyConnection实现Connection接口,重写close()方法,关闭时重新放入连接池,其他的非close()方法则直接转交给真正的Connection实现即可。该方式的缺点是,需要实现Connection接口的所有方法。

package com.jdbc.datasource;

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

/**
 * MyConnection 代理 Connection,实现 Connection接口
 * 相当于Connection的子类,可以和Connection一样操作
 * 代理了所有对真正的Connection操作
 * 重要:close()方法重写,关闭时重新放入连接池
 * 不足:需要重写所有方法
 * 改进:改用Proxy代理模式实现,只对close()方法进行拦截,不修改其他方法
 *
 */
public class MyConnection implements Connection {

    private MyDataSource myDataSource = null; // 数据源
    private Connection realConn = null; // 真正的connection

    private static final int MAX_USE_COUNT = 5; // 最大使用次数
    private int curUseCount = 0;    // 当前使用次数

    MyConnection(MyDataSource myDataSource, Connection realConn) {
        this.myDataSource = myDataSource;
        this.realConn = realConn;
    }


    /**
     * close()方法重写
     * 未超过最大使用次数,关闭时重新放入连接池
     * @throws SQLException
     */
    @Override
    public void close() throws SQLException {
        curUseCount++;
        if (curUseCount < MAX_USE_COUNT) {
            this.myDataSource.connsPool.addLast(this);
        } else {
            this.realConn.close();
            this.myDataSource.curCount--;
        }
    }

    /**
     * 其他方法直接交给realConn实现
     * @throws SQLException
     */
    @Override
    public boolean isClosed() throws SQLException {
        return this.realConn.isClosed();
    }

    //其他方法略...
}

InvocationHandler动态代理实现方式

MyConnectionHandler类实现InvocationHandler接口,最主要包括两个步骤:

  • Proxy.newProxyInstance()方法
    this.warpedConn = (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] { Connection.class }, this); 使用包裹后的Connection代理真正的Connection
  • 重写invoke()方法
    public Object invoke(Object proxy, Method method, Object[] args)拦截代理对应的方法。
package com.jdbc.datasource;

import java.lang.reflect.*;
import java.sql.Connection;

/**
 * Connection 代理类
 * 拦截 close()方法
 *
 */
public class MyConnectionHandler implements InvocationHandler {

    private Connection realConn = null; // 真正的连接
    private Connection warpedConn = null;   // 包裹的连接(代理连接)
    private MyDataSource myDataSource = null;   // 连接池

    private static final int MAX_USE_COUNT = 5; // 最大使用次数
    private int curUseCount = 0;    // 当前使用次数

    /**
     * 构造方法
     * @param myDataSource 连接池
     */
    public MyConnectionHandler(MyDataSource myDataSource) {
        this.myDataSource = myDataSource;
    }

    /**
     * 绑定真正的连接到包裹连接
     * @param realConn 真正的连接
     * @return
     */
    public Connection bind(Connection realConn) {
        /*** 真正的连接,用于数据库的其他操作 ***/
        this.realConn = realConn;

        /*** 代理模式动态生成类 ,实现了Connection.Class 接口, 其方法作用在当前handler上 ***/
        /*** 用于拦截 realConn.close() 方法 ***/
        this.warpedConn = (Connection) Proxy.newProxyInstance(this.getClass()
                .getClassLoader(), new Class[] { Connection.class }, this);

        return this.warpedConn; // 返回包裹后的连接
    }

    /**
     * 重写 invoke() 方法
     * 拦截close()方法,关闭连接是重新放回到数据库连接池
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /*** 拦截 realConn.close() 方法 ***/
        if (method.getName().equals("close")) {
            curUseCount++;
            if (curUseCount < MAX_USE_COUNT) {
                this.myDataSource.connsPool.addLast(this.warpedConn);
            } else {
                this.realConn.close();
                this.myDataSource.curCount--;
            }
        }

        /*** 其他方法,直接执行到realConn上 ***/
        return method.invoke(this.realConn, args);
    }
}

Apache Commons DBCP 数据源
数据源一般不需要自己实现,apache DBCP数据源就是一种很好的开源实现。

将数据源的相关配置信息以配置文件的方式进行设置,读取配置文件,生成数据源即可。DBCP数据源会自动管理数据库连接池,JDBC操作直接从数据源中获取Connection即可。

DBCP数据源配置信息:

#连接设置
driverClassName=org.postgresql.Driver
url=jdbc:postgresql://localhost:5432/db
username=postgres
password=root

#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000


#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] 
#注意:"user""password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

JdbcUtil类使用DBCP数据源:

package com.utils;

import java.io.*;
import java.sql.*;
import java.util.Properties;
import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
//import com.jdbc.datasource.MyDataSource;

public class JdbcUtil {
/*  private static final String URL = "jdbc:postgresql://localhost:5432/db";
    private static final String USER = "postgres";
    private static final String PASSWORD = "root";*/

    // 使用自定义数据源
    // private static MyDataSource myDataSource = null;

    // 使用Apache的框架,提供数据源,实现了DataSource接口
    private static DataSource myDataSource = null;

    /**
     * 私有构造方法
     */
    private JdbcUtil() {

    }

    /**
     * 静态代码注册驱动
     */
    static {
        try {
            Class.forName("org.postgresql.Driver").newInstance();

            // 使用自定义数据源
            // myDataSource = new MyDataSource();

            // 使用Apache的框架,提供数据源,实现了DataSource接口
            Properties prop = new Properties();
            InputStream is = JdbcUtil.class.getClassLoader().
                    getResourceAsStream("dbcpconfig.properties");
            prop.load(is);
            myDataSource = BasicDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

    /**
     * 获取datasource
     * @return
     */
    public static final DataSource GetDataSource() {
        return myDataSource;
    }


    /**
     * 获取数据库连接
     * @return
     */
    public static final Connection GetConnection() {
        Connection conn = null;
        try {
            // conn = DriverManager.getConnection(URL, USER, PASSWORD);
            conn = myDataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 关闭相关资源
     * @param rs 
     * @param st
     * @param conn
     */
    public static final void Free(ResultSet rs, Statement st, Connection conn) {
        try {
            if (rs != null)
                rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (st != null)
                    st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                if (conn != null)
                    try {
                        conn.close();
                        // myDataSource.free(conn);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            }
        }
    }
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页