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

Posted smile4lee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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();
                    
            
        
    

以上是关于JDBC/InvocationHandler动态代理实现数据库连接池数据源的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向Android 中常用的 so 动态库 ( /system/lib/libc.so 动态库 | libc++.so 动态库 | libstdc++.so 动态库 )(代

设计模式 结构型模式 -- 代理模式(动态代理(JDK动态代理(JDK动态代理要求必须定义接口,对接口进行代理。)动态代理原理(使用arthas-boot.jar查看代理类的结构)动态代理的作用)(代

Centos7-yum部署配置LAMP-之LAMP及php-fpm实现反代动态资源

Java堆内存

Android 安装包优化使用 lib7zr.so 动态库处理压缩文件 ( 拷贝 lib7zr.so 动态库到 Android Studio 工程 | 配置 build.gradle 构建脚本 )(代

Android 安装包优化动态库打包配置 ( “armeabi-v7a“, “arm64-v8a“, “x86“, “x86_64“ APK 打包 CPU 指令集配置 | NDK 完整配置参考 )(代