Mybatis源码分析-----Mybatis数据源与连接池(2)

六、使用了连接池的PooledDataSource

同样地,我们也是使用PooledDataSource的getConnection()方法来返回Connection对象。现在让我们看一下它的基本原理:

 PooledDataSource将java.sql.Connection对象包裹成PooledConnection对象放到了PoolState类型的容器中维护。 MyBatis将连接池中的PooledConnection分为两种状态: 空闲状态(idle)和活动状态(active),这两种状态的PooledConnection对象分别被存储到PoolState容器内的idleConnectionsactiveConnections两个List集合中:

idleConnections:空闲(idle)状态PooledConnection对象被放置到此集合中,表示当前闲置的没有被使用的PooledConnection集合,调用PooledDataSource的getConnection()方法时,会优先从此集合中取PooledConnection对象。当用完一个java.sql.Connection对象时,MyBatis会将其包裹成PooledConnection对象放到此集合中。

activeConnections:活动(active)状态的PooledConnection对象被放置到名为activeConnections的ArrayList中,表示当前正在被使用的PooledConnection集合,调用PooledDataSource的getConnection()方法时,会优先从idleConnections集合中取PooledConnection对象,如果没有,则看此集合是否已满,如果未满,PooledDataSource会创建出一个PooledConnection,添加到此集合中,并返回。

  

PoolState连接池的大致结构如下所示:

6.1 获取java.sql.Connection对象的过程

下面让我们看一下PooledDataSource 的getConnection()方法获取Connection对象的实现:
 
  1. public Connection getConnection() throws SQLException {

  2. return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();

  3. }

  4.  
  5. public Connection getConnection(String username, String password) throws SQLException {

  6. return popConnection(username, password).getProxyConnection();

  7. }

 

上述的popConnection()方法,会从连接池中返回一个可用的PooledConnection对象,然后再调用getProxyConnection()方法最终返回Conection对象。(至于为什么会有getProxyConnection(),请关注下一节)

现在让我们看一下popConnection()方法到底做了什么:

1.  先看是否有空闲(idle)状态下的PooledConnection对象,如果有,就直接返回一个可用的PooledConnection对象;否则进行第2步。

2.  查看活动状态的PooledConnection池activeConnections是否已满;如果没有满,则创建一个新的PooledConnection对象,然后放到activeConnections池中,然后返回此PooledConnection对象;否则进行第三步;

3.  看最先进入activeConnections池中的PooledConnection对象是否已经过期:如果已经过期,从activeConnections池中移除此对象,然后创建一个新的PooledConnection对象,添加到activeConnections中,然后将此对象返回;否则进行第4步。

4.  线程等待,循环2步

 
  1. /*

  2. * 传递一个用户名和密码,从连接池中返回可用的PooledConnection

  3. */

  4. private PooledConnection popConnection(String username, String password) throws SQLException

  5. {

  6. boolean countedWait = false;

  7. PooledConnection conn = null;

  8. long t = System.currentTimeMillis();

  9. int localBadConnectionCount = 0;

  10.  
  11. while (conn == null)

  12. {

  13. synchronized (state)

  14. {

  15. if (state.idleConnections.size() > 0)

  16. {

  17. // 连接池中有空闲连接,取出第一个

  18. conn = state.idleConnections.remove(0);

  19. if (log.isDebugEnabled())

  20. {

  21. log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");

  22. }

  23. }

  24. else

  25. {

  26. // 连接池中没有空闲连接,则取当前正在使用的连接数小于最大限定值,

  27. if (state.activeConnections.size() < poolMaximumActiveConnections)

  28. {

  29. // 创建一个新的connection对象

  30. conn = new PooledConnection(dataSource.getConnection(), this);

  31. @SuppressWarnings("unused")

  32. //used in logging, if enabled

  33. Connection realConn = conn.getRealConnection();

  34. if (log.isDebugEnabled())

  35. {

  36. log.debug("Created connection " + conn.getRealHashCode() + ".");

  37. }

  38. }

  39. else

  40. {

  41. // Cannot create new connection 当活动连接池已满,不能创建时,取出活动连接池的第一个,即最先进入连接池的PooledConnection对象

  42. // 计算它的校验时间,如果校验时间大于连接池规定的最大校验时间,则认为它已经过期了,利用这个PoolConnection内部的realConnection重新生成一个PooledConnection

  43. //

  44. PooledConnection oldestActiveConnection = state.activeConnections.get(0);

  45. long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();

  46. if (longestCheckoutTime > poolMaximumCheckoutTime)

  47. {

  48. // Can claim overdue connection

  49. state.claimedOverdueConnectionCount++;

  50. state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;

  51. state.accumulatedCheckoutTime += longestCheckoutTime;

  52. state.activeConnections.remove(oldestActiveConnection);

  53. if (!oldestActiveConnection.getRealConnection().getAutoCommit())

  54. {

  55. oldestActiveConnection.getRealConnection().rollback();

  56. }

  57. conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);

  58. oldestActiveConnection.invalidate();

  59. if (log.isDebugEnabled())

  60. {

  61. log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");

  62. }

  63. }

  64. else

  65. {

  66.  
  67. //如果不能释放,则必须等待有

  68. // Must wait

  69. try

  70. {

  71. if (!countedWait)

  72. {

  73. state.hadToWaitCount++;

  74. countedWait = true;

  75. }

  76. if (log.isDebugEnabled())

  77. {

  78. log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");

  79. }

  80. long wt = System.currentTimeMillis();

  81. state.wait(poolTimeToWait);

  82. state.accumulatedWaitTime += System.currentTimeMillis() - wt;

  83. }

  84. catch (InterruptedException e)

  85. {

  86. break;

  87. }

  88. }

  89. }

  90. }

  91.  
  92. //如果获取PooledConnection成功,则更新其信息

  93.  
  94. if (conn != null)

  95. {

  96. if (conn.isValid())

  97. {

  98. if (!conn.getRealConnection().getAutoCommit())

  99. {

  100. conn.getRealConnection().rollback();

  101. }

  102. conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));

  103. conn.setCheckoutTimestamp(System.currentTimeMillis());

  104. conn.setLastUsedTimestamp(System.currentTimeMillis());

  105. state.activeConnections.add(conn);

  106. state.requestCount++;

  107. state.accumulatedRequestTime += System.currentTimeMillis() - t;

  108. }

  109. else

  110. {

  111. if (log.isDebugEnabled())

  112. {

  113. log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");

  114. }

  115. state.badConnectionCount++;

  116. localBadConnectionCount++;

  117. conn = null;

  118. if (localBadConnectionCount > (poolMaximumIdleConnections + 3))

  119. {

  120. if (log.isDebugEnabled())

  121. {

  122. log.debug("PooledDataSource: Could not get a good connection to the database.");

  123. }

  124. throw new SQLException("PooledDataSource: Could not get a good connection to the database.");

  125. }

  126. }

  127. }

  128. }

  129.  
  130. }

  131.  
  132. if (conn == null)

  133. {

  134. if (log.isDebugEnabled())

  135. {

  136. log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");

  137. }

  138. throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");

  139. }

  140.  
  141. return conn;

  142. }

对应的处理流程图如下所示:

如上所示,对于PooledDataSource的getConnection()方法内,先是调用类PooledDataSource的popConnection()方法返回了一个PooledConnection对象,然后调用了PooledConnection的getProxyConnection()来返回Connection对象。

6.2java.sql.Connection对象的回收

       当我们的程序中使用完Connection对象时,如果不使用数据库连接池,我们一般会调用 connection.close()方法,关闭connection连接,释放资源。如下所示:
 
  1. private void test() throws ClassNotFoundException, SQLException

  2. {

  3. String sql = "select * from hr.employees where employee_id < ? and employee_id >= ?";

  4. PreparedStatement st = null;

  5. ResultSet rs = null;

  6.  
  7. Connection con = null;

  8. Class.forName("oracle.jdbc.driver.OracleDriver");

  9. try

  10. {

  11. con = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:xe", "louluan", "123456");

  12. st = con.prepareStatement(sql);

  13. //设置参数

  14. st.setInt(1, 101);

  15. st.setInt(2, 0);

  16. //查询,得出结果集

  17. rs = st.executeQuery();

  18. //取数据,省略

  19. //关闭,释放资源

  20. con.close();

  21. }

  22. catch (SQLException e)

  23. {

  24. con.close();

  25. e.printStackTrace();

  26. }

  27. }

 

调用过close()方法的Connection对象所持有的资源会被全部释放掉,Connection对象也就不能再使用。

那么,如果我们使用了连接池,我们在用完了Connection对象时,需要将它放在连接池中,该怎样做呢?

可能大家第一个在脑海里闪现出来的想法就是:我在应该调用con.close()方法的时候,不调用close()f方法,将其换成将Connection对象放到连接池容器中的代码!

好,我们将上述的想法实现,首先定义一个简易连接池Pool,然后将上面的代码改写:
 
  1. package com.foo.jdbc;

  2.  
  3. import java.sql.Connection;

  4. import java.sql.DriverManager;

  5. import java.sql.SQLException;

  6. import java.util.Vector;

  7.  
  8. /**

  9. *

  10. * 一个线程安全的简易连接池实现,此连接池是单例的

  11. * putConnection()将Connection添加到连接池中

  12. * getConnection()返回一个Connection对象

  13. */

  14. public class Pool {

  15.  
  16. private static Vector<Connection> pool = new Vector<Connection>();

  17.  
  18. private static int MAX_CONNECTION =100;

  19.  
  20. private static String DRIVER="oracle.jdbc.driver.OracleDriver";

  21. private static String URL = "jdbc:oracle:thin:@127.0.0.1:1521:xe";

  22. private static String USERNAME = "louluan";

  23. private static String PASSWROD = "123456";

  24.  
  25. static {

  26. try {

  27. Class.forName(DRIVER);

  28. } catch (ClassNotFoundException e) {

  29. e.printStackTrace();

  30. }

  31. }

  32.  
  33. /**

  34. * 将一个Connection对象放置到连接池中

  35. */

  36. public static void putConnection(Connection connection){

  37.  
  38. synchronized(pool)

  39. {

  40. if(pool.size()<MAX_CONNECTION)

  41. {

  42. pool.add(connection);

  43. }

  44. }

  45. }

  46.  
  47.  
  48. /**

  49. * 返回一个Connection对象,如果连接池内有元素,则pop出第一个元素;

  50. * 如果连接池Pool中没有元素,则创建一个connection对象,然后添加到pool中

  51. * @return Connection

  52. */

  53. public static Connection getConnection(){

  54. Connection connection = null;

  55. synchronized(pool)

  56. {

  57. if(pool.size()>0)

  58. {

  59. connection = pool.get(0);

  60. pool.remove(0);

  61. }

  62. else

  63. {

  64. connection = createConnection();

  65. pool.add(connection);

  66. }

  67. }

  68. return connection;

  69. }

  70.  
  71. /**

  72. * 创建一个新的Connection对象

  73. */

  74. private static Connection createConnection()

  75. {

  76. Connection connection = null;

  77. try {

  78. connection = DriverManager.getConnection(URL, USERNAME,PASSWROD);

  79. } catch (SQLException e) {

  80. e.printStackTrace();

  81. }

  82. return connection;

  83. }

  84.  
  85. }

 
 
  1. package com.foo.jdbc;

  2.  
  3. import java.sql.Connection;

  4. import java.sql.DriverManager;

  5. import java.sql.PreparedStatement;

  6. import java.sql.ResultSet;

  7. import java.sql.SQLException;

  8. import java.util.Vector;

  9.  
  10. public class PoolTest

  11. {

  12.  
  13. private void test() throws ClassNotFoundException, SQLException

  14. {

  15. String sql = "select * from hr.employees where employee_id < ? and employee_id >= ?";

  16. PreparedStatement st = null;

  17. ResultSet rs = null;

  18.  
  19. Connection con = null;

  20. Class.forName("oracle.jdbc.driver.OracleDriver");

  21. try

  22. {

  23. con = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:xe", "louluan", "123456");

  24. st = con.prepareStatement(sql);

  25. //设置参数

  26. st.setInt(1, 101);

  27. st.setInt(2, 0);

  28. //查询,得出结果集

  29. rs = st.executeQuery();

  30. //取数据,省略

  31. //将不再使用的Connection对象放到连接池中,供以后使用

  32. Pool.putConnection(con);

  33. }

  34. catch (SQLException e)

  35. {

  36. e.printStackTrace();

  37. }

  38. }

  39. }

 

上述的代码就是将我们使用过的Connection对象放到Pool连接池中,我们需要Connection对象的话,只需要使用Pool.getConnection()方法从里面取即可。

是的,上述的代码完全可以实现此能力,不过有一个很不优雅的实现:就是我们需要手动地将Connection对象放到Pool连接池中,这是一个很傻的实现方式。这也和一般使用Connection对象的方式不一样:一般使用Connection的方式是使用完后,然后调用.close()方法释放资源。

为了和一般的使用Conneciton对象的方式保持一致,我们希望当Connection使用完后,调用.close()方法,而实际上Connection资源并没有被释放,而实际上被添加到了连接池中。这样可以做到吗?答案是可以。上述的要求从另外一个角度来描述就是:能否提供一种机制,让我们知道Connection对象调用了什么方法,从而根据不同的方法自定义相应的处理机制。恰好代理机制就可以完成上述要求.

  

怎样实现Connection对象调用了close()方法,而实际是将其添加到连接池中

这是要使用代理模式,为真正的Connection对象创建一个代理对象,代理对象所有的方法都是调用相应的真正Connection对象的方法实现。当代理对象执行close()方法时,要特殊处理,不调用真正Connection对象的close()方法,而是将Connection对象添加到连接池中。

MyBatis的PooledDataSource的PoolState内部维护的对象是PooledConnection类型的对象,而PooledConnection则是对真正的数据库连接java.sql.Connection实例对象的包裹器。

PooledConnection对象内持有一个真正的数据库连接java.sql.Connection实例对象和一个java.sql.Connection的代理:

其部分定义如下:

 
  1. class PooledConnection implements InvocationHandler {

  2.  
  3. //......

  4. //所创建它的datasource引用

  5. private PooledDataSource dataSource;

  6. //真正的Connection对象

  7. private Connection realConnection;

  8. //代理自己的代理Connection

  9. private Connection proxyConnection;

  10.  
  11. //......

  12. }


PooledConenction实现了InvocationHandler接口,并且,proxyConnection对象也是根据这个它来生成的代理对象:
 
  1. public PooledConnection(Connection connection, PooledDataSource dataSource) {

  2. this.hashCode = connection.hashCode();

  3. this.realConnection = connection;

  4. this.dataSource = dataSource;

  5. this.createdTimestamp = System.currentTimeMillis();

  6. this.lastUsedTimestamp = System.currentTimeMillis();

  7. this.valid = true;

  8. this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);

  9. }

 

实际上,我们调用PooledDataSource的getConnection()方法返回的就是这个proxyConnection对象。

当我们调用此proxyConnection对象上的任何方法时,都会调用PooledConnection对象内invoke()方法。

让我们看一下PooledConnection类中的invoke()方法定义:
 
  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  2. String methodName = method.getName();

  3. //当调用关闭的时候,回收此Connection到PooledDataSource中

  4. if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {

  5. dataSource.pushConnection(this);

  6. return null;

  7. } else {

  8. try {

  9. if (!Object.class.equals(method.getDeclaringClass())) {

  10. checkConnection();

  11. }

  12. return method.invoke(realConnection, args);

  13. } catch (Throwable t) {

  14. throw ExceptionUtil.unwrapThrowable(t);

  15. }

  16. }

  17. }

 

从上述代码可以看到,当我们使用了pooledDataSource.getConnection()返回的Connection对象的close()方法时,不会调用真正Connection的close()方法,而是将此Connection对象放到连接池中。

七、JNDI类型的数据源DataSource

对于JNDI类型的数据源DataSource的获取就比较简单,MyBatis定义了一个JndiDataSourceFactory工厂来创建通过JNDI形式生成的DataSource。

下面让我们看一下JndiDataSourceFactory的关键代码:
 
  1. if (properties.containsKey(INITIAL_CONTEXT)

  2. && properties.containsKey(DATA_SOURCE))

  3. {

  4. //从JNDI上下文中找到DataSource并返回

  5. Context ctx = (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT));

  6. dataSource = (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE));

  7. }

  8. else if (properties.containsKey(DATA_SOURCE))

  9. {

  10. // //从JNDI上下文中找到DataSource并返回

  11. dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE));

  12. }

猜你喜欢

转载自blog.csdn.net/asdasdasd123123123/article/details/81066568