使用代理模式手写简单的数据库连接池

JDBC直连数据库

我们都知道要连接数据库需要使用JDBC,所以我们先看个简单的demo。下面这个例子单纯的使用JDBC连接数据库并查询其中的数据

/**
 * 代理
 * @author skyline
 */
public class JDBCMain {
    private static final Logger logger = LoggerFactory.getLogger(JDBCMain.class);
    public static void main(String[] args) throws SQLException {
        logger.info("开始加载数据库驱动");
        Driver driver = new Driver();
        logger.info("数据库驱动加载成功,{}",ConnectionConst.DRIVER_CLASS_NAME );
        Properties properties = new Properties();
        properties.setProperty("user", ConnectionConst.USER);
        properties.setProperty("password", ConnectionConst.PASSWORD);
        logger.info("开始获取数据库连接");
        Connection connect = driver.connect(ConnectionConst.URL, properties);
        logger.info("数据库连接获取成功,{}",connect);
        PreparedStatement preparedStatement = connect.prepareStatement("select id,name,mathScore,languageScore,englishScore from student");
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()){
            logger.info("id:{},name:{}",resultSet.getString(1),resultSet.getString(2));
        }
        resultSet.close();
        logger.info("关闭resultSet");
        preparedStatement.close();
        logger.info("关闭preparedStatement");
        connect.close();
        logger.info("关闭connect");
    }
}

简单的跑一下,各项输出都正常
在这里插入图片描述

思考

  1. 上面的代码用来学习JDBC是完全没有问题的,但是在实际的项目中是没法使用的。总不能每查询一下数据都要创建一个新的连接吧。连接的创建是一个重量级的操作,创建完只用这一次就关闭实在是太浪费资源了
  2. 如果我们创建一个连接池,每次需要连接数据库时,从连接池中获取连接,使用完后,再归还连接,这样不就可以达到复用的目的了吗。
  3. 但是,还有一个问题。一般来说,每个连接在使用完毕后都需要调用close()方法来关闭连接,如果不关闭的话,会导致连接泄露。所以我们归还连接到连接池中的逻辑最好也写在close()内部,这样做一方面可以保证连接一定会被归还,另一方也降低了客户端代码的改造成本。

改造

因为需要改造Connectionclose方法,所以我们需要一个自己的Connection对象。我们使用自己的Connection对象代理具体的数据库的Connection对象。操作数据库的方法由具体的数据库Connection来处理,我们自己处理close方法

同时,我还希望我们自己的Connection对象可以延迟具体连接的创建,只有真正操作数据库时,才创建具体的数据库连接。

ConnectionProxy

/**
 * 一个链接的代理
 * @author skyline
 */
public class ConnectionProxy implements Connection {

    private static final Logger logger = LoggerFactory.getLogger(ConnectionProxy.class);
    private final ConnectionPool connectionPool;
    private Connection deleget;
    private boolean close = false;

    public ConnectionProxy(ConnectionPool connectionPool) {
        this.connectionPool = connectionPool;
    }

    void setClose(boolean close) {
        this.close = close;
    }

	//执行真正的close
    void physicsClose(){
        close = true;
        if (deleget != null) {
            try {
                deleget.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            deleget = null;
        }
    }

    @Override
    public boolean isClosed() throws SQLException {
        return close;
    }

    @Override
    public void close() throws SQLException {
        if(!close){
            close=true;
            if (connectionPool.isClose()) {
                physicsClose();
                logger.info("由于连接池关闭,所以连接直接关闭,{}", this);
            }else {
                connectionPool.getConnections().add(this);
                logger.info("连接归还,{}", this);
            }
        }
    }

    /**
     * 获取真实连接
     * @return
     * @throws SQLException
     */
    private Connection getPhysicsConnection() throws SQLException {
        if (close) {
            throw new RuntimeException("connection is closed!");
        }
        if (deleget == null) {
            deleget = connectionPool.getDriver().connect(connectionPool.getUrl(), connectionPool.getProperties());
        }
        return deleget;
    }

    @Override
    public Statement createStatement() throws SQLException {
        return getPhysicsConnection().createStatement();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return getPhysicsConnection().prepareStatement(sql);
    }
    //TODO 还有很多其他方法就不展示了
}

ConnectionPool

/**
 * 数据库连接池
 * @author skyline
 */
public class ConnectionPool {

    private static final Logger logger = LoggerFactory.getLogger(ConnectionPool.class);
    private final Driver driver;
    private final Properties properties;
    private final String url;
    private final ArrayBlockingQueue<ConnectionProxy> connections;
    private volatile boolean close;

    /**
     * 创建一个数据库连接池
     * @param url 连接url
     * @param userName 用户名
     * @param password 密码
     * @param coreSize 连接池中连接的个数
     * @throws SQLException
     */
    public ConnectionPool(String url, String userName, String password, int coreSize) throws SQLException {
        driver = new Driver();
        this.url = url;
        properties = new Properties();
        properties.setProperty("user", userName);
        properties.setProperty("password", password);
        logger.info("数据库驱动加载成功,{}",ConnectionConst.DRIVER_CLASS_NAME );
        connections = new ArrayBlockingQueue<>(coreSize);
        for (int i = 0; i < coreSize; i++) {
            connections.add(new ConnectionProxy(this));
        }
    }

    public boolean isClose(){
        return close;
    }

    public void close(){
        close = true;
        ConnectionProxy poll = connections.poll();
        while (poll!=null) {
            poll.physicsClose();
            poll = connections.poll();
        }
    }

    public Connection getConnection() throws InterruptedException {
        if (close) {
            throw new RuntimeException("connection pool is closed!");
        }
        ConnectionProxy take = connections.take();
        take.setClose(false);
        logger.info("获取链接,{}", take);
        return take;
    }

    public Connection getConnection(long time, TimeUnit timeUnit) throws InterruptedException {
        if (close) {
            throw new RuntimeException("connection pool is closed!");
        }
        ConnectionProxy poll = connections.poll(time, timeUnit);
        if (poll == null) {
            return null;
        }
        poll.setClose(false);
        logger.info("获取链接,{}", poll);
        return poll;
    }

    Driver getDriver() {
        return driver;
    }

    Properties getProperties() {
        return properties;
    }

    String getUrl() {
        return url;
    }

    ArrayBlockingQueue<ConnectionProxy> getConnections() {
        return connections;
    }
}

ProxyMain

public class ProxyMain {
    private static final Logger logger = LoggerFactory.getLogger(ProxyMain.class);
    public static void main(String[] args) throws SQLException, InterruptedException {
        int coreSize =2;
        ConnectionPool connectionPool = new ConnectionPool(ConnectionConst.URL, ConnectionConst.USER, ConnectionConst.PASSWORD, coreSize);
        for (int i = 0; i < coreSize*2; i++) {
            Thread thread = new Thread(()->{
                Connection connect = null;
                try {
                    long timeMillis = System.currentTimeMillis();
                    logger.info("开始获取连接");
                    connect = connectionPool.getConnection();
                    logger.info("[{}]毫秒后,数据库连接获取成功",System.currentTimeMillis()-timeMillis);
                    PreparedStatement preparedStatement = connect.prepareStatement("select id,name,mathScore,languageScore,englishScore from student");
                    ResultSet resultSet = preparedStatement.executeQuery();
                    while (resultSet.next()){
                        logger.info("id:{},name:{}",resultSet.getString(1),resultSet.getString(2));
                    }
                    resultSet.close();
                    logger.info("关闭resultSet");
                    preparedStatement.close();
                    logger.info("关闭preparedStatement");
                } catch (InterruptedException | SQLException e) {
                    e.printStackTrace();
                }finally {
                    if (connect != null) {
                        try {
                            connect.close();
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                        logger.info("关闭connect");
                    }
                }

            },"thread-"+i);
            thread.start();
        }
    }
}

运行结果

15:46:38.044 [main] INFO com.example.designpatterns.proxy.ConnectionPool - 数据库驱动加载成功,com.mysql.cj.jdbc.Driver
15:46:38.049 [thread-0] INFO com.example.designpatterns.proxy.ProxyMain - 开始获取连接
15:46:38.049 [thread-3] INFO com.example.designpatterns.proxy.ProxyMain - 开始获取连接
15:46:38.049 [thread-2] INFO com.example.designpatterns.proxy.ProxyMain - 开始获取连接
15:46:38.049 [thread-0] INFO com.example.designpatterns.proxy.ConnectionPool - 获取链接,com.example.designpatterns.proxy.ConnectionProxy@675a39c1
15:46:38.049 [thread-0] INFO com.example.designpatterns.proxy.ProxyMain - [0]毫秒后,数据库连接获取成功
15:46:38.049 [thread-1] INFO com.example.designpatterns.proxy.ProxyMain - 开始获取连接
15:46:38.049 [thread-3] INFO com.example.designpatterns.proxy.ConnectionPool - 获取链接,com.example.designpatterns.proxy.ConnectionProxy@58d1cd6f
15:46:38.050 [thread-3] INFO com.example.designpatterns.proxy.ProxyMain - [1]毫秒后,数据库连接获取成功
15:46:38.270 [thread-0] INFO com.example.designpatterns.proxy.ProxyMain - id:001,name:张三
15:46:38.270 [thread-3] INFO com.example.designpatterns.proxy.ProxyMain - id:001,name:张三
15:46:38.270 [thread-0] INFO com.example.designpatterns.proxy.ProxyMain - id:002,name:李四
15:46:38.270 [thread-3] INFO com.example.designpatterns.proxy.ProxyMain - id:002,name:李四
15:46:38.270 [thread-0] INFO com.example.designpatterns.proxy.ProxyMain - id:003,name:王五
15:46:38.270 [thread-3] INFO com.example.designpatterns.proxy.ProxyMain - id:003,name:王五
15:46:38.270 [thread-0] INFO com.example.designpatterns.proxy.ProxyMain - 关闭resultSet
15:46:38.270 [thread-3] INFO com.example.designpatterns.proxy.ProxyMain - 关闭resultSet
15:46:38.270 [thread-0] INFO com.example.designpatterns.proxy.ProxyMain - 关闭preparedStatement
15:46:38.270 [thread-3] INFO com.example.designpatterns.proxy.ProxyMain - 关闭preparedStatement
15:46:38.270 [thread-0] INFO com.example.designpatterns.proxy.ConnectionProxy - 连接归还,com.example.designpatterns.proxy.ConnectionProxy@675a39c1
15:46:38.270 [thread-3] INFO com.example.designpatterns.proxy.ConnectionProxy - 连接归还,com.example.designpatterns.proxy.ConnectionProxy@58d1cd6f
15:46:38.270 [thread-3] INFO com.example.designpatterns.proxy.ProxyMain - 关闭connect
15:46:38.270 [thread-0] INFO com.example.designpatterns.proxy.ProxyMain - 关闭connect
15:46:38.270 [thread-2] INFO com.example.designpatterns.proxy.ConnectionPool - 获取链接,com.example.designpatterns.proxy.ConnectionProxy@675a39c1
15:46:38.270 [thread-1] INFO com.example.designpatterns.proxy.ConnectionPool - 获取链接,com.example.designpatterns.proxy.ConnectionProxy@58d1cd6f
15:46:38.270 [thread-2] INFO com.example.designpatterns.proxy.ProxyMain - [221]毫秒后,数据库连接获取成功
15:46:38.270 [thread-1] INFO com.example.designpatterns.proxy.ProxyMain - [221]毫秒后,数据库连接获取成功
15:46:38.271 [thread-1] INFO com.example.designpatterns.proxy.ProxyMain - id:001,name:张三
15:46:38.271 [thread-2] INFO com.example.designpatterns.proxy.ProxyMain - id:001,name:张三
15:46:38.271 [thread-1] INFO com.example.designpatterns.proxy.ProxyMain - id:002,name:李四
15:46:38.271 [thread-2] INFO com.example.designpatterns.proxy.ProxyMain - id:002,name:李四
15:46:38.271 [thread-1] INFO com.example.designpatterns.proxy.ProxyMain - id:003,name:王五
15:46:38.271 [thread-1] INFO com.example.designpatterns.proxy.ProxyMain - 关闭resultSet
15:46:38.271 [thread-1] INFO com.example.designpatterns.proxy.ProxyMain - 关闭preparedStatement
15:46:38.271 [thread-1] INFO com.example.designpatterns.proxy.ConnectionProxy - 连接归还,com.example.designpatterns.proxy.ConnectionProxy@58d1cd6f
15:46:38.271 [thread-1] INFO com.example.designpatterns.proxy.ProxyMain - 关闭connect
15:46:38.274 [thread-2] INFO com.example.designpatterns.proxy.ProxyMain - id:003,name:王五
15:46:38.275 [thread-2] INFO com.example.designpatterns.proxy.ProxyMain - 关闭resultSet
15:46:38.275 [thread-2] INFO com.example.designpatterns.proxy.ProxyMain - 关闭preparedStatement
15:46:38.275 [thread-2] INFO com.example.designpatterns.proxy.ConnectionProxy - 连接归还,com.example.designpatterns.proxy.ConnectionProxy@675a39c1
15:46:38.275 [thread-2] INFO com.example.designpatterns.proxy.ProxyMain - 关闭connect

从运行结果中可以看出连接池已经起到作用了。

  1. 当连接池中没有可用的连接时,业务代码被阻塞,直到获取到可用的连接。
  2. 连接被复用了,一共从连接池中获取4次连接,但是实际上只有ConnectionProxy@675a39c1ConnectionProxy@58d1cd6f两个对象。

代理模式

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

与装饰器的区别

代理模式与装饰器模式很像,都是需要依赖相同的接口或父类。但是装饰器模式由客户端发起对象的“装饰”,而代理模式是服务端创建好代理的对象直接给客户端使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值