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

来源:互联网 发布:linux终端退出命令 编辑:程序博客网 时间:2024/05/17 08:40

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.Driverurl=jdbc:postgresql://localhost:5432/dbusername=postgrespassword=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, SERIALIZABLEdefaultTransactionIsolation=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();                    }            }        }    }}
0 0
原创粉丝点击