浩哥解析MyBatis源码(六)——DataSource数据源模块之池型数据源

原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6675674.html

1 回顾

  上一文中解读了MyBatis中非池型数据源的源码,非池型也就是只拥有单一数据连接的数据源,他只管理着一个数据连接,这种数据源现在很少使用,一般都是用池型数据源,因为单个连接的情况下,为了保证操作的正确性,针对这个连接的使用要进行同步,这样无疑会拖慢系统运行速度。

  而使用池型数据源,在池中保存有多个数据库连接,可以供多个数据库访问线程同时获取现成的不同的数据库连接,既保证了数据访问的安全性,也能极大的提升系统的运行速度。

2 池型数据源

  现在的Java项目中多采用池型数据源,C3P0,DBCP之类的也都提供了池型数据源,在MyBatis中也自定义了一种池型数据源PooledDataSource,这个pooled正好与之前的Configuration配置文件中配置的数据源的类型“POOLED”对应。

<dataSource type="POOLED">

2.1 池型数据源工厂

  首先我们来看看池型数据源的数据源工厂:PooledDataSourceFactory

1 package org.apache.ibatis.datasource.pooled;
2 import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
3 public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
4 public PooledDataSourceFactory() {
5 this.dataSource = new PooledDataSource();
6 }
7 }

  代码很简单,内部只有一个方法,用于获取吃型数据源的实例。果然好简单,你想错了......

  从类结构就可以看出,这个类继承了UnpooledDataSourceFactory工厂类,也就是说,PooledDataSourceFactory也拥有UnpooledDataSourceFactory中罗列的诸多方法功能,最主要继承的功能就是设置属性的功能,这个功能可以将被读取到内存中的数据源配置信息保存到数据源实例中。

  这个功能之前已经有过介绍,这里不再赘述。下面看看PooledDataSource数据源。

  这里在介绍PooledDataSource之前需要先对MyBatis自定义的池连接辅助类进行介绍。

2.2 池型连接:PooledConnedtion

class PooledConnection implements InvocationHandler {

  不看不知道,一看就明白,这是一个动态代理,采用的是JDK动态代理,说明PooledConnection池连接就是为了创建一个真实连接的代理,使用连接代理来调用真实连接来进行其他操作。

  同时这个代理对连接的功能进行了扩充,将其转化为池型连接,即池型的概念与实现有一部分就在这个类中进行,这个类将一个普通的连接包装成为一个池型化的连接,使其适用于MyBatis自定义的池型数据源。当然这并不是包装器模式,明显的代理模式(动态代理模式)。

  下面让我们来仔细看看如何将一个简单的连接包装成为一个池型连接。

  首先我们来看看池型连接拥有哪些属性?

 1   private static final String CLOSE = "close";
2 private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
3 private int hashCode = 0;
4 private PooledDataSource dataSource;
5 //真正的连接
6 private Connection realConnection;
7 //代理的连接
8 private Connection proxyConnection;
9 private long checkoutTimestamp;
10 private long createdTimestamp;
11 private long lastUsedTimestamp;
12 private int connectionTypeCode;
13 private boolean valid;

  这里我们一一介绍:

    String CLOSE = "close":这个很好理解,这只是下方代码中需要使用到的一个静态字符串常量而已,表示关闭。

    Class<?>[] IFACES = new Class<?>[] {Connection.class}:这个和上面类似,也是下方要使用的一个静态数组常量,表示连接类型,这里明显使用到了多态的概念。Connection是所有连接的祖类,

    int hashCode = 0:这是数据库连接(真实连接)的HashCode值,默认为0,表示当真实连接不存在即为null时的值。

    PooledDataSource dataSource:池型数据源,为什么在池型连接中会需要池型数据源实例呢?在下面你会看到它的应用,它的主要目的还是为了方便调用其内部定义的部分方法来辅助完成池型连接的一些功能判断。

    Connection realConnection:表示真实的数据库连接,属于被代理的对象

    Connection proxyConnection:表示使用JDK动态代理创建的代理真实连接的代理连接实例

    long checkoutTinmestamp:表示数据库连接被检出的时间戳,这个用于计算具体的检出时间

    long createdTimestamp:表示数据库连接被创建的时间戳,用于计算数据库连接被创建的时间

    long lastUsedTimestamp:表示连接被最后使用的时间戳,用于计算数据库连接被最后使用的时间

    int connectionTypeCode:数据库连接的类型编码,格式为:url+username+password

    boolean valid:表示连接是否可用的逻辑值

  以上的属性大多就是在原有的数据库连接的基础上做的再包装,为其赋予更多的属性,使其成为一个不折不扣的池型连接,这就像为一个人穿上各种衣服,装饰,学习各种知识能力,最后将其包装成一个某一方面的专业人士。呵呵,这里是真正的专业人士,真正拥有专业能力的人士。当然最核心的还是这个人了,在池型连接中也一样,最核心的当然还是这个真实连接了。

  下面看看构造器:

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 }

  在这个构造器中需要传递两个参数,分别为:数据库连接实例与池型数据源实例,将其分别赋值给对应的属性,同时初始化其余属性的值,最重要的还是最后一项,调用Proxy的newProxyInstance()方法来生成代理连接实例,其参数分别为:Connection类的类加载器、接口数组、当前类的实例。(这是固定格式,套路,详情可参见《代理模式之动态代理》、《java静态代理与动态代理简单分析》)

  在这个动态代理实现中最重要的还是invoke方法的实现:

 1   @Override
2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
3 String methodName = method.getName();
4 //如果调用close的话,忽略它,反而将这个connection加入到池中
5 if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
6 dataSource.pushConnection(this);
7 return null;
8 } else {
9 try {
10 if (!Object.class.equals(method.getDeclaringClass())) {
11 // issue #579 toString() should never fail
12 // throw an SQLException instead of a Runtime
13 //除了toString()方法,其他方法调用之前要检查connection是否还是合法的,不合法要抛出SQLException
14 checkConnection();
15 }
16 //其他的方法,则交给真正的connection去调用
17 return method.invoke(realConnection, args);
18 } catch (Throwable t) {
19 throw ExceptionUtil.unwrapThrowable(t);
20 }
21 }
22 }
23
24 private void checkConnection() throws SQLException {
25 if (!valid) {
26 throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
27 }
28 }

  我们一句句来看:

   首先获取要调用方法的名称methodName,然后对这个名称进行验证,如果是close方法的话,那么忽略关闭的实现,转而将这个连接加到连接池中(推入pushConnection),这表示在池型了数据源中,当连接不再使用后是要返回池中备用的,而不是直接被关闭销毁。

   如果调用的方法是不是close时,则首先进行该方法申明处的判断,如果这个方法不是来自Object类(剔除toString()方法),那么对当前的连接的可用性进行判断,如果不可用(valid值为false),则抛出SqlException,否则继续执行下一步,由真实连接进行方法调用。

   原理也很简单,总的来说就是实现方法调用(这也是代理的目的所在),外面看起来是由代理类执行方法,其实内部是由真实连接类来执行方法。

2.3 池状态类:PoolState

  池状态,顾名思义,用于描述连接池的状态(或称为属性也行)的类,这个属性不同于池连接的属性,这是出于连接池的属性,这个属性是作用于整个连接池的,而连接池中包含有限个池连接,一定要明白其中的关系。

  在这个类中定义了诸多属性,但是这些属性多用于统计信息的输出,什么是统计信息呢,就是将池连接的一些信息做一番统计并输出。所以其中重要的属性并不多,如下:

 1   protected PooledDataSource dataSource;
2 //空闲的连接
3 protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
4 //活动的连接
5 protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
6 //----------以下是一些统计信息----------
7 //请求次数
8 protected long requestCount = 0;
9 //总请求时间
10 protected long accumulatedRequestTime = 0;
11 protected long accumulatedCheckoutTime = 0;
12 protected long claimedOverdueConnectionCount = 0;
13 protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
14 //总等待时间
15 protected long accumulatedWaitTime = 0;
16 //要等待的次数
17 protected long hadToWaitCount = 0;
18 //坏的连接次数
19 protected long badConnectionCount = 0;
20 //构造器
21 public PoolState(PooledDataSource dataSource) {
22 this.dataSource = dataSource;
23 }

  上面代码注释中解释的很明白,第一个是数据源实例,这个实例在通过构造器创建实例的时候传入并赋值,然后及时两个ArrayList集合分别用于保存空闲的池连接实例与活动(使用)中的池连接实例。其余的就是一些统计信息,对于这些统计信息,不再详述,可自行翻看源码。

  这里做简单介绍:类中重写了toString()方法,用于输出这些信息,在类中用于获取这些信息的方法都是同步方法,保证线程安全性,保证获得的是正确的信息。

  下面就是重点:池型数据源

2.4 池型数据源:PooledDataSource

  这是一个同步的线程安全的数据库连接池。

  其实对于一个有连接池的数据源来说,针对池中数据连接的操作就是定义这个池型数据源的重点所在,那么针对池型连接的操作有哪些呢?

    获取池连接(推出池连接)

    收回池连接(推入池连接)

    关闭池连接

  在加上一个池连接的可用性判断,而我们的重点也就集中在这几点。

  首先我们来看看池型数据源拥有那些属性:

 1 public class PooledDataSource implements DataSource {
2
3 private static final Log log = LogFactory.getLog(PooledDataSource.class);
4
5 //有一个池状态
6 private final PoolState state = new PoolState(this);
7
8 //里面有一个UnpooledDataSource
9 private final UnpooledDataSource dataSource;
10
11 // OPTIONAL CONFIGURATION FIELDS
12 //正在使用连接的数量
13 protected int poolMaximumActiveConnections = 10;
14 //空闲连接数
15 protected int poolMaximumIdleConnections = 5;
16 //在被强制返回之前,池中连接被检查的时间
17 protected int poolMaximumCheckoutTime = 20000;
18 //这是给连接池一个打印日志状态机会的低层次设置,还有重新 尝试获得连接, 这些情况下往往需要很长时间 为了避免连接池没有配置时静默失 败)。
19 protected int poolTimeToWait = 20000;
20 //发送到数据的侦测查询,用来验证连接是否正常工作,并且准备 接受请求。默认是“NO PING QUERY SET” ,这会引起许多数据库驱动连接由一 个错误信息而导致失败
21 protected String poolPingQuery = "NO PING QUERY SET";
22 //开启或禁用侦测查询
23 protected boolean poolPingEnabled = false;
24 //用来配置 poolPingQuery 多次时间被用一次
25 protected int poolPingConnectionsNotUsedFor = 0;
26
27 private int expectedConnectionTypeCode;
28 ......
29 }

  结合源码中的注释内容可知:

    PoolState state = new PoolState(this):拥有一个池状态属性,无可厚非,池状态正是用于描述整个连接池整体的,这里将当前数据源实例作为参数赋予池状态形成一个不变的实例(final修饰的作用),这里的不变指的是这个池状态的实例是不变的,但不并意味着池状态中的各属性的值也不变,这个要看池状态类中属性是如何定义的,查看源码发现,这两个集合也是final修饰的,这表明这两个集合实例也是不会变的,但这同样无法保证集合中属性值的不变性,所以,final修饰所针对的就是最外层,他并不会对其内部的定义产生影响。

    UnpooledDataSource dataSource:拥有一个非池型数据源。可以这么说,一个池型数据源就是一个非池型数据源加上一个连接池,也就是说,池型数据源是在非池型数据源基础上发展而来,是以非池型为基础的。

    int poolMaximumActiveConnections = 10:连接池中最多可拥有的活动连接数,这是最大值,池中保存的活动连接数不能超过这个值(10个),当要超过时,在没有空闲连接的基础下,不能在新建连接,而是从活动链接中取最老的那个连接进行使用。(这个发生在推出连接时)

    int poolMaximumIdleConnections = 5:连接池中最多可拥有的空闲连接数,这是最大值,池中保存的空闲连接数不能超过这个值(5个),当要超过时,将多出的真实连接直接关闭,池连接置为无效。(这个发生在推入连接时)

      int poolMaximumCheckoutTime = 20000:连接池最大检出时间(可以理解为验证时间),如果一个连接验证时间超过设定值,则将这个连接设置为过期(发生在推出连接时)

    int poolTimeToWait = 20000:池等待时间,当需要从池中获取一个连接时,如果空闲连接数量为0,而活动连接的数量也达到了最大值,那么就针对那个最早取出的连接进行检查验证(check out),如果验证成功(即在上面poolMaximumCheckoutTime限定的时间内验证通过),说明这个连接还处于使用状态,这时取出操作暂停,线程等待限定时间,这个限定时间就是这个参数的使用位置。

    String poolPingQuery = "NO PING QUERY SET":在验证连接是否有效的时候,对数据库执行查询,查询内容为该设置内容。整个目的就是为了得知这个数据库连接还是否能够使用(未关闭,并处于正常状态),这是一个侦测查询。

    boolean poolPingEnabled = false:这是一个开关,表示是否打开侦测查询功能,默认为false,表示关闭该功能。

    int poolPingConnectionsNotUsedFor = 0:如果一个连接在限定的时间内一直未被使用,那么就要对该连接进行验证,以确定这个连接是否处于可用状态(即进行侦测查询),这个限定的时间就使用poolPingConnectionsNotUsedFor来设定,默认值为0。

    int expectedConnectionTypeCode:连接的类型编码,这个类型编码在创建池型数据源实例的时候会被组装,他的组装需要从数据源中获取连接的url、username、password三个值,将其按顺序组合在一起,这个类型编码可用于区别连接种类。

  上面说到了属性字段,下面紧接着就说说针对属性的操作方法:

    state:它只有get方法,用于获取池状态值,因为他是final的,所以不设置set方法

    dataSource:它的值在构造器中创建并进行赋值,是final的,不存在get与set方法。

    poolMaximumActiveConnections:拥有set与get方法,可设置新值,也能获取其值。

    poolMaximumCheckoutTime:拥有set与get方法,可设置新值,也能获取其值。

    poolTimeToWait:拥有set与get方法,可以设置新值,也能获取其值。

    poolPingQuery:拥有set与get方法,可以设置新值,也能获取其值。

    poolPingEnabled:拥有set与get方法,可以设置新值,也能获取其值。

    poolPingConnectionsNotUsedFor:拥有set与get方法,可以设置新值,也能获取其值。

  这里有个注意点,在上述的每个set方法中,其中还包括针对UnpooledDataSource中的属性的set方法重写之中,都在最后拥有这么一个方法:forceCloseAll()

1   public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
2 this.poolPingConnectionsNotUsedFor = milliseconds;
3 forceCloseAll();
4 }

  这是什么意思呢?让我们来看看forceCloseAll()这个方法就明白了:

 1   /*
2 * Closes all active and idle connections in the pool
3 */
4 public void forceCloseAll() {
5 synchronized (state) {
6 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
7 //关闭所有的activeConnections和idleConnections
8 for (int i = state.activeConnections.size(); i > 0; i--) {
9 try {
10 PooledConnection conn = state.activeConnections.remove(i - 1);
11 conn.invalidate();
12
13 Connection realConn = conn.getRealConnection();
14 if (!realConn.getAutoCommit()) {
15 realConn.rollback();
16 }
17 realConn.close();
18 } catch (Exception e) {
19 // ignore
20 }
21 }
22 for (int i = state.idleConnections.size(); i > 0; i--) {
23 try {
24 PooledConnection conn = state.idleConnections.remove(i - 1);
25 conn.invalidate();
26
27 Connection realConn = conn.getRealConnection();
28 if (!realConn.getAutoCommit()) {
29 realConn.rollback();
30 }
31 realConn.close();
32 } catch (Exception e) {
33 // ignore
34 }
35 }
36 }
37 if (log.isDebugEnabled()) {
38 log.debug("PooledDataSource forcefully closed/removed all connections.");
39 }
40 }

  注释很明白:关闭池中所有活动的和空闲的连接。代码也很简洁明了,就是将池状态中的两个集合中保存的池连接全部值为无效,所有的真是连接全部关闭。但是重要的不是这个做法,而是为什么要这么做?当我们呢重新设置一个参数时,就需要将所有的连接全部关闭。

  其实也很好理解:连接是在数据源完全设置完整的情况下才生成的,数据源就是连接生成的基础,是连接存在的田地,当我们要修改数据源的基础属性的时候,原有设置上产生的连接必定不再适合新的设置,需要全部推倒重来,这里就是这个意思。

  下面我们来看看构造器:

 1   public PooledDataSource() {
2 dataSource = new UnpooledDataSource();
3 }
4
5 public PooledDataSource(String driver, String url, String username, String password) {
6 dataSource = new UnpooledDataSource(driver, url, username, password);
7 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
8 }
9
10 public PooledDataSource(String driver, String url, Properties driverProperties) {
11 dataSource = new UnpooledDataSource(driver, url, driverProperties);
12 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
13 }
14
15 public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
16 dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
17 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
18 }
19
20 public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
21 dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
22 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
23 }

  从上面列出的构造器中可以看出,池型数据源的根本还是非池型数据源,池型就是在非池型的基础上加上池型的概念与实现。我们在创建池型数据源实例的时候首先会创建一个非池型数据源的实例并将其赋值给参数。五种构造器也是根据非池型数据源的五种构造器而来,一一对应,只是在后四个构造器中增加了连接类型编码的组装。这个连接类型编码适用于区分连接种类的。

  下面介绍PooledDataSource中最重要的方法,推入(popConnection)与推出(pushConnection)方法。

  推出方法是由getConnection()方法调用的。

 1   @Override
2 public Connection getConnection() throws SQLException {
3 //覆盖了DataSource.getConnection方法,每次都是pop一个Connection,即从池中取出一个来
4 return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
5 }
6
7 @Override
8 public Connection getConnection(String username, String password) throws SQLException {
9 return popConnection(username, password).getProxyConnection();
10 }
  1   private PooledConnection popConnection(String username, String password) throws SQLException {
2 boolean countedWait = false;
3 PooledConnection conn = null;
4 long t = System.currentTimeMillis();
5 int localBadConnectionCount = 0;
6
7 //最外面是while死循环,如果一直拿不到connection,则不断尝试
8 while (conn == null) {
9 synchronized (state) {
10 if (!state.idleConnections.isEmpty()) {
11 //如果有空闲的连接的话
12 // Pool has available connection
13 //删除空闲列表里第一个,返回
14 conn = state.idleConnections.remove(0);
15 if (log.isDebugEnabled()) {
16 log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
17 }
18 } else {
19 //如果没有空闲的连接
20 // Pool does not have available connection
21 if (state.activeConnections.size() < poolMaximumActiveConnections) {
22 //如果activeConnections太少,那就new一个PooledConnection
23 // Can create new connection
24 conn = new PooledConnection(dataSource.getConnection(), this);
25 if (log.isDebugEnabled()) {
26 log.debug("Created connection " + conn.getRealHashCode() + ".");
27 }
28 } else {
29 //如果activeConnections已经很多了,那不能再new了
30 // Cannot create new connection
31 //取得activeConnections列表的第一个(最老的)
32 PooledConnection oldestActiveConnection = state.activeConnections.get(0);
33 long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
34 if (longestCheckoutTime > poolMaximumCheckoutTime) {
35 //如果checkout时间过长,则这个connection标记为overdue(过期)
36 // Can claim overdue connection
37 state.claimedOverdueConnectionCount++;
38 state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
39 state.accumulatedCheckoutTime += longestCheckoutTime;
40 state.activeConnections.remove(oldestActiveConnection);
41 if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
42 oldestActiveConnection.getRealConnection().rollback();
43 }
44 //删掉最老的连接,然后再new一个新连接
45 conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
46 oldestActiveConnection.invalidate();
47 if (log.isDebugEnabled()) {
48 log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
49 }
50 } else {
51 //如果checkout时间不够长,等待吧
52 // Must wait
53 try {
54 if (!countedWait) {
55 //统计信息:等待+1
56 state.hadToWaitCount++;
57 countedWait = true;
58 }
59 if (log.isDebugEnabled()) {
60 log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
61 }
62 long wt = System.currentTimeMillis();
63 //睡一会儿吧
64 state.wait(poolTimeToWait);
65 state.accumulatedWaitTime += System.currentTimeMillis() - wt;
66 } catch (InterruptedException e) {
67 break;
68 }
69 }
70 }
71 }
72 if (conn != null) {
73 //如果已经拿到connection,则返回
74 if (conn.isValid()) {
75 if (!conn.getRealConnection().getAutoCommit()) {
76 conn.getRealConnection().rollback();
77 }
78 conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
79 //记录checkout时间
80 conn.setCheckoutTimestamp(System.currentTimeMillis());
81 conn.setLastUsedTimestamp(System.currentTimeMillis());
82 state.activeConnections.add(conn);
83 state.requestCount++;
84 state.accumulatedRequestTime += System.currentTimeMillis() - t;
85 } else {
86 if (log.isDebugEnabled()) {
87 log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
88 }
89 //如果没拿到,统计信息:坏连接+1
90 state.badConnectionCount++;
91 localBadConnectionCount++;
92 conn = null;
93 if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
94 //如果好几次都拿不到,就放弃了,抛出异常
95 if (log.isDebugEnabled()) {
96 log.debug("PooledDataSource: Could not get a good connection to the database.");
97 }
98 throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
99 }
100 }
101 }
102 }
103
104 }
105
106 if (conn == null) {
107 if (log.isDebugEnabled()) {
108 log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
109 }
110 throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
111 }
112
113 return conn;
114 }

  结合注释内容,我对上面的执行过程进行简单的描述:

    (1) 这是个同步方法,线程安全。但是将synchronized锁同步放置到循环内部,而不是循环之外的原因是因为:如果将同步锁放置在循环之外,当多个线程执行到锁的位置,其中一个线程获得锁然后开始执行循环,如果发生问题导致无限循环,那么这个锁将是一直被这个线程所持有,导致其他线程永久处于等待锁的状态,程序无法执行下去。而将锁放置到循环内部,当多个线程来到锁之前,其中一个线程获得锁,执行循环内部代码,当执行完成一次循环,无论成功失败,都会释放锁,而其他线程就可以获得锁进而执行。

    (2) 首先验证空闲连接集合是否为空(验证是否还有空闲连接备用),如果存在空闲连接,那么直接获取这个空闲连接,将这个连接从空闲连接集合中删除。

    (3) 如果没有空闲连接,那么就验证活动连接集合中连接的数量是否达到最大值(poolMaximumActiveConnections),如果未达到最大值,这时,我们可以直接创建一个新的池型连接(需要一个真实连接于与一个池型数据源实例作为参数)

    (4) 如果活动连接集合中的连接数目已经达到最大值(poolMaximumActiveConnections),那么就针对最早的那个活动连接(即在集合中排在0位的那个连接实例)进行验证。并获取其验证时间间隔值(该连接上一次记录验证时间戳到当前时间的间隔),将其与池连接的最大验证时限(poolMaximumCheckoutTime)进行比较,如果前者大,说明针对这个连接距上一次记录验证时间戳的时间超过了限定时限,这时将这个老连接从活动连接集合中删除,并新建一个池连接,还以老连接所代理的真实连接为真实连接(实际上就是创建一个新的代理),并将老的池连接设为无效。

    (5) 如果验证时间与显示时间比较结果为验证时间小于限定时限(这个限定时限的设置需要根据项目实际情况来设置,或通过经验来设置,确保在这个时间之内连接的数据库操作执行完毕,不然贸然将连接关闭会导致原本的数据库操作失败),说明这个连接还可能处于使用状态,这时候只有等待一途,这里将线程设置等待限定秒数(poolTimeToWait),线程进入等待状态,那么就会释放同步锁,此时其他线程就能获得锁来进行执行。当前线程在等待N秒之后自动进入准备状态准备重新获得锁。

    (6) 然后就获得的连接进行判断,如果连接不为空,那么验证连接是否可用(isValid),如果连接可用则设置连接类型编码,并记录验证时间戳(setCheckoutTimestamp)与最后一次使用时间戳(setLastUsedTimestamp),这两个时间戳可用于计算该连接的验证时间与最后一次使用时间,在前面会使用到这些值进行判断。再然后将该链接添加到活动连接集合中。

    (7)如果获取的连接为空,或者说没有获取到连接,则坏连接数加1,将连接置null,并验证坏连接数值,如果比当前空闲连接数量+3都大的话,那么就放弃获取连接,并抛出SqlException,(抛出异常也就意味着执行的终止,这个线程将不再执行循环操作)

  下面看看推入连接方法:

 1   protected void pushConnection(PooledConnection conn) throws SQLException {
2
3 synchronized (state) {
4 //先从activeConnections中删除此connection
5 state.activeConnections.remove(conn);
6 if (conn.isValid()) {
7 if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
8 //如果空闲的连接太少,
9 state.accumulatedCheckoutTime += conn.getCheckoutTime();
10 if (!conn.getRealConnection().getAutoCommit()) {
11 conn.getRealConnection().rollback();
12 }
13 //new一个新的Connection,加入到idle列表
14 PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
15 state.idleConnections.add(newConn);
16 newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
17 newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
18 conn.invalidate();
19 if (log.isDebugEnabled()) {
20 log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
21 }
22 //通知其他线程可以来抢connection了
23 state.notifyAll();
24 } else {
25 //否则,即空闲的连接已经足够了
26 state.accumulatedCheckoutTime += conn.getCheckoutTime();
27 if (!conn.getRealConnection().getAutoCommit()) {
28 conn.getRealConnection().rollback();
29 }
30 //那就将connection关闭就可以了
31 conn.getRealConnection().close();
32 if (log.isDebugEnabled()) {
33 log.debug("Closed connection " + conn.getRealHashCode() + ".");
34 }
35 conn.invalidate();
36 }
37 } else {
38 if (log.isDebugEnabled()) {
39 log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
40 }
41 state.badConnectionCount++;
42 }
43 }
44 }

  同样的,我们结合注释内容进行解析:

    (1) 这个方法同样是一个同步方法,拥有同步锁,以池状态实例为锁。

    (2) 首先我们将当前要推入的连接实例从活动连接中删除,表示其不再处于使用状态。

    (3) 然后对连接额可用性进行(valid)判断,如果还处于可用状态,则验证空闲连接集合中的空闲连接数量是否小于设置的限定值(poolMaximumIdleConnections)和当前连接实例的类型编码是否与当前池型数据源中的连接类型编码一致,如果上面两点都满足,则进行下一步:

    (4) 新建一个池型连接实例并将其添加到空闲连接集合中,这个池型连接实例是以之前要推入的连接为基础重新创建的,也就是说是针对那个要推入的池型连接的真实连接重新创建一个池型连接代理(只改变外包装,实质不改变),并将原池型连接的时间戳设置统统设置到新的连接中,保持连接的持续性,然后将原池型连接置为无效。

    (5) 然后唤醒所有沉睡线程notifyAll()。

    (6) 如果第(3)点中的判断中有一个不成立(空闲连接数量达到最大值或者连接的类型编码不一致)那么直接将该连接的真实连接关闭,池连接置为无效即可。

  这个方法比较简单,条理也很清楚。

下面讨论池型数据源中的最后一个方法:pingConnection()

 1   protected boolean pingConnection(PooledConnection conn) {
2 boolean result = true;
3
4 try {
5 result = !conn.getRealConnection().isClosed();
6 } catch (SQLException e) {
7 if (log.isDebugEnabled()) {
8 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
9 }
10 result = false;
11 }
12
13 if (result) {
14 if (poolPingEnabled) {
15 if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
16 try {
17 if (log.isDebugEnabled()) {
18 log.debug("Testing connection " + conn.getRealHashCode() + " ...");
19 }
20 Connection realConn = conn.getRealConnection();
21 Statement statement = realConn.createStatement();
22 ResultSet rs = statement.executeQuery(poolPingQuery);
23 rs.close();
24 statement.close();
25 if (!realConn.getAutoCommit()) {
26 realConn.rollback();
27 }
28 result = true;
29 if (log.isDebugEnabled()) {
30 log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
31 }
32 } catch (Exception e) {
33 log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
34 try {
35 conn.getRealConnection().close();
36 } catch (Exception e2) {
37 //ignore
38 }
39 result = false;
40 if (log.isDebugEnabled()) {
41 log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
42 }
43 }
44 }
45 }
46 }
47 return result;
48 }

  这个方法也很是简单,它的目的就是为了验证某一个连接是否任然可用,它会被池连接PooledConnection类中的isValid()方法调用,用于判断一个连接是否还可用。这个方法就是真正用于判断连接可用与否的功能性方法。

  (1) 首先创建一个局部变量result用于保存判断结果,默认为true

  (2) 然后将当前池型连接包裹的真实连接的开闭状态值的非值赋值给result(当真实连接处于关闭状态时,result值为false,当真实连接处于开启状态时,result值为true),如果赋值过程出现了异常,则直接将result置false

  (3) 判断result的值,如果result值为true,则判断poolPingEnabled的值,这是侦测查询的开关,如果这个值为true,表示开启侦测查询,那么就可以执行以下内容。

  (4) 判断poolPingConnectionsNotUsedFor的值是否大于等于0(这个判断的意思是判断是否设置了正确的poolPingConnectionsNotUsedFor值),并且判断该连接的自最后一次使用以来的时间间隔是否大于设定的poolPingConnectionsNotUsedFor值(验证该连接是否到了需要进行侦测查询的时间,如果小于设置时间则不进行侦测查询)

  (5) 如果上述条件均满足,则进行一次侦测查询,这个侦测查询就是针对这个连接的一个测试查询,看看整个查询过程是否通畅,若通畅(没有任何异常出现),则将result置为true,一旦测试过程出现了异常,则将该连接的真实连接关闭,并将result置为false

2.5 总结

  MyBatis自行实现的池型数据源简单易懂,代码简洁明快,单功能较为简单,无法与那些较为专业的如dbcp和c3p0相媲美,但是满足日常需要还是足够了,这其中涉及到的有关池型的概念更是有助于我们真正了解Java中池的实现方法。

上一篇:IE8下调用Active控件


下一篇:xcode8.0升级之后公司项目遇到的问题