【mybatis】mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)
一、概述
二、创建
mybatis数据源的创建过程稍微有些曲折。
1. 数据源的创建过程;
2. mybatis支持哪些数据源,也就是dataSource标签的type属性可以写哪些合法的参数?
弄清楚这些问题,对mybatis的整个解析流程就清楚了,同理可以应用于任何一个配置上的解析上。
从SqlSessionFactoryBuilder开始追溯DataSource的创建。SqlSessionFactoryBuilder中9个构造方法,其中字符流4个构造方法一一对应字节流4个构造方法,都是将mybatis-config.xml配置文件解析成Configuration对象,最终导向build(Configuration configuration)进行SqlSessionFactory的构造。
配置文件的在build(InputStream, env, Properties)构造方法中进行解析,InputStream和Reader方式除了流不一样之外均相同,本处以InputStream为例,追踪一下源码。
- public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
- try {
- // mybatis-config.xml文件的解析对象
- // 在XMLConfigBuilder中封装了Configuration对象
- // 此时还未真正发生解析,但是将解析的必备条件都准备好了
- XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
- // parser.parse()的调用标志着解析的开始
- // mybatis-config.xml中的配置将会被解析成运行时对象封装到Configuration中
- return build(parser.parse());
- } catch (Exception e) {
- throw ExceptionFactory.wrapException("Error building SqlSession.", e);
- } finally {
- ErrorContext.instance().reset();
- try {
- inputStream.close();
- } catch (IOException e) {
- // Intentionally ignore. Prefer previous error.
- }
- }
- }
在XMLConfigBuilder进一步追踪,疑问最终保留在其父类BaseBuilder的resolveClass方法上,该方法对数据源工厂的字节码进行查找。
- public Configuration parse() {
- if (parsed) {
- throw new BuilderException("Each XMLConfigBuilder can only be used once.");
- }
- parsed = true;
- // mybatis-config.xml的根节点就是configuration
- // 配置文件的解析入口
- parseConfiguration(parser.evalNode("/configuration"));
- return configuration;
- }
- private void parseConfiguration(XNode root) {
- try {
- propertiesElement(root.evalNode("properties")); //issue #117 read properties first
- typeAliasesElement(root.evalNode("typeAliases"));
- pluginElement(root.evalNode("plugins"));
- objectFactoryElement(root.evalNode("objectFactory"));
- objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
- settingsElement(root.evalNode("settings"));
- // environment节点包含了事务和连接池节点
- environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
- 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);
- }
- }
- private void environmentsElement(XNode context) throws Exception {
- if (context != null) {
- if (environment == null) {
- // 如果调用的build没有传入environment的id
- // 那么就采用默认的environment,即environments标签配置的default="environment_id"
- 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());
- }
- }
- }
- }
- private DataSourceFactory dataSourceElement(XNode context) throws Exception {
- if (context != null) {
- // dataSource标签的属性type
- String type = context.getStringAttribute("type");
- // 解析dataSource标签下的子标签<property name="" value="">
- // 实际上就是数据源的配置信息,url、driver、username、password等
- Properties props = context.getChildrenAsProperties();
- // resolveClass:到XMLConfigBuilder的父类BaseBuilder中进行工厂Class对象的查找
- // 这里是重点
- DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
- factory.setProperties(props);
- return factory;
- }
- throw new BuilderException("Environment declaration requires a DataSourceFactory.");
- }
在父类中并没有窥探到重点,转到其实例属性typeAliasRegistry中才真正进行查找过程。
- 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) {
- // BaseBuilder中的实例属性
- // 实例属性:protected final TypeAliasRegistry typeAliasRegistry;
- return typeAliasRegistry.resolveAlias(alias);
- }<span style="font-family: SimSun; background-color: rgb(255, 255, 255);"> </span>
typeAliasRegistry中实际上是在一个Map中进行KV的匹配。
- 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)) {
- // private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
- // TYPE_ALIASES是一个实例属性,类型是一个Map
- 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);
- }
- }
那么问题就来了,工厂类什么时候被注册到这个map中的?
实际上在SqlSessionFactoryBuilder的build(InputStream, env, Propeerties)方法中调用parse解析配置文件之前,我们忽略了一段重要的代码。
查看创建XMLConfigBuilder的过程,根据继承中初始化的规则,将会在父类BaseBuilder构造方法中创建Configuration对象,而Configuration对象的构造方法中将会注册框架中的一些重要参数。
- public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
- // 转
- this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
- }
- private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
- // 转调父类构造方法
- // 同时最终要的是直接new Configuration()传入父类
- // Configuration中的属性TypeAliasRegistry将会注册数据源工厂
- super(new Configuration());
- ErrorContext.instance().resource("SQL Mapper Configuration");
- this.configuration.setVariables(props);
- this.parsed = false;
- this.environment = environment;
- this.parser = parser;
- }
- public abstract class BaseBuilder {
- protected final Configuration configuration;
- protected final TypeAliasRegistry typeAliasRegistry;
- protected final TypeHandlerRegistry typeHandlerRegistry;
- public BaseBuilder(Configuration configuration) {
- this.configuration = configuration;
- // typeAliasRegistry来自于Configuration
- // 也就是合理解释了刚才通过typeAliasRegistry来找数据源工厂
- this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
- this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
- }
至此,数据源创建结束。接下来就看看怎么用。
三、详解
1. Mybatis datasource结构
2. mybatis JNDI
mybatis JNDI之前已经剖析过源码,此处不再进行剖析,原文链接:点击打开链接
3. mybatis UNPOOLED
mybatis UNPOOLED数据源创建的思想,先通过默认构造方法创建数据源工厂(此时UNPOOLED dataSource随之创建),将mybatis-config.xml中数据源的配置信息通过setProperties传给工厂,然后通过工厂getDataSource。回顾一下这一段源码。
最终是利用简单的反射通过默认无参的构造方法实例化了数据源工厂,此时在数据源工厂中也实例化了UNPOOLED数据源对象。
未完待续!