`

ibatis2.3源码之数据源&连接池浅析

阅读更多
在datasource包中ibatis提供三类对外数据源factory,分别为:SimpleDataSourceFactory、JndiDataSourceFactory、DbcpDataSourceFactory。

SimpleDataSourceFactory

SimpleDataSourceFactory对外提供简单数据源,接口定义如下:

Java代码
public class SimpleDataSourceFactory implements DataSourceFactory {  
  private DataSource dataSource;  
  public void initialize(Map map) {// map是从sqlMapConfig.xml配置文件拆分出来的配置信息,用于初始化数据源。  
    dataSource = new SimpleDataSource(map);  
  }  

工厂定义很简单,主要对外提供一个javax.sql.DataSource数据源。
 
对于数据源来说,配置信息涉及到:
Java代码
// Required Properties  
private static final String PROP_JDBC_DRIVER = "JDBC.Driver";  
private static final String PROP_JDBC_URL = "JDBC.ConnectionURL";  
private static final String PROP_JDBC_USERNAME = "JDBC.Username";  
private static final String PROP_JDBC_PASSWORD = "JDBC.Password";  
private static final String PROP_JDBC_DEFAULT_AUTOCOMMIT = "JDBC.DefaultAutoCommit";  
 
// Optional Properties  
private static final String PROP_POOL_MAX_ACTIVE_CONN = "Pool.MaximumActiveConnections";  
private static final String PROP_POOL_MAX_IDLE_CONN = "Pool.MaximumIdleConnections";  
... 
典型的数据源配置如下:
Java代码
<transactionManager type="JDBC">  
    <dataSource type="DBCP">  
    <property name="JDBC.Driver" value="${driver}"/>  
    <property name="JDBC.ConnectionURL" value="${url}"/>  
    <property name="JDBC.Username" value="${username}"/>  
    <property name="JDBC.Password" value="${password}"/>  
    <property name="Pool.MaximumActiveConnections" value="8"/>  
    <property name="Pool.MaximumIdleConnections" value="8"/>  
    ....      
    </dataSource>  
</transactionManager> 

知道了需要配置的参数后,第一件是就是调用SimpleDataSource的initialize(Map props)做初始化工作,主要需要校验、设置默认值或赋值等。
   大堆的初始化工作中就不说了,注意到initialize方法里有个driverProps变量,用于存储用户在配置中以Driver.开头的配置,如<property name="Driver.xxx" values="xxx" />,在建立连接池的时候,加载这些配置(DriverManager.getConnection(jdbcUrl, driverProps))。

JndiDataSourceFactory
   同样ibatis也支持JNDI来初始化datasource,主要用于让服务器容器管理连接池。同样初始化工作、获取数据源实现如下:
Java代码
public void initialize(Map properties) {  
   try {  
     InitialContext initCtx = null;  
     Hashtable context = getContextProperties(properties);  
 
     if (context == null) {  
       initCtx = new InitialContext();  
     } else {  
       initCtx = new InitialContext(context);  
     }  
 
     if (properties.containsKey("DataSource")) {  
       dataSource = (DataSource) initCtx.lookup((String) properties.get("DataSource"));  
     } else if (properties.containsKey("DBJndiContext")) { // LEGACY --Backward compatibility          
       dataSource = (DataSource) initCtx.lookup((String) properties.get("DBJndiContext"));  
     } else if (properties.containsKey("DBFullJndiContext")) { // LEGACY --Backward compatibility  
       dataSource = (DataSource) initCtx.lookup((String) properties.get("DBFullJndiContext"));  
     } else if (properties.containsKey("DBInitialContext")  
         && properties.containsKey("DBLookup")) { // LEGACY --Backward compatibility  
       Context ctx = (Context) initCtx.lookup((String) properties.get("DBInitialContext"));  
       dataSource = (DataSource) ctx.lookup((String) properties.get("DBLookup"));  
     }  
 
   } catch (NamingException e) {  
     throw new SqlMapException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e);  
   }  


DbcpDataSourceFactory
使用了第三方apache的dbcp来管理连接池。工厂接口很简单,如下:
Java代码
public class DbcpDataSourceFactory implements DataSourceFactory {  
 
  private DataSource dataSource;  
 
  public void initialize(Map map) {  
    DbcpConfiguration dbcp = new DbcpConfiguration(map);  
    dataSource = dbcp.getDataSource();  
  }  


加载dbcp的时候,有个判断配置Map是否含有“JDBC.Driver“属性,如下:
Java代码
BasicDataSource basicDataSource = null;  
   if (map.containsKey("JDBC.Driver")) {  
     basicDataSource = new BasicDataSource();  
     String driver = (String) map.get("JDBC.Driver");  
     String url = (String) map.get("JDBC.ConnectionURL");  
     String username = (String) map.get("JDBC.Username");  
   ...  
如果有:则正常加载所有属性(源码省略),这里注意IBATIS只为DBCP加载一定量的配置,其他DBCP配置请以Driver.开头。
如果没有JDBC.Driver,则利用反射的知识进行赋值,在赋值的时候ibatis做了点类型转换的工作,因为源数据都是String字符类型,需要反射invoke到方法里,需要做类型变换,如下:

Java代码
private BasicDataSource newDbcpConfiguration(Map map) {  
    BasicDataSource basicDataSource = new BasicDataSource();  
    Iterator props = map.keySet().iterator();  
    while (props.hasNext()) {  
      String propertyName = (String) props.next();  
      if (PROBE.hasWritableProperty(basicDataSource, propertyName))//判断basticDataSource对象有没有propertyName属性 {  
        String value = (String) map.get(propertyName);  
        Object convertedValue = convertValue(basicDataSource, propertyName, value);// 将value类型转换成basicDataSource对象propertyName变量的类型  
        PROBE.setObject(basicDataSource, propertyName, convertedValue);  
      }  
    }  
    return basicDataSource;  
  }  
 
  private Object convertValue(Object object, String propertyName, String value) {  
    Object convertedValue = value;  
    Class targetType = PROBE.getPropertyTypeForSetter(object, propertyName);// 获取object对象propertyName变量的类型  
    if (targetType == Integer.class || targetType == int.class) {  
      convertedValue = Integer.valueOf(value);  
    } else if (targetType == Long.class || targetType == long.class) {  
      convertedValue = Long.valueOf(value);  
    } else if (targetType == Boolean.class || targetType == boolean.class) {  
      convertedValue = Boolean.valueOf(value);  
    }  
    return convertedValue;  
  } 

以上关于ibatis三类数据源加载就完成了,对于加载数据源,看到ibatis基本没有什么限制,甚至可以不配置任何数据源信息,这为外部应用加载其他数据源提供了很大灵活性。


连接池(SimpleDataSource)

    对于JNDIDataSource和DBCP都有自己的连接池管理,而SimpleDataSource由ibatis自己管理着连接,所有需要有自己的实现。在上面创建SimpleDataSource的时,ibatis并不马上建立自己的连接池的,而是在第一次使用Connection时触发连接池的创建。

    看看 public Connection getConnection()方法:
Java代码
public Connection getConnection() throws SQLException {  
    return popConnection(jdbcUsername, jdbcPassword).getProxyConnection();  
  } 
ibatis的连接池由2个数组分别存放空闲连接和非空闲连接:
Java代码
private final Object POOL_LOCK = new Object();  
private List idleConnections = new ArrayList();  
private List activeConnections = new ArrayList(); 

ibatis的连接池实现大致解读为:
 
Java代码
SimplePooledConnection conn = null;  
 
while (conn == null) {  
      synchronized (POOL_LOCK) {  
        if (idleConnections.size() > 0) {  
          // 有空闲连接,从池中取,这里因为用的ArrayList,效率有待提高,remove会触发数组的复制。  
          conn = (SimplePooledConnection) idleConnections.remove(0);  
        } else {  
          // 无空闲连接且活动连接小于最大活动数,则创建新的连接池  
          if (activeConnections.size() < poolMaximumActiveConnections) {  
            // Can create new connection  
            if (useDriverProps) {  
              conn = new SimplePooledConnection(DriverManager.getConnection(jdbcUrl, driverProps), this);  
            } else {  
              conn = new SimplePooledConnection(DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword), this);  
            }  
          } else {  
            // Cannot create new connection,当前活动连接数大于最大值,不能创建新连接。  
            SimplePooledConnection oldestActiveConnection = (SimplePooledConnection) activeConnections.get(0);  
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();  
        // 尝试移除oldest活动连接,判断是否超时  
            if (longestCheckoutTime > poolMaximumCheckoutTime) {  
              // Can claim overdue connection  
              ...  
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {  
                oldestActiveConnection.getRealConnection().rollback();// 回滚  
              }  
              conn = new SimplePooledConnection(oldestActiveConnection.getRealConnection(), this);  
              oldestActiveConnection.invalidate();// 当前连接已经超时却为活动状态,判为无效。  
              } else {  
              // Must wait 没有可用的连接池,最坏情况,会造成当先线程等待  
              try {  
                if (!countedWait) {  
                  hadToWaitCount++;  
                  countedWait = true;  
                }  
               long wt = System.currentTimeMillis();  
                POOL_LOCK.wait(poolTimeToWait);  
                accumulatedWaitTime += System.currentTimeMillis() - wt;  
              } catch (InterruptedException e) {  
                break;  
              }  
            }  
          }  
        }  
        
        if (conn != null) {  
      // 非新建立连接,从空闲队列中取的连接池,需要重置状态  
          if (conn.isValid()) {  
            if (!conn.getRealConnection().getAutoCommit()) {  
              conn.getRealConnection().rollback();  
            }  
            conn.setConnectionTypeCode(assembleConnectionTypeCode(jdbcUrl, username, password));  
            conn.setCheckoutTimestamp(System.currentTimeMillis());  
            conn.setLastUsedTimestamp(System.currentTimeMillis());  
            activeConnections.add(conn);  
            requestCount++;  
            accumulatedRequestTime += System.currentTimeMillis() - t;  
          } else {  
            badConnectionCount++;  
            localBadConnectionCount++;  
            conn = null;  
            if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {// 当前活动连接超时成为“坏”连接,抛出程序异常。  
             throw new SQLException("SimpleDataSource: Could not get a good connection to the database.");  
            }  
          }  
        }  
      }  
 
    } 

再看看pushConnection连接返回连接池操作,能看出ibatis池的一点异同。
Java代码
private void pushConnection(SimplePooledConnection conn)  
      throws SQLException {  
    synchronized (POOL_LOCK) {  
      activeConnections.remove(conn);// 从活动队列移除conn  
      if (conn.isValid()) {  
        if (idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == getExpectedConnectionTypeCode()) {  
          accumulatedCheckoutTime += conn.getCheckoutTime();  
          if (!conn.getRealConnection().getAutoCommit()) {  
            conn.getRealConnection().rollback();  
          }  
          SimplePooledConnection newConn = new SimplePooledConnection(conn.getRealConnection(), this);// <SPAN style="COLOR: #ff0000">精华,把移除的conn中的Connection重新赋值给新的SimplePooledConnection,而原来的SimplePooledConnection对象会销毁。  
</SPAN>          idleConnections.add(newConn);  
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());  
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());  
          conn.invalidate();  
          POOL_LOCK.notifyAll();  
        } else {  
          accumulatedCheckoutTime += conn.getCheckoutTime();  
          if (!conn.getRealConnection().getAutoCommit()) {  
            conn.getRealConnection().rollback();  
          }  
          conn.getRealConnection().close();  
          conn.invalidate();  
        }  
      } else {  
       badConnectionCount++;  
      }  
    }  
  } 

以上的精华就在:SimplePooledConnection newConn = new SimplePooledConnection(conn.getRealConnection(), this);// 精华,把移除的conn中的Connection重新赋值给新的SimplePooledConnection,而原来的SimplePooledConnection对象会销毁。
        
对于对象池操作,如果要把老对象返回到池中,必定需要做清理工作,而ibatis的连接池在做返回池中并没有保留老对象,而是直接摒弃老对象,new一个新对象且载入老对象的Connection入到idleConnections队列中。

注意这里的remove()操作只是去除了引用,而非内存对象,GC暂时不会回收:)
推荐对于非容器管理连接池的话,用DBCP。


事务(transaction)

     说道事务一般会涉及到事务的接口、状态、配置、分布式事务等。ibatis提供的事务接口看上去很简单:
Java代码
public interface Transaction {  
 
  public void commit() throws SQLException, TransactionException;  
  public void rollback() throws SQLException, TransactionException;  
  public void close() throws SQLException, TransactionException;  
 
  public Connection getConnection() throws SQLException, TransactionException;  
 


    事务状态有:STATE_STARTED、STATE_COMMITTED、STATE_ENDED、STATE_USER_PROVIDED
    事务配置有:
Java代码
public interface TransactionConfig {  
 
  public DataSource getDataSource();  
  public void setDataSource(DataSource ds);  
 
  public void initialize(Properties props) throws SQLException, TransactionException;  
 
  public Transaction newTransaction(int transactionIsolation) throws SQLException, TransactionException;  
  public int getMaximumConcurrentTransactions();  
  public void setMaximumConcurrentTransactions(int maximumConcurrentTransactions);  
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics