前言:
我们知道,Mybatis在配置xml文件的时候,需要选择dataSource的类型,而我们操作JDBC正是通过使用DataSource中的Connection来完成的。
本文主要分析,Mybatis中有哪些DataSource类型可选,从源码级别解析其又是如何产生Connection的
本文不再介绍如何使用mybatis,读者可参考易佰教程 易佰教程mybatis
配置如下所示(命名为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是在哪一步被创建的
关键的信息都在以下这句里面,那么我们直接来分析:
SqlSessionFactory sqlSessionFactory = new 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.
}
}
}
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,分别有对应的工厂实现类
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。
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
源码就一句话,直接指向PooledDataSource
总结:所以,寻找DataSource最终指向PooledDataSource
已经有了DataSource,那么下面就看其实如何产生Connection的了,Connection才是真正的主角
话不多说,直接奔主题PooledDataSource.getConnection()方法
@Override
public Connection getConnection() throws SQLException {
// 这一句分为两步,popConnection(),然后getProxyConnection()
// 看样子,还用到了反射,不管了,先看popConnection()方法
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
好长的方法啊,第一次看吓一跳,慢慢来分析
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;
}
总结:
分析可知:所谓连接池就是一个创建好的连接的集合,如果集合不为空,则直接从集合中获取连接使用;
如果集合为空,且当前活跃连接数不超过最大活跃数,则直接创建连接;
否则,基本只能等待,等待连接归还
可以看得出来,这个方法也是分为两步的,我们先来看第一步
* 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);
}
获取的是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的代理模式的使用,值得我们学习