概述

上一篇我们主要分析了下<environments>标签下面,transactionManager的配置,上问最后还有个遗留问题:就是在设置事物管理器的时候有个autocommit的变量的初始值是在哪边处理的呢?今天我们就来解答一下。

<environments>的dataSource分析

   private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}

上一篇我们分析了黄色部分的,今天我们来分析下红色部分的,我们照旧先来看下configuation.xml中这部分的配置:

     <environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${driveClass}" />
<property name="url" value="${url}" />
<property name="username" value="${userName}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments> 

这边4到9行就是配置的datasource的相关信息,我们来看下上面标红的第九行代码,跟进去之后,解析代码如下:

   private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

第3行代码,根据上面配置文件中的type来确定数据源的类型,第4行,再获取连接数据源的一些必要属性配置,看下第5行代码:

   protected Class<?> resolveClass(String alias) {
if (alias == null) {
return null;
}
try {
return resolveAlias(alias);
} catch (Exception e) {
throw new BuilderException("Error resolving class. Cause: " + e, e);
}
} protected Class<?> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}

可以看出,跟前面实例化事物管理器一样,也是从typeAliasRegistry中根据type去获取,然后实例化,我上面配置的是pooled,这边最终实例化的应该是PooledDataSourceFactory这个数据源工厂,其实这边的配置不止这一个,还有JNDI,UNPOOLED,分别对应于JndiDataSourceFactory,UnpooledDataSourceFactory,这些也都是在Configuration.class中加载好的,

     typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

我们依次来介绍下,先看下目录结构:

可以看到,DataSourceFactory是数据源工厂接口,主要有如下两个方法:

 public interface DataSourceFactory {

   void setProperties(Properties props);

   DataSource getDataSource();

 }

第3行代码,是用来设置数据源的相关属性,一般是在初始化完成之后进行,第5行代码,是获取数据源对象的方法。

这个接口有两个实现类,JndiDataSourceFactory和UnpooledDataSourceFactory,另外一个PooledDataSourceFactory其实是继承于UnpooledDataSourceFactory。

UnpooledDataSourceFactory

UnpooledDataSourceFactory主要用来创建UnpooledDataSource对象,代码如下:

 public class UnpooledDataSourceFactory implements DataSourceFactory {

   private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length(); protected DataSource dataSource; public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
} @Override
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
} @Override
public DataSource getDataSource() {
return dataSource;
} private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
Object convertedValue = value;
Class<?> targetType = metaDataSource.getSetterType(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;
} }

第8到10行代码,在构造方法中初始化UnpooledDataSource对象,并在getDataSource方法中返回UnpooledDataSource对象,在setProperties方法中完成对UnpooledDataSource对象的相关配置。我们看下setProperties方法大致流程如下:

1.创建datasource对应的MetaObject

2.遍历properties集合,这个集合中存放了数据源需要的信息,也是我们配置在configuration.xml的property

3.判断key值是否以“driver.”开头,以这个开头的是驱动类信息,保存至driverProperties中

4.不是以“driver.”开头属性,先判断在MetaObject(其实就是UnpooledDataSource)中是否有对应的set方法,没有,则抛出异常

5.有的话,现获取属性值,根据MetaObject中返回值进行类型进行类型转换,主要针对Integer,Long,Boolean

6.设置MetaObject中driverProperties的属性值,也就是datasource的属性值。

PooledDataSourceFactory

PooledDataSourceFactory 主要用来创建 PooledDataSource 对象,它继承了 UnpooledDataSource 类,设置 DataSource 参数的方法复用UnpooledDataSource 中的 setProperties 方法,只是数据源返回的是  PooledDataSource 对象而已。

 public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

   public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
} }

初始化PooledDataSourceFactory这个类时,会在构造方法中初始化PooledDataSource对象,后续getDataSource方法中返回的也就是这个对象。

JndiDataSourceFactory

JndiDataSourceFactory 依赖 JNDI 服务器中获取用户配置的 DataSource,这里暂时不看。

下面我们就来看看具体的数据源是怎么实现的,

UnpooledDataSource

UnpooledDataSource 不使用连接池来创建数据库连接,每次获取数据库连接时都会创建一个新的连接进行返回;

 public class UnpooledDataSource implements DataSource {

   private ClassLoader driverClassLoader;  // 加载 Driver 类的类加载器
private Properties driverProperties; // 数据库连接驱动的相关配置
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>(); // 缓存所有已注册的数据库连接驱动 private String driver;
private String url;
private String username;
private String password; private Boolean autoCommit; // 是否自动提交
private Integer defaultTransactionIsolationLevel; // 事物的隔离级别 static {
Enumeration<Driver> drivers = DriverManager.getDrivers(); // 从DriverManager中获取已注册的驱动信息
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver); // 将已注册的驱动信息保存至registeredDrivers中
}
}

接下来看一下,实现DataSource的两个获取连接的方法:

   @Override
public Connection getConnection() throws SQLException {
return doGetConnection(username, password); // 根据properties中设置的属性类来获取连接
} @Override
public Connection getConnection(String username, String password) throws SQLException {
return doGetConnection(username, password);
}

可以看出最终调用的都是doGetConnection(username, password)方法,具体如下:

   private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties != null) {
props.putAll(driverProperties); // 设置数据库连接驱动的相关配置属性
}
if (username != null) {
props.setProperty("user", username); // 设置用户名
}
if (password != null) {
props.setProperty("password", password); // 设置密码
}
return doGetConnection(props);
} private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver(); // 初始化数据库驱动
Connection connection = DriverManager.getConnection(url, properties); // 通过 DriverManager 来获取一个数据库连接
configureConnection(connection); // 配置数据库连接的 autoCommit 和隔离级别
return connection;
} private synchronized void initializeDriver() throws SQLException {
if (!registeredDrivers.containsKey(driver)) { // 当前的驱动还有注册过,进行注册
Class<?> driverType;
try {
if (driverClassLoader != null) {
driverType = Class.forName(driver, true, driverClassLoader);
} else {
driverType = Resources.classForName(driver);
}
Driver driverInstance = (Driver)driverType.newInstance(); // 创建驱动对象实例
DriverManager.registerDriver(new DriverProxy(driverInstance)); // 往DriverManager中注册驱动
registeredDrivers.put(driver, driverInstance); // 在registerDrivers中记录加载过的驱动
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
// 设置数据库连接的 autoCommit 和隔离级别
private void configureConnection(Connection conn) throws SQLException {
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit); // 上一篇文章最后的问题答案就在这边
}
if (defaultTransactionIsolationLevel != null) {
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}

以上代码就是 UnpooledDataSource 类的主要实现逻辑,每次获取连接都是从数据库新创建一个连接进行返回,又因为,数据库连接的创建是一个耗时的操作,且数据库连接是非常珍贵的资源,如果每次获取连接都创建一个,则可能会造成系统的瓶颈,拖垮响应速度等,这时就需要数据库连接池了,Mybatis 也提供了自己数据库连接池的实现,就是 PooledDataSource 类。

PooledDataSource

这个类说真的,还是比较复杂的,我也是研究了有一会儿的,它内部创建数据库连接时基于我们上面介绍的UnpooledDataSource,但是呢,PooledDataSource并不会像UnpooledDataSource那样去管理数据库连接,而是通过PoolConnection来实现对于连接的管理,当然,既然是连接池,就有相关的状态,这边通过PoolState来管理连接池的状态,下面我们就来一次介绍下:

PoolConnection

代码如下,最明显的就是这个实现了InvocationHandler接口,说明它是一个代理类,那他肯定就会实现invoke接口。

 class PooledConnection implements InvocationHandler {

   private static final String CLOSE = "close";   // 判断是否是close方法
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class }; private int hashCode = 0;
    
private PooledDataSource dataSource; // 当前PoolConnection是属于哪个PooldataSource的
private Connection realConnection; // 真正的数据库连接
private Connection proxyConnection; // 数据库连接的代理对象
private long checkoutTimestamp; // 从连接池中取出该连接的时间戳
private long createdTimestamp; // 该连接创建的时间戳
private long lastUsedTimestamp; // 该连接最后一次被使用的时间戳
private int connectionTypeCode; // 用于标识该连接所在的连接池,由URL+username+password 计算出来的hash值
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 void invalidate() {
valid = false;
} // 判断连接是否有效
      1.根据valid
2.向数据库中发送检测测试的SQL,查看真正的连接还是否有效
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
}

上面这几个方法就是PoolConnection的构造方法,初始化相关类变量,判断连接是否有效及废弃连接的方法。下面我们再来看下invoke方法:

   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { // 判断是不是close方法,如果是,将连接放回到连接池中,下次继续获取使用
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) { // 不是close方法,执行真正的数据库连接执行
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection(); // 检查连接是否有效
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}

这个方法主要就是通过代理判断是不是close方法,是的的话并不是这届关闭,而是将其放回连接池中,供下次使用。

PoolState

这个类主要用来管理连接池的一些状态,没有

 public class PoolState {

   protected PooledDataSource dataSource; // 该poolstate属于哪个pooleddatasource

   protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>(); // 来用存放空闲的 pooledConnection 连接
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>(); // 用来存放活跃的 PooledConnection 连接
protected long requestCount = 0; // 请求数据库连接的次数
protected long accumulatedRequestTime = 0; // 获取连接的累计时间
protected long accumulatedCheckoutTime = 0; // 所有连接的累计从获取连接到归还连接的时长
protected long claimedOverdueConnectionCount = 0; // 连接超时的连接个数
protected long accumulatedCheckoutTimeOfOverdueConnections = 0; // 累计超时时间
protected long accumulatedWaitTime = 0; // 累计等待时间
protected long hadToWaitCount = 0; // 等待次数
protected long badConnectionCount = 0; // 无效连接数

PooledDataSource

PooledDataSource 它是一个简单的,同步的,线程安全的数据库连接池,它使用UnpooledDataSource 来创建数据库连接,使用PooledConnection来管理数据库中的连接,使用PoolState来管理连接池的状态。下面我们就来一起看下这个类,首先看下相关的类变量:

 public class PooledDataSource implements DataSource {

   private final PoolState state = new PoolState(this); // 当前连接池的状态

   private final UnpooledDataSource dataSource; // 用来创建真正的数据库连接对象

   // OPTIONAL CONFIGURATION FIELDS
protected int poolMaximumActiveConnections = 10; // 最大活跃连接数 默认值 10
protected int poolMaximumIdleConnections = 5; // 最大空闲连接数 默认值 5
protected int poolMaximumCheckoutTime = 20000; // 最大获取连接的时长 默认值 20000
protected int poolTimeToWait = 20000; // 获取连接时,最大的等待时长 默认值 20000
protected String poolPingQuery = "NO PING QUERY SET"; // 检测连接是否可用时的测试sql
protected boolean poolPingEnabled; // 是否允许发送测试sql
protected int poolPingConnectionsNotUsedFor; // 当连接超过 poolPingConnectionsNotUsedFor 毫秒未使用时,会发送一次测试 SQL 语句,测试连接是否正常 private int expectedConnectionTypeCode; // 用来标识当前的连接池,是 url+username+password 的 hash 值

下面我们来看下从数据库获取连接的实现:

   public Connection getConnection(String username, String password) throws SQLException {
return popConnection(username, password).getProxyConnection(); // 这边最终拿到的连接时代理的连接,不是真正的数据库连接
}
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null; // pooledConnection对象
long t = System.currentTimeMillis();
int localBadConnectionCount = 0; // 无效的连接个数 while (conn == null) { // pooledConnection代理对象为空
synchronized (state) { // 加锁操作
if (!state.idleConnections.isEmpty()) { // 判断当前idleConnection中是否存在空闲连接
// Pool has available connection
conn = state.idleConnections.remove(0); // 存在,则获取第一个连接
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else { // 不存在空闲连接,作如下判断
// Pool does not have available connection
if (state.activeConnections.size() < poolMaximumActiveConnections) { // 如果连接池的活跃连接数小于最大活跃连接数
// Can create new connection
conn = new PooledConnection(dataSource.getConnection(), this); // 创建一个数据库连接代理对象,创建新的连接
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else { // 如果连接池的活跃连接数大于等于最大活跃连接数,不能创建新的连接
// Cannot create new connection
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); // 将超时连接移出activeConnections
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { // 如果该连接没有设置自动提交
try {
oldestActiveConnection.getRealConnection().rollback(); // 回滚操作
} catch (SQLException e) {
log.debug("Bad connection. Could not roll back");
}
}
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); // 创建新的数据库代理对象,用的原来的数据库连接,并没有创建新的连接
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
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; // 中断退出
}
}
}
}
if (conn != null) { // 获取到了代理连接对象
if (conn.isValid()) { // 判断对象是否有效 -- 代理对象是否有效,发送测试Sql测试连接
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); // 将此连接放入activeConnection中
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)) { // 无效连接超过最大空闲连接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;
}

上面的方法中有一个isValid方法,判断连接是否有效,我们来看一下:

   public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
}

先判断代理连接对象是否有效,然后调用pingConnection方法尝试调用数据库,执行测试sql,看下pingConnection方法:

   protected boolean pingConnection(PooledConnection conn) {
boolean result = true; try {
result = !conn.getRealConnection().isClosed(); // 检测真实的数据库连接是否已关闭
} catch (SQLException e) {
if (log.isDebugEnabled()) { // 抛出异常的话,将连接设置为已关闭,直接返回
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
result = false;
} if (result) { // 没有关闭的话
if (poolPingEnabled) { // 判断是否允许发送测试sql
if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {// 允许发送测试sql,并且该连接超过设定时间未使用
try {
if (log.isDebugEnabled()) {
log.debug("Testing connection " + conn.getRealHashCode() + " ...");
}
Connection realConn = conn.getRealConnection(); // 这边几行代码就是通过连接去发送测试sql
Statement statement = realConn.createStatement();
ResultSet rs = statement.executeQuery(poolPingQuery);
rs.close();
statement.close();
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
result = true; // 执行测试sql成功,返回true
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
}
} catch (Exception e) { // 捕获到异常,返回false
log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
try {
conn.getRealConnection().close();
} catch (Exception e2) {
//ignore
}
result = false;
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
}
}
}
}
return result;
}

上面讲了获取数据库连接的方法,当然就有归还数据库连接的方法,这个方法是在连接关闭的时候通过代理调用的,pushConnection方法如下:

   protected void pushConnection(PooledConnection conn) throws SQLException {

     synchronized (state) { // 加锁操作
      // 从活跃连接list中移除这个连接代理
state.activeConnections.remove(conn);
      // 连接代理是否有效
if (conn.isValid()) {
        // 连接代理有效,判断当前空闲连接list的数量是否小于最大值,并且 当前连接代理是属于当前的连接池
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
        // 统计使用的时间
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
        // 没有设置自动提交的话,执行回滚操作
conn.getRealConnection().rollback();
}
        // 使用原来的数据库连接创建新的连接代理
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
        // 将新建的连接代理放入空闲列表list
state.idleConnections.add(newConn);
        // 设置创建时间戳
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
        // 设置上一次使用的时间戳
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
        // 将原来的连接代理置为失效
conn.invalidate();
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
        // 唤醒所有等待线程
state.notifyAll();
} else {
        // 当前连接池的空闲连接已经达到最大值了
        // 统计使用时间
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
        // 没有设置自动提交的话,执行回滚操作
conn.getRealConnection().rollback();
}
        // 空闲列表放不下,只好将连接关闭
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
        // 将连接代理置为失效
conn.invalidate();
}
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
       // 统计无效连接个数
state.badConnectionCount++;
}
}
}

可以看出归还连接的时候,如果空闲连接池里面还有位置,则把连接放进去,如果没有位置,则关闭连接。

我们再看回PooldataSource这个类,这里面有很多的set方法,都是去设置当前datasource相关属性,像驱动加载,url,用户名,密码,是否自动提交,事物隔离级别,驱动加载属性,最大活跃连接数,最大空闲连接数,连接超时时间,获取连接等待时间,测试连接的sql,是否允许发送测试sql,需要发送测试sql的未使用时间等,这些方法里面都有一个这样的方法,forceCloseAll(),我们来看一下:

   public void forceCloseAll() {
    // 这也是个同步方法
synchronized (state) {
     // 记录当前连接池的标志
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
     // 这个for循环用来关闭活跃List中的连接
for (int i = state.activeConnections.size(); i > 0; i--) {
try {
        // 获取一个数据库连接代理
PooledConnection conn = state.activeConnections.remove(i - 1);
        // 将代理置为无效
conn.invalidate();
      // 从代理中获取真正的连接
Connection realConn = conn.getRealConnection();
        // 没有设置自动提交的,执行回滚操作
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
        // 关闭连接
realConn.close();
} catch (Exception e) {
// ignore
}
}
     // 这个循环用来关闭空闲List中的连接
for (int i = state.idleConnections.size(); i > 0; i--) {
try {
PooledConnection conn = state.idleConnections.remove(i - 1);
conn.invalidate(); Connection realConn = conn.getRealConnection();
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
realConn.close();
} catch (Exception e) {
// ignore
}
}
}
if (log.isDebugEnabled()) {
log.debug("PooledDataSource forcefully closed/removed all connections.");
}
}

可以看出来,这个方法的主要作用就是把两个list中的连接全部关闭掉,为什么呢?因为之前修改了数据源的属性,需要重新加载。

总结

关于datasource这块的内容是看完了,也看了好几天,大致总结下:

1.mybatis自带的数据源有三种类型, 池型,非池型,外部数据源

2.对于非池型的数据源,每次都回去回去连接,只适合于比较简单的,小型的工作场景,对于池型的数据源,是我们现在用的比较多的,当然现在也有很多的第三方数据源可以使用,具体流程就不回顾了。

mybatis源码解析之Configuration加载(三)的更多相关文章

  1. mybatis源码解析之Configuration加载(四)

    概述 上一篇文章,我们主要讲了datasource的相关内容,那么<environments>标签下的内容就看的差不多了,今天就来看一下在拿到transationManager和datas ...

  2. mybatis源码解析之Configuration加载(五)

    概述 前面几篇文章主要看了mybatis配置文件configuation.xml中<setting>,<environments>标签的加载,接下来看一下mapper标签的解析 ...

  3. mybatis源码解析之Configuration加载(二)

    概述 上一篇我们讲了configuation.xml中几个标签的解析,例如<properties>,<typeAlises>,<settings>等,今天我们来介绍 ...

  4. mybatis源码解析之Configuration加载(一)

    概要 上一篇,我们主要搭建了一个简单的环境,这边我们主要来分析下mybatis是如何来加载它的配置文件Configuration.xml的. 分析 public class App { public ...

  5. 【MyBatis源码分析】Configuration加载(下篇)

    元素设置 继续MyBatis的Configuration加载源码分析: private void parseConfiguration(XNode root) { try { Properties s ...

  6. 【MyBatis源码分析】Configuration加载(上篇)

    config.xml解析为org.w3c.dom.Document 本文首先来简单看一下MyBatis中将config.xml解析为org.w3c.dom.Document的流程,代码为上文的这部分: ...

  7. webpack4.X源码解析之懒加载

    本文针对Webpack懒加载构建和加载的原理,对构建后的源码进行分析. 一.准备工作 首先,init之后创建一个简单的webpack基本的配置,在src目录下创建两个js文件(一个主入口文件和一个非主 ...

  8. Spring源码解析-配置文件的加载

    spring是一个很有名的java开源框架,作为一名javaer还是有必要了解spring的设计原理和机制,beans.core.context作为spring的三个核心组件.而三个组件中最重要的就是 ...

  9. Mybatis源码学习之资源加载(六)

    类加载器简介 Java虚拟机中的类加载器(ClassLoader)负责加载来自文件系统.网络或其他来源的类文件.Java虚拟机中的类加载器默认使用的是双亲委派模式,如图所示,其中有三种默认使用的类加载 ...

随机推荐

  1. StreamReader 和 StreamWriter 简单调用

    /* ######### ############ ############# ## ########### ### ###### ##### ### ####### #### ### ####### ...

  2. java中的构造器

    构造器是什么 1.构造器,也称构造方法.构造函数.作用是构造出来一个类的实例,确保对象得到初始化. 2.构造器的格式: 权限修饰符 类名(无参/有参){}. 3.根据有无参数,可分为无参构造 和有参构 ...

  3. Nuget安装包

    Selenium.WebDriver Selenium.WebDriver.ChromeDriver Selenium.Firefox.WebDriver Selenium.WebDriver.IED ...

  4. Zynq PS和PL间的连接

    跨越PS和PL的信号 AXI总线.EMIO.其他(看门狗.重启信号.中断信号.DMA接口信号) AXI标准 AXI(高级可扩展接口)是ARM AMBA的一部分.AMBA总线主要用于片上系统.AXI总线 ...

  5. Maven pom详解

    POM全称是Project Object Model,即项目对象模型.pom.xml是maven的项目描述文件,以xml的 形式描述项目的信息,包括项目名称.版本.项目id.项目的依赖关系.编译环境. ...

  6. Linux(例如CentOS 7)打开TCP 22端口,基于SSH协议

    SSH 为 Secure Shell 的缩写,由 IETF 的网络工作小组(Network Working Group)所制定:SSH 为建立在应用层和传输层基础上的安全协议.SSH 是目前较可靠,专 ...

  7. leecode第二百三十题(二叉搜索树中第K小的元素)

    /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode ...

  8. java中捕获Oracle异常

    try{ } catch (Exception e){ String msg=.getMessage(): int index= msg. toUpperCase(). indexOf("O ...

  9. 主成分分析(PCA)原理及R语言实现 | dimension reduction降维

    如果你的职业定位是数据分析师/计算生物学家,那么不懂PCA.t-SNE的原理就说不过去了吧.跑通软件没什么了不起的,网上那么多教程,copy一下就会.关键是要懂其数学原理,理解算法的假设,适合解决什么 ...

  10. 【Mac】系统语言切换为英文后chrome浏览器无法用国内印象笔记账号登陆印象笔记剪藏

    解决办法: 将chrome浏览器语言设置成中文就可以了