JDBC数据库连接池 怎么实现

发布时间:2021-12-21 16:17:02 作者:iii
来源:亿速云 阅读:123

本篇内容介绍了“JDBC数据库连接池 怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

什么情况下使用连接池?

对于一个简单的数据库应用,由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。但是对于一个复杂的数据库应用,情况就完全不同了。频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈。

使用连接池的好处

  1. 连接复用。通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。

  2. 对于共享资源,有一个很著名的设计模式:资源池。该模式正是为了解决资源频繁分配、释放所造成的问题的。把该模式应用到数据库连接管理领域,就是建立一个数据库连接池,提供一套高效的连接分配、使用策略,最终目标是实现连接的高效、安全的复用。

连接池的实现

数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。

外部使用者可通过 getConnection 方法获取连接,使用完毕后再通过 close 方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。

Java 中有一个 DataSource 接口, 数据库连接池就是 DataSource 的一个实现

下面我们自己实现一个数据库连接池:

首先实现 DataSource, 这里使用 BlockingQueue 作为池 (只保留了关键代码)

import javax.sql.DataSource;import java.io.PrintWriter;import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;import java.sql.SQLFeatureNotSupportedException;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import java.util.logging.Logger;public class MyDataSource implements DataSource {
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //这里有个坑
    //MySQL用的是5.5的
    //驱动用的是最新的
    //连接的时候会报The server time zone value '�й���׼ʱ��'
    // is unrecognized or represents more than one time zone
    //解决方法:
    //1.在连接串中加入?serverTimezone=UTC
    //2.在mysql中设置时区,默认为SYSTEM
    //set global time_zone='+8:00'
    private String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC";
    private String user = "root";
    private String password = "123456";
    private BlockingQueue<Connection> pool = new ArrayBlockingQueue<>(3);
    public MyDataSource() {
        initPool();
    }
    private void initPool() {
        try {
            for (int i = 0; i < 3; i++) {
                pool.add(new MyConnection(
                        DriverManager.getConnection(url, user, password), this));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    /*
    从池中获取连接
     */
    @Override
    public synchronized Connection getConnection() throws SQLException {
        try {
            return pool.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("get connection failed!");
    }
    public BlockingQueue<Connection> getPool() {
        return pool;
    }
    public void setPool(BlockingQueue<Connection> pool) {
        this.pool = pool;
    }}

实现自己的连接, 对原生连接进行封装, 调用 close 方法的时候将连接放回到池中

import java.sql.*; import java.util.Map; import java.util.Properties; importjava.util.concurrent.Executor; public class MyConnection implements Connection { //包装的连接private Connection conn; private MyDataSource dataSource; public MyConnection(Connection conn, MyDataSource dataSource) { this.conn = conn; this.dataSource = dataSource; } @Overridepublic Statement createStatement() throws SQLException { return conn.createStatement(); }@Override public PreparedStatement prepareStatement(String sql) throws SQLException { returnconn.prepareStatement(sql); } @Override public boolean getAutoCommit() throws SQLException {return conn.getAutoCommit(); } @Override public void setAutoCommit(boolean autoCommit) throwsSQLException { conn.setAutoCommit(autoCommit); } @Override public void commit() throwsSQLException { conn.commit(); } @Override public void rollback() throws SQLException { conn.rollback(); } @Override public void close() throws SQLException { //解决重复关闭问题 if(!isClosed()) { dataSource.getPool().add(this); } } @Override public boolean isClosed()throws SQLException { return dataSource.getPool().contains(this); } }

main 方法

import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException;import java.sql.Statement; public class Main { public static void main(String[] args) { DataSource source = new MyDataSource(); try { Connection conn = source.getConnection(); Statement st = conn.createStatement(); st.execute("INSERT INTO USER (name,age) values('bob',12)"); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

数据源连接池的原理及Tomcat中的应用

在Java Web开发过程中,会广泛使用到数据源。
我们基本的使用方式,是通过Spring使用类似如下的配置,来声明一个数据源:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="100" />
        <property name="maxIdle" value="20" />
        <property name="validationQuery" value="SELECT 1 from dual" />
        <property name="testOnBorrow" value="true" />
    </bean>

在之后应用里对于数据库的操作,都基于这个数据源,但这个数据源连接池的创建、销毁、管理,对于用户都是近乎透明的,甚至数据库连接的获取,我们都看不到Connection对象了。
这种方式是应用自身的数据库连接池,各个应用之间互相独立。

在类似于Tomcat这样的应用服务器内部,也有提供数据源的能力,这时的数据源,可以为多个应用提供服务。

这一点类似于以前写过关于Tomcat内部的Connector对于线程池的使用,可以各个Connector独立使用线程池,也可以共用配置的Executor。( Tomcat的Connector组件 )

那么,在Tomcat中,怎么样配置和使用数据源呢?

  1. 先将对应要使用的数据库的驱动文件xx.jar放到TOMCAT_HOME/lib目录下。

  2. 编辑TOMCAT_HOME/conf/context.xml文件,增加类似于下面的内容:

<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
          maxTotal="100" maxIdle="30" maxWaitMillis="10000"
          username="root" password="pwd" driverClassName="com.mysql.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/test"/>
  1. 需要提供数据源的应用内,使用JNDI的方式获取

Context initContext = new InitialContext();Context envContext  = (Context)initContext.lookup("java:/comp/env");DataSource ds = (DataSource)envContext.lookup("jdbc/TestDB");Connection conn = ds.getConnection();
  1. 愉快的开始使用数据库…

我们看,整个过程也并不比使用Spring等框架进行配置复杂,在应用内获取连接也很容易。多个应用都可以通过第3步的方式获取数据源,这使得同时提供多个应用共享数据源很容易。

这背后的是怎么实现的呢?

这个容器的连接池是怎么工作的呢,我们一起来看一看。

在根据context.xml中配置的Resouce初始化的时候,会调用具体DataSource对应的实现类,Tomcat内部默认使用的BasicDataSource,在类初始化的时候,会执行这样一行代码DriverManager.getDrivers(),其对应的内容如下,主要作用是使用 java.sql.DriverManager实现的Service Provider机制,所有jar文件包含META-INF/services/java.sql.Driver文件的,会被自动发现、加载和注册,不需要在需要获取连接的时候,再手动的加载和注册。

public static java.util.Enumeration<Driver> getDrivers() {
        java.util.Vector<Driver> result = new java.util.Vector<>();
        for(DriverInfo aDriver : registeredDrivers) {
            if(isDriverAllowed(aDriver.driver, callerClass)) {
                result.addElement(aDriver.driver);
            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }
        }
        return (result.elements());
    }

之后DataSourceFactory会读取Resouce中指定的数据源的属性,创建数据源。

在我们的应用内getConnection的时候,使用ConnectionFactory创建Connection, 注意在创建Connection的时候,重点代码是这个样子:

public PooledObject<PoolableConnection> makeObject() throws Exception {
        Connection conn = _connFactory.createConnection();
        initializeConnection(conn);
        PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName);
        return new DefaultPooledObject<>(pc);

这里的_pool是GenericObjectPool,连接的获取是通过其进行的。

public Connection getConnection() throws SQLException {
        C conn = _pool.borrowObject();
}

在整个pool中包含几个队列,其中比较关键的一个定义如下:

private final LinkedBlockingDeque<PooledObject<T>> idleObjects;

我们再看连接的关闭,

public void close() throws SQLException {
    if (getDelegateInternal() != null) {
        super.close();
        super.setDelegate(null);
    }
}

这里的关闭,并不会真的调用到Connection的close方法,我们通过上面的代码已经看到,Connection返回的时候,其实是Connection的Wrapper类。在close的时候,真实的会调用到下面的代码

// Normal close: underlying connection is still open, so we
           // simply need to return this proxy to the pool
           try {
               _pool.returnObject(this);
           } catch(IllegalStateException e) {}

所谓的return,是把连接放回到上面我们提到的idleObjects队列中。整个连接是放在一个LIFO的队列中,所以如果没有关闭或者超过最大空闲连接,就会加到队列中。而允许外的连接才会真实的销毁destory

int maxIdleSave = getMaxIdle();
        if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
            try {
                destroy(p);
            } catch (Exception e) {
                swallowException(e);
            }
        } else {
            if (getLifo()) {
                idleObjects.addFirst(p); // 这里。
            } else {
                idleObjects.addLast(p);
            }
            if (isClosed()) {
                // Pool closed while object was being added to idle objects.
                // Make sure the returned object is destroyed rather than left                // in the idle object pool (which would effectively be a leak)
                clear();
            }
        }

“JDBC数据库连接池 怎么实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

推荐阅读:
  1. JDBC笔记介绍
  2. JDBC技术

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

jdbc

上一篇:怎么实现真正的linux不停机数据迁移

下一篇:怎么给你的OSS资源加上监控

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》