Mybatis深入之DataSource实例化过程

最后更新于:2022-04-01 16:30:07

# Mybatis深入之DataSource实例化过程 ### 简介 主要介绍Mybatis启动过程中DataSource实例化的过程、为后面解析一个完整SQL执行过程做个前章。 ### Mybatis中DataSource体系 ### MybatisDataSource整体简介 Mybatis中关于数据库的类都在`org.apache.ibatis.datasource`包中 ![DataSource体系](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a85803a27fa.jpg "") Mybatis配置文件中关于数据库的配置: ~~~ <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> ~~~ - 重点关注`<dataSource type="POOLED">`的type属性、其有三种取值: - POOLED:使用Mybatis自带的数据库连接池来管理数据库连接 - UNPOOLED:不使用任何数据库连接池来管理数据库连接 - JNDI:jndi形式使用数据库连接、主要用于项目正常使用的时候 类与类之间的关系: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-08_57a85803b30c3.jpg "") 每一条线都是一种关系、简单解释一下 1. PooledDataSource实现java.sql.DataSource接口 2. PooledDataSource内部持有一个DataSource引用 3. UnpooledDataSource实现java.sql.DataSource接口 4. PooledDataSource内部持有一个UnpooledDataSource引用 5.PooledDataSourceFactory无参构造方法体中将其父类UnpooledDataSourceFactory持有的引用DataSource实例化为PooledDataSource 6. PooledDataSourceFactory继承UnpooledDataSourceFactory 7. UnpooledDataSourceFactory无参构造方法将其持有的引用DataSource实例化为UnpooledDataSource 8. UnpooledDataSourceFactory持有一个DataSource引用、用于返回实例化好的DataSource。 ### Mybatis中DataSource实例化整体过程 这里以使用Mybatis自带的数据库连接池为例。也就是type为 “POOLED”类型的数据连接。 1. 根据配置文件中type的类型实例化具体的DataSourceFactory。这里是POOLED所以实例化的是PooledDataSourceFactory。 1. 通过PooledDataSourceFactory来获取DataSource具体实例:PooledDataSource 对于第一步更详细点的过程: - 在Mybatis初始化Configuration对象时、Configuration中属性TypeAliasRegistry同样被实例化、并且在Configuration的无参构造方法中对TypeAliasRegistry注册了许多常用的类(以键值对的形式保存在TypeAliasRegistry的属性`private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();`中、也包括TypeAliasRegistry无参构造方法注册的基本java类型。 - 通过配置文件中type指定的”POOLED”在TypeAliasRegistry中查找其对应的类:PooledDataSourceFactory - 调用其newInstance()实例化 - PooledDataSourceFactory继承自UnpooledDataSourceFactory、所以UnpooledDataSourceFactory先被实例化、 - UnpooledDataSourceFactory无参构造方法中实例化了其DataSource引用为UnpooledDataSource。 - 接着实例化PooledDataSourceFactory、其无参构造方法体将父类UnpooledDataSourceFactory持有的DataSource实例化为PooledDataSource。 - PooledDataSource实例化时初始化了一些关于数据库连接池的配置信息 - PooledDataSource的无参构造方法中将其持有的UnpooledDataSource实例化。 - UnpooledDataSource中关于数据库连接的属性值在实例化DataSourceFactory之后读取properties值设置到对应属性上。 ### 具体过程 从上一篇中知道Mybatis初始化过程是解析Mybatis配置文件并装配Configuration对象。从Mybatis基础使用中知道Mybatis数据库连接信息的配置是在environments标签中配置的: ~~~ <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> ~~~ 所以想要了解DataSource初始化过程可以从XMLConfigBuilder中的parse方法入手: ~~~ 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()); } } } } ~~~ - 这里同样有关于数据库事务的配置、具体事务有关的后面再说 - 主要看如何实例化DataSource、同样从上面代码中我们知道只是将DataSource实例化了而没有进行任何操作、原因是只有具体执行某SQL语句的时候才会使用DataSource来获取数据库连接。 获取DataSource关键代码: ~~~ DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); ~~~ - 根据配置文件中dataSource标签中内容实例化DataSourceFactory - 通过DataSourceFactory获取DataSource 下面首先看如何根据dataSource标签内容实例化DataSourceFactory ~~~ private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { //获取数据库连接池类型: POOLED-使用Mybatis自带数据库连接池。UNPOOL-不使用数据库连接池。这里看POOLED的情况。 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."); } ~~~ - 上面一段代码关键点在于`resolveClass(type)` - 经过一系列的方法调用、最终返回结果的方法是:`TypeAliasRegistry` ~~~ @SuppressWarnings("unchecked") // throws class cast exception as well if types cannot be assigned public <T> Class<T> resolveAlias(String string) { try { if (string == null) return null; String key = string.toLowerCase(Locale.ENGLISH); // issue #748 Class<T> value; if (TYPE_ALIASES.containsKey(key)) { value = (Class<T>) TYPE_ALIASES.get(key); } else { value = (Class<T>) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); } } ~~~ - 这里重点在于TypeAliasRegistry是何时实例化的 - 上一篇初始化过程中知道Configuration中有一个私有变量`protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();` - 进一步看看Configuration实例化的时候其无参构造函数体就知道其缘由 ~~~ public Configuration() { typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); 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); } ~~~ - 从上面可以看出TypeAliasRegistry在实例化之后并初始化了其内部私有变量`private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();`来保存一些Type alias(类型别名)供后面程序使用。 从上面代码`typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);`可以看出最后返回的是`PooledDataSourceFactory`。 ~~~ public class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() { this.dataSource = new PooledDataSource(); } } ~~~ - PooledDataSourceFactory继承UnpooledDataSourceFactory 其中UnpooledDataSourceFactory拥有一个DataSource的protected级别的属性`protected DataSource dataSource;`并且其构造函数: ~~~ public UnpooledDataSourceFactory() { this.dataSource = new UnpooledDataSource(); } ~~~ 对比PooledDataSourceFactory的构造函数: ~~~ public PooledDataSourceFactory() { this.dataSource = new PooledDataSource(); } ~~~ 知道最终UnpooledDataSourceFactory的`protected DataSource dataSource;`实例是:`PooledDataSource`。 到这里只要知道PooledDataSource是什么、那么返回的DataSource就是什么。 当然在这之前还需要一步、就是将配置文件中的数据库连接信息设置到最后生成的DataSourceFactory(在这里就是PooledDataSourceFactory)中去。这个过程中使用了一个Mybatis很长用的用于操作反射的封装类:MetaObject。提供了一些简便的获取、设置类等通过反射来操作类的方法、以后有时间专门看一眼。 下面的主要目标就是看PooledDataSource调用其无参构造方法时到底做了什么。 PooledDataSource是java.sql.DataSource的一个实现类、其属性与无参构造方法如下: ~~~ private static final Log log = LogFactory.getLog(PooledDataSource.class); private final PoolState state = new PoolState(this); private final UnpooledDataSource dataSource; // OPTIONAL CONFIGURATION FIELDS protected int poolMaximumActiveConnections = 10; protected int poolMaximumIdleConnections = 5; protected int poolMaximumCheckoutTime = 20000; protected int poolTimeToWait = 20000; protected String poolPingQuery = "NO PING QUERY SET"; protected boolean poolPingEnabled = false; protected int poolPingConnectionsNotUsedFor = 0; private int expectedConnectionTypeCode; public PooledDataSource() { dataSource = new UnpooledDataSource(); } ~~~ - 设置了一些作为数据库连接池初始化使用的参数 - 无参方法体中实例话了属性`dataSource = new UnpooledDataSource();` - UnpooledDataSource无参构造函数是空方法题 - 要注意的是:前面实例化DataSourceFactory的时候最后一步是设置属性。其实就是通过MetaObject来将属性值设置到UnpooledDataSource的数据库连接属性上了。 到这里、关于数据库DataSource类的实例话也就结束了。 ### 补充 ### 对数据库实例化做个总结: 当使用数据库连接池时、即`<dataSource type="POOLED">`时、DataSourceFactory具体实例是PooledDataSourceFactory。返回的DataSource具体实例是内部持有UnpooledDataSource实例的PooledDataSource。 当不使用数据库连接池时、即`<dataSource type="UNPOOLED">` 时、DataSourceFactory具体实例是UnpooledDataSourceFactory。返回的DataSource具体实例是UnpooledDataSource实例。 更多内容:[Mybatis 目录](http://blog.csdn.net/crave_shy/article/details/45825599)
';