Mybatis源码解析之DataSource

前言:

    我们知道,Mybatis在配置xml文件的时候,需要选择dataSource的类型,而我们操作JDBC正是通过使用DataSource中的Connection来完成的。

    本文主要分析,Mybatis中有哪些DataSource类型可选,从源码级别解析其又是如何产生Connection的

    本文不再介绍如何使用mybatis,读者可参考易佰教程 易佰教程mybatis

 

1.常规配置文件

    配置如下所示(命名为configure.xml):




	
		
			
			
				
				
				
				
			
		
	

    使用配置文件创建SqlSession

public class HelloWord {
	private static SqlSessionFactory sqlSessionFactory;
	private static Reader reader;

	static {
		try {
            // 1.读取mybatis配置文件,并生成SQLSessionFactory
			reader = Resources.getResourceAsReader("config/configure.xml");
			sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static SqlSessionFactory getSession() {
		return sqlSessionFactory;
	}
	
	public static void main(String[] args) {
		// 2.获取session,主要的CRUD操作均在SqlSession中提供
		SqlSession session = sqlSessionFactory.openSession();
		try {
            // operation...
		} finally {
			session.close();
		}
	}
}

    由上可知:通过解析配置文件生成SqlSessionFactory,然后通过SqlSessionFactory创建session,session用于具体操作JDBC

    通过之前的分析(原生Mybatis框架源码解析 )可知,在创建SqlSessionFactory的时候,就会创建对应的DataSource,并存放到Configuration中,下面就来看一下,DataSource是在哪一步被创建的

 

 

2.寻找DataSource

    关键的信息都在以下这句里面,那么我们直接来分析:

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

    1)SqlSessionFactoryBuilder().build(reader)

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
	try {
		XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
		return build(parser.parse());// 主要在这里
	} catch (Exception e) {
		throw ExceptionFactory.wrapException("Error building SqlSession.", e);
	} finally {
		ErrorContext.instance().reset();
		try {
			reader.close();
		} catch (IOException e) {
			// Intentionally ignore. Prefer previous error.
		}
	}
}

    2)先来看下new XMLConfigBuilder(reader, environment, properties)所做的操作

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    // 1.创建Configuration
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    // 2.赋值
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

// new Configuration()
public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    // 重点看这里,这里针对不同类型的DataSource做了映射
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
}

    总结2):

    在创建Configuration的构造方法中,我们看到DataSource的类型主要是三种:JNDI、POOLED、UNPOOLED,分别有对应的工厂实现类

 

    3)XMLConfigBuilder.parse()进行解析xml操作

public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

private void parseConfiguration(XNode root) {
    try {
        Properties settings = settingsAsPropertiess(root.evalNode("settings"));
        //issue #117 read properties first
        propertiesElement(root.evalNode("properties"));
        loadCustomVfs(settings);
        typeAliasesElement(root.evalNode("typeAliases"));
        pluginElement(root.evalNode("plugins"));
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        reflectionFactoryElement(root.evalNode("reflectionFactory"));
        settingsElement(settings);
        // 我们重点关注对environments的解析,
        environmentsElement(root.evalNode("environments"));
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        typeHandlerElement(root.evalNode("typeHandlers"));
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

// XMLConfigBuilder.environmentsElement()
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"));
                
                // 下面两句就是关于DataSource的产生和处理的
                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());
            }
        }
    }
}

// XMLConfigBuilder.dataSourceElement()
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
        // 当前示例中type为POOLED
        String type = context.getStringAttribute("type");
        Properties props = context.getChildrenAsProperties();
        
        // 通过下面的resolveClass可知,其是通过typeAliasRegistry.resolveAlias(type)来获取对应的class,
        // 然后通过反射来获取对应的工厂类实例
        // 再结合前面Configuration创建时对Alias的注册,可知当前的DataSourceFactory就是PooledDataSourceFactory
        DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
        factory.setProperties(props);
        return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

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);
    }
}

    总结3):

    通过上面的代码分析可知:有关于environments标签的解析,其中dataSource的值早早已经注册到Configuration中,在这里只是根据用户所选择的DataSource类型来反射对应的DataSourceFactory类型。

    本例中dataSource属性type为POOLED,那么对应的DataSourceFactory为PooledDataSourceFactory。

 

    4)PooledDataSourceFactory.getDataSource()产生具体的DataSource

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

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

    源码就一句话,直接指向PooledDataSource

 

    总结:所以,寻找DataSource最终指向PooledDataSource

 

3.PooledDataSource与Connection

    已经有了DataSource,那么下面就看其实如何产生Connection的了,Connection才是真正的主角

    话不多说,直接奔主题PooledDataSource.getConnection()方法

 

    1)PooledDataSource.getConnection()

@Override
public Connection getConnection() throws SQLException {
    // 这一句分为两步,popConnection(),然后getProxyConnection()
    // 看样子,还用到了反射,不管了,先看popConnection()方法
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}

    2)PooledDataSource.popConnection(dataSource.getUsername(), dataSource.getPassword())

    好长的方法啊,第一次看吓一跳,慢慢来分析

private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    // 1.上来一个while循环
    while (conn == null) {
        // 同步包裹了整个代码块,可知,其实线程安全的
        synchronized (state) {
            // 2.如果有空闲线程
            if (!state.idleConnections.isEmpty()) {
                // 直接从空闲连接中获取一个连接并返回
                conn = state.idleConnections.remove(0);
                if (log.isDebugEnabled()) {
                    log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
                }
            } else {
                // 3.没有空闲连接的话,如果没有超过最大活跃连接数,则直接创建一个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() + ".");
                    }
                // 4.如果超过了最大活跃连接数,说明不能继续创建新的连接了,
                    // 如果最先创建的连接已经超时使用了,则直接rollback,并重新生成一个新的连接
                } 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);
                        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);
                        oldestActiveConnection.invalidate();
                        if (log.isDebugEnabled()) {
                            log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
                        }
                    // 5.如果一切连接都在正常使用,那么当前线程只能等待,等待一段时间后,进行下一轮循环
                    } 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;
                        }
                    }
                }
            }
            // 6.对获取到的Connection进行赋值操作,如果连接无效,超过一定次数后,则抛出异常
            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;
}

    总结:

    分析可知:所谓连接池就是一个创建好的连接的集合,如果集合不为空,则直接从集合中获取连接使用;

    如果集合为空,且当前活跃连接数不超过最大活跃数,则直接创建连接;

    否则,基本只能等待,等待连接归还

 

    3)new PooledConnection(dataSource.getConnection(), this)连接的创建

    可以看得出来,这个方法也是分为两步的,我们先来看第一步

    * dataSource.getConnection()

// UnpooledDataSource.getConnection()
@Override
public Connection getConnection() throws SQLException {
    return 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();
    // 最终还是委托为DriverManager来创建
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
}

    总结:一路代码跟过来应该没有难度,最终我们看到还是委托给DriverManager来创建Connection的,细节不表

 

    * new PooledConnection(dataSource.getConnection(), this)

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;
    // 该PooledConnection有真实连接realConnection,也有代理连接ProxyConnection
    this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}

    4)回到最初PooledDataSource.getConnection()

    获取的是PooledConnection.getProxyConnection()

@Override
public Connection getConnection() throws SQLException {
    // 这一句分为两步,popConnection(),然后getProxyConnection()
    // 看样子,还用到了反射,不管了,先看popConnection()方法
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}

    Q:

    这里有一个疑问,为什么要获得代理类呢,而不是直接使用PooledConnection的realConnection呢?

    我们可以看下PooledConnection这个类

class PooledConnection implements InvocationHandler {

    很熟悉是吧,标准的代理类用法,看下其invoke方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 重点在这里,如果调用了其close方法,则实际执行的是将连接放回连接池的操作
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
        dataSource.pushConnection(this);
        return null;
    } else {
        try {
            if (!Object.class.equals(method.getDeclaringClass())) {
                // issue #579 toString() should never fail
                // throw an SQLException instead of a Runtime
                checkConnection();
            }
            // 其他的操作都交给realConnection执行
            return method.invoke(realConnection, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
}

 

总结:

    连接池总觉得很神秘,但仔细分析完其代码之后,也就没那么神秘了,就是将连接使用完之后放到一个集合中,下面再获取连接的时候首先从这个集合中获取。

    还有PooledConnection的代理模式的使用,值得我们学习

 

你可能感兴趣的:(Mybatis,Mybatis源码解析)