009Java操作数据库004手动实现数据库连接池

部分内容来自以下博客:

https://blog.csdn.net/soonfly/article/details/72731144

1 一个简单的数据库连接池

1.1 连接池工具类

连接池使用了线程安全的队列存储连接资源,保证了线程安全。

提供了获取连接和释放连接的方法,实现了连接资源的循环使用。

在对线程进行技术时,使用原子类,保证了线程计数在多线程环境下的安全。

代码如下:

public class DataPoolUtils {
    // 活动连接,使用线程安全的队列
    private static LinkedBlockingQueue<Connection> busy = new LinkedBlockingQueue<Connection>();
    // 空闲连接,使用线程安全的队列
    private static LinkedBlockingQueue<Connection> idle = new LinkedBlockingQueue<Connection>();
    // 已创建连接数,使用原子操作类实现线程安全
    private static AtomicInteger createCount = new AtomicInteger(0);
    // 最大连接数
    private static int maxConnection = 5;
    // 最大等待毫秒数
    private static int maxWaitTimeout = 1000;

    /**
     * 创建连接
     * @return
     * @throws Exception
     */
    private Connection createConnection() throws Exception {
        Properties pros = new Properties();
        InputStream is = DataPoolUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
        pros.load(is);
        String driverClass = pros.getProperty("driverClass");
        Class.forName(driverClass);
        String url = pros.getProperty("url");
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        return DriverManager.getConnection(url, user, password);
    }

    /**
     * 关闭连接
     * @param connection
     */
    private void closeConnection(Connection connection) {
        try {
            if (!connection.isClosed()) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接
     * @return
     * @throws Exception
     */
    public Connection getConnection() throws Exception {
        // 尝试获取空闲连接
        Connection connection = idle.poll();
        if (connection == null) {
            // 尝试创建连接,使用双重CAS检查现有连接数是否小于最大连接数
            if (createCount.get() < maxConnection) {
                if (createCount.incrementAndGet() <= maxConnection) {
                    connection = createConnection();
                } else {
                    createCount.decrementAndGet();
                }
            }
            // 尝试等待获取空闲连接,实现超时等待机制
            if (connection == null) {
                connection = idle.poll(maxWaitTimeout, TimeUnit.MILLISECONDS);
                if (connection == null) {
                    throw new Exception("获取连接超时");
                }
            }
        }
        busy.offer(connection);
        return connection;
    }

    /**
     * 归还连接
     * @param connection
     */
    public void releaseConnection(Connection connection) {
        // 处理空连接
        if (connection == null) {
            createCount.decrementAndGet();
            return;
        }
        // 处理移除失败的连接
        boolean removeResult = busy.remove(connection);
        if (!removeResult) {
            closeConnection(connection);
            createCount.decrementAndGet();
            return;
        }
        // 处理已经关闭的连接
        try {
            if (connection.isClosed()) {
                createCount.decrementAndGet();
                return;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 处理添加失败的连接
        boolean offerResult = idle.offer(connection);
        if (!offerResult) {
            closeConnection(connection);
            createCount.decrementAndGet();
            return;
        }
    }
}

1.2 测试连接池的业务类

为了能够实现线程的循环使用,需要调用线程池的释放连接资源的方法,而不是将连接资源直接关闭。

代码如下:

public class TestPool {
    // 根据配置文件里的名称创建连接池
    private static DataPoolUtils pool = new DataPoolUtils();

    /**
     * 主程序
     */
    public static void main(String[] args) {
        // 模拟多次对数据库的查询操作
        for (int i = 0; i < 6; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    select();
                }
            }, "线程" + i).start();
        }
    }

    /**
     * 查询程序
     */
    public static void select() {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        // 获取连接并执行SQL
        try {
            conn = pool.getConnection();
            pstmt = conn.prepareStatement("select * from student where id = 906");
            rs = pstmt.executeQuery();
            while (rs.next()) {
                System.out.println(Thread.currentThread().getName() + "\t" + rs.getString(1) + "\t" + rs.getString(2) + "\t" + rs.getString("address"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            /*
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            */
            pool.releaseConnection(conn);
        }
    }
}

2 使用动态代理修改原生连接的关闭方法

2.1 改进说明

简单的数据库连接池已经有了,但是在使用的时候如果调用了原生的关闭方法,会导致连接不能重复使用。

利用之前学过的动态代理进行改进,使调用关闭方法的时候执行的仍然是连接池里的释放资源的方法。

在DataPoolUtils工具类里添加动态代理的相关内部类:

/**
* 代理处理类
*/
class ConnectionInvocationHandler implements InvocationHandler{
    private Connection connection;
    private DataPoolUtils dpu;

    public ConnectionInvocationHandler(DataPoolUtils dpu, Connection connection) {
        this.dpu = dpu;
        this.connection = connection;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 对原生的关闭方法进行修改
        if(method.getName().equals("close")){
            dpu.releaseConnection(connection);
            return null;
        }else{
            return method.invoke(connection, args);
        }
    }
}

修改DataPoolUtils工具类中getConnection()方法的返回值,将返回值改为使用动态代理后的值:

return (Connection) Proxy.newProxyInstance(
    Connection.class.getClassLoader(),
    new Class[] { Connection.class },
    new ConnectionInvocationHandler(this, connection));

修改TestPool业务类中的select()方法,将释放连接改为关闭连接:

try {
    conn.close();
} catch (SQLException e) {
    e.printStackTrace();
}

2.2 注意说明

在工具类的getConnection()方法中返回代理类,而不是在工具类的createConnection()方法中返回,是因为通过后者得到的对象是要放到活动队列里的,如果在后者中返回代理对象,那么就会导致活动队列里的对象都是代理对象。

那么在执行代理对象的close()方法时,经过动态代理后,实际上是执行的是被代理对象的releaseConnection()方法,也就是将被代理对象从活动队列放到空闲队列,但因为活动队列里存放的都是代理对象,导致无法通过被代理对象从活动队列将代理对象放到空闲队列,进而导致连接资源并没有得到循环利用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值