手写java数据库连接池,自定义实现数据库连接池,兼容springboot

一、目标

          用精简的代码实现一个类似于Hikari,Druid一样的高性能数据库连接池。

二、实现思路

       1:新建连接池配置类保存连接池配置。

       2:实现DataSource接口。

       3:新增SmpDbPool类,内部维护一个阻塞队列保存数据库连接,并提供数据库连接的获取回收等方法。

       4:大致类图:

三、核心代码

1:数据库连接代理类SmpConnectionProxy

SmpConnectionProxy实现jdbc的Connection接口

重写close方法,确保外部调用close的时候将连接归还到连接池。

public void close() throws SQLException {
        if (this.closed) return;

        this.closed = true;
        //回滚掉未提交的脏statement
        if (this.isCommitStateDirty && !this.isAutoCommit){
            this.delegate.rollback();//
        }
        this.clearWarnings();
        this.smpDbPool.releaseConnection(this);
    }

新增判断连接是否有效方法供连接池类使用。

public boolean isValid() throws SQLException {
        if (System.currentTimeMillis() - this.lastActiveTime < this.smpDbPool.getConfig().getKeepaliveTimeInMill()) return true;

        long validationTimeout = this.smpDbPool.getConfig().getValidationTimeoutInMill();
        this.delegate.setNetworkTimeout(this.smpDbPool.getNetTimeoutExecutor(), (int) validationTimeout);
        int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000;

        return this.delegate.isValid(validationSeconds);
    }

2:数据源类SmpDataSource

SmpDataSource实现DataSource接口

实现getConnection方法,以便外界从连接池获取连接。

@Override
    public Connection getConnection() throws SQLException {
        try {
            return this.dbPool.borrowConnection();
        } catch (Exception e) {
            LOG.error("getConnection error", e);
            throw new SQLException(e);
        }
    }

3:连接池类SmpDbPool

获取数据库连接方法:

public Connection borrowConnection() throws Exception {
        boolean isDebugEnable = LOG.isDebugEnabled();
        long sTime = 0;
        if (isDebugEnable){//尽量少访问临界资源
            sTime = System.currentTimeMillis();
        }
        try {
            SmpConnectionProxy connection = this.getConnectionFromQueue();
            if (connection != null) return connection;

            //未获取到有效连接,校验是否创建连接
            int maxActive = this.smpConfig.getMaxActive(), dbAmount = createdDbConnectionAmount.get();
            if (dbAmount >= maxActive){
                //尝试等待其他线程释放连接
                connection = this.dbConnectQueue.poll(this.smpConfig.getMaxWaitInMill(), TimeUnit.MILLISECONDS);
                if (connection != null) return connection.borrowConnection();

                LOG.error("get connection error, no connection available,maxActive:{}, activated connection:{}", maxActive, dbAmount);
                throw new IllegalStateException("database connection pool too busy");
            }
            connection = this.createConnectionForPool();
            if (connection != null) return connection;

            throw new IllegalStateException("database connection pool too busy");
        } finally {
            if (isDebugEnable){
                LOG.debug("get database connection cost {} ms", System.currentTimeMillis() - sTime);
            }
        }
    }

从阻塞队列获取数据库连接方法:

private SmpConnectionProxy getConnectionFromQueue() throws SQLException {
        int times = 0;
        while (times++ < 1000){
            SmpConnectionProxy connection = this.dbConnectQueue.poll();
            if (connection == null) return null;

            if (connection.isValid()) return connection.borrowConnection();

            //destroy invalid connection
            connection.destroy();
            createdDbConnectionAmount.decrementAndGet();
            LOG.info("destroyed invalid jdbc connection");
        }
        return null;
    }

新建数据库连接:

private SmpConnectionProxy createConnectionForPool() throws Exception{
        boolean locked = false;
        try {
            locked = GET_CONNECTION_LOCK.tryLock(this.smpConfig.getMaxWaitInMill() >> 1, TimeUnit.MILLISECONDS);
            if (!locked){
                LOG.error("get lock to create connection error");
                return null;
            }
            SmpConnectionProxy connection = this.getConnectionFromQueue();
            if (connection != null) return connection;

            int dbAmount = createdDbConnectionAmount.get(), maxActive = this.smpConfig.getMaxActive();
            if (dbAmount >= maxActive){
                LOG.error("get connection error, no connection available,maxActive:{}, activated connection:{}", maxActive, dbAmount);
                return null;
            }
            connection = this.createConnection();
            createdDbConnectionAmount.addAndGet(1);
            return connection.borrowConnection();
        } catch (ClassNotFoundException ex){
            LOG.error("class {} not found", this.smpConfig.getDriverClassName(), ex);
            throw new SQLException(ex);
        }finally {
            if (locked){
                GET_CONNECTION_LOCK.unlock();
            }
        }
    }

private SmpConnectionProxy createConnection() throws ClassNotFoundException, SQLException {
        Class.forName( this.smpConfig.getDriverClassName() );
        Connection connection = DriverManager.getConnection( this.smpConfig.getUrl(), this.smpConfig.getUsername(), this.smpConfig.getPassword());
        LOG.debug("create new database connection with url:{}", this.smpConfig.getUrl());
        return new SmpConnectionProxy(connection, this, connection.getAutoCommit());
    }

4:构建spring-boot-starter

         这里需要说明的是springboot默认会尝试使用com.zaxxer.hikari.HikariDataSource,org.apache.tomcat.jdbc.pool.DataSource,org.apache.commons.dbcp2.BasicDataSource作为连接池,相关代码逻辑在org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration,因此配置spring-boot-starter的时候先要确保应用没有配置连接池。

核心代码:

 5:springboot使用

添加依赖后再添加如下配置:

spring.datasource.type=com.lauor.smpdb.SmpDataSource
#最小连接数,默认4
spring.datasource.smpdb.minIdle=4
#最大连接数,默认40
spring.datasource.smpdb.maxActive=20
#数据库连接有效期检查间隔时间ms,默认30分钟,最小1s
spring.datasource.smpdb.keepaliveTimeInMill=1800000
#获取数据库连接最大等待时间ms,默认30s,最小1s
spring.datasource.smpdb.maxWaitInMill=30000
#数据连接合法性校验超时时间,默认5s,最小1s
spring.datasource.smpdb.validationTimeoutInMill=5000

连接池完整代码:https://gitee.com/tandatda/smpdb

连接池使用完整demo:https://gitee.com/tandatda/demo-edr-smpdb

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是Java手写动态数据库连接池的示例代码: ``` import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class ConnectionPool { private String url; private String username; private String password; private List<Connection> connections = new ArrayList<>(); private int maxConnections; public ConnectionPool(String url, String username, String password, int maxConnections) { this.url = url; this.username = username; this.password = password; this.maxConnections = maxConnections; } public synchronized Connection getConnection() throws SQLException { if (connections.isEmpty()) { if (maxConnections == 0 || connections.size() < maxConnections) { Connection conn = DriverManager.getConnection(url, username, password); connections.add(conn); return conn; } else { throw new SQLException("Connection pool is full"); } } else { Connection conn = connections.remove(0); if (conn.isClosed()) { return getConnection(); } else { return conn; } } } public synchronized void releaseConnection(Connection conn) { connections.add(conn); } public synchronized void closeAllConnections() throws SQLException { for (Connection conn : connections) { conn.close(); } connections.clear(); } } ``` 使用方法: ``` ConnectionPool connectionPool = new ConnectionPool("jdbc:mysql://localhost:3306/mydb", "user", "password", 10); Connection conn = connectionPool.getConnection(); // Use the connection for database operations connectionPool.releaseConnection(conn); connectionPool.closeAllConnections(); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值