在Java工程项目中,咱们常会用到Mybatis
框架对数据库中的数据进行增删查改,其原理就是对 JDBC
作了一层封装,并优化数据源的链接。java
咱们先来回顾下 JDBC
操做数据库的过程。mysql
JDBC
操做数据库
JDBC
操做数据库的时候须要指定 链接类型、加载驱动、创建链接、最终执行 SQL
语句,代码以下:sql
public static final String url = "jdbc:mysql://127.0.0.1/somedb"; public static final String name = "com.mysql.jdbc.Driver"; public static final String user = "root"; public static final String password = "root"; public Connection conn = null; public PreparedStatement pst = null; public DBHelper(String sql) { try { //指定链接类型 Class.forName(name); //创建链接 conn = DriverManager.getConnection(url, user, password); //准备执行语句 pst = conn.prepareStatement(sql); } catch (Exception e) { e.printStackTrace(); } } public void close() { try { this.conn.close(); this.pst.close(); } catch (SQLException e) { e.printStackTrace(); } }
一个SQL
的执行,若是使用JDBC
进行处理,须要通过 加载驱动、创建链接、再执行SQL
的一个过程,当下一个SQL
到来的时候,还须要进行一次这个流程,这就形成没必要要的性能损失,并且随着用户操做的逐渐增多,每次都和数据库创建链接对数据库自己来讲也是一种压力。数据库
为了减小这种没必要要的消耗,能够对数据的操做进行拆分。在Mybatis
中,数据库链接的创建和管理的部分叫作数据库链接池。mybatis
Mybatis
数据源DateSource
的分类
UNPOOLED
UNPOOLED
不使用链接池的数据源,当 dateSource
的type属性被配置成了UNPOOLED
,MyBatis
首先会实例化一个UnpooledDataSourceFactory
工厂实例,而后经过 .getDataSource()
方法返回一个UnpooledDataSource
实例对象引用,咱们假定为dataSource
。框架
使用 UnpooledDataSource
的 getConnection()
,每调用一次就会产生一个新的 Connection
实例对象。UnPooledDataSource
的 getConnection()
方法实现以下:性能
public class UnpooledDataSource implements DataSource { private ClassLoader driverClassLoader; private Properties driverProperties; private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap(); private String driver; private String url; private String username; private String password; private Boolean autoCommit; private Integer defaultTransactionIsolationLevel; public UnpooledDataSource() { } public UnpooledDataSource(String driver, String url, String username, String password){ this.driver = driver; this.url = url; this.username = username; this.password = password; } public Connection getConnection() throws SQLException { return this.doGetConnection(this.username, this.password); } private Connection doGetConnection(String username, String password) throws SQLException { Properties props = new Properties(); if(this.driverProperties != null) { props.putAll(this.driverProperties); } if(username != null) { props.setProperty("user", username); } if(password != null) { props.setProperty("password", password); } return this.doGetConnection(props); } private Connection doGetConnection(Properties properties) throws SQLException { this.initializeDriver(); Connection connection = DriverManager.getConnection(this.url, properties); this.configureConnection(connection); return connection; } }
如上代码所示,UnpooledDataSource
会作如下事情:优化
driver
类,并实例化一个Driver
对象,使用DriverManager.registerDriver()
方法将其注册到内存中,以供后续使用。DriverManager.getConnection()
方法建立链接。autoCommit
和隔离级别isolationLevel
。从上述的代码中能够看到,咱们每调用一次
getConnection()
方法,都会经过DriverManager.getConnection()
返回新的java.sql.Connection
实例,因此没有链接池。
POOLED
数据源 链接池PooledDataSource: 将java.sql.Connection
对象包裹成PooledConnection
对象放到了PoolState
类型的容器中维护。 MyBatis
将链接池中的PooledConnection
分为两种状态: 空闲状态(idle)和活动状态(active),这两种状态的PooledConnection
对象分别被存储到PoolState
容器内的idleConnections
和activeConnections
两个List集合中:this
idleConnections: 空闲(idle)状态PooledConnection
对象被放置到此集合中,表示当前闲置的没有被使用的PooledConnection
集合,调用PooledDataSource
的getConnection()
方法时,会优先今后集合中取PooledConnection
对象。当用完一个java.sql.Connection对象时,MyBatis
会将其包裹成PooledConnection
对象放到此集合中。url
activeConnections: 活动(active)状态的PooledConnection
对象被放置到名为activeConnections
的ArrayList
中,表示当前正在被使用的PooledConnection
集合,调用PooledDataSource
的getConnection()
方法时,会优先从idleConnections
集合中取PooledConnection
对象,若是没有,则看此集合是否已满,若是未满,PooledDataSource
会建立出一个PooledConnection
,添加到此集合中,并返回
如今让咱们看一下popConnection()
方法到底作了什么:
PooledConnection
对象,若是有,就直接返回一个可用的PooledConnection
对象;不然进行第2步。PooledConnection
池activeConnections
是否已满;若是没有满,则建立一个新的PooledConnection
对象,而后放到activeConnections
池中,而后返回此PooledConnection
对象;不然进行第三步;activeConnections
池中的PooledConnection
对象是否已通过期:若是已通过期,从activeConnections
池中移除此对象,而后建立一个新的PooledConnection
对象,添加到activeConnections
中,而后将此对象返回;不然进行第4步。/* * 传递一个用户名和密码,从链接池中返回可用的PooledConnection */ private PooledConnection popConnection(String username, String password) throws SQLException { boolean countedWait = false; PooledConnection conn = null; long t = System.currentTimeMillis(); int localBadConnectionCount = 0; while (conn == null) { synchronized (state) { if (state.idleConnections.size() > 0) { // 链接池中有空闲链接,取出第一个 conn = state.idleConnections.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); } } else { // 链接池中没有空闲链接,则取当前正在使用的链接数小于最大限定值, if (state.activeConnections.size() < poolMaximumActiveConnections) { // 建立一个新的connection对象 conn = new PooledConnection(dataSource.getConnection(), this); @SuppressWarnings("unused") //used in logging, if enabled Connection realConn = conn.getRealConnection(); if (log.isDebugEnabled()) { log.debug("Created connection " + conn.getRealHashCode() + "."); } } else { // Cannot create new connection 当活动链接池已满,不能建立时,取出活动链接池的第一个,即最早进入链接池的PooledConnection对象 // 计算它的校验时间,若是校验时间大于链接池规定的最大校验时间,则认为它已通过期了,利用这个PoolConnection内部的realConnection从新生成一个PooledConnection // PooledConnection oldestActiveConnection = state.activeConnections.get(0); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); if (longestCheckoutTime > poolMaximumCheckoutTime) { // Can claim overdue connection state.claimedOverdueConnectionCount++; state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; state.accumulatedCheckoutTime += longestCheckoutTime; state.activeConnections.remove(oldestActiveConnection); if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { oldestActiveConnection.getRealConnection().rollback(); } conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); oldestActiveConnection.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); } } else { //若是不能释放,则必须等待有 // Must wait try { if (!countedWait) { state.hadToWaitCount++; countedWait = true; } if (log.isDebugEnabled()) { log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection."); } long wt = System.currentTimeMillis(); state.wait(poolTimeToWait); state.accumulatedWaitTime += System.currentTimeMillis() - wt; } catch (InterruptedException e) { break; } } } } //若是获取PooledConnection成功,则更新其信息 if (conn != null) { if (conn.isValid()) { if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); conn.setCheckoutTimestamp(System.currentTimeMillis()); conn.setLastUsedTimestamp(System.currentTimeMillis()); state.activeConnections.add(conn); state.requestCount++; state.accumulatedRequestTime += System.currentTimeMillis() - t; } else { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection."); } state.badConnectionCount++; localBadConnectionCount++; conn = null; if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Could not get a good connection to the database."); } throw new SQLException("PooledDataSource: Could not get a good connection to the database."); } } } } } if (conn == null) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } return conn; }
java.sql.Connection
对象的回收
当咱们的程序中使用完Connection
对象时,若是不使用数据库链接池,咱们通常会调用 connection.close()
方法,关闭connection
链接,释放资源
调用过close()
方法的Connection
对象所持有的资源会被所有释放掉,Connection
对象也就不能再使用。那么,若是咱们使用了链接池,咱们在用完了Connection
对象时,须要将它放在链接池中,该怎样作呢?
可能你们第一个在脑海里闪现出来的想法就是:我在应该调用con.close()
方法的时候,不调用close()
方法,将其换成将Connection
对象放到链接池容器中的代码!
怎样实现Connection
对象调用了close()
方法,而实际是将其添加到链接池中
这是要使用代理模式,为真正的Connection
对象建立一个代理对象,代理对象全部的方法都是调用相应的真正Connection
对象的方法实现。当代理对象执行close()
方法时,要特殊处理,不调用真正Connection
对象的close()
方法,而是将Connection
对象添加到链接池中。
MyBatis
的PooledDataSource
的PoolState
内部维护的对象是PooledConnection
类型的对象,而PooledConnection
则是对真正的数据库链接java.sql.Connection
实例对象的包裹器。
PooledConnection
对象内持有一个真正的数据库链接java.sql.Connection
实例对象和一个java.sql.Connection
的代理:
其源码以下:
class PooledConnection implements InvocationHandler { private static final String CLOSE = "close"; private static final Class<?>[] IFACES = new Class[]{Connection.class}; private int hashCode = 0; private PooledDataSource dataSource; private Connection realConnection; private Connection proxyConnection; private long checkoutTimestamp; private long createdTimestamp; private long lastUsedTimestamp; private int connectionTypeCode; private boolean valid; public PooledConnection(Connection connection, PooledDataSource dataSource) { this.hashCode = connection.hashCode(); this.realConnection = connection; this.dataSource = dataSource; this.createdTimestamp = System.currentTimeMillis(); this.lastUsedTimestamp = System.currentTimeMillis(); this.valid = true; this.proxyConnection = (Connection)Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); //当close时候,会回收 connection , 不会真正的close if("close".hashCode() == methodName.hashCode() && "close".equals(methodName)) { this.dataSource.pushConnection(this); return null; } else { try { if(!Object.class.equals(method.getDeclaringClass())) { this.checkConnection(); } return method.invoke(this.realConnection, args); } catch (Throwable var6) { throw ExceptionUtil.unwrapThrowable(var6); } } } }