Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)

沙海 2019年6月20日11:29:54Java SQL Java框架评论2942字数 7491阅读24分58秒阅读模式

一、概述

Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)文章源自JAVA秀-https://www.javaxiu.com/1018.html

二、创建

Mybatis数据源的创建过程稍微有些曲折。文章源自JAVA秀-https://www.javaxiu.com/1018.html

  • 数据源的创建过程;
  • Mybatis支持哪些数据源,也就是dataSource标签的type属性可以写哪些合法的参数?

弄清楚这些问题,对Mybatis的整个解析流程就清楚了,同理可以应用于任何一个配置上的解析上。从SqlSessionFactoryBuilder开始追溯DataSource的创建。SqlSessionFactoryBuilder中9个构造方法,其中字符流4个构造方法一一对应字节流4个构造方法,都是将mybatis-config.xml配置文件解析成Configuration对象,最终导向build(Configuration configuration)进行SqlSessionFactory的构造。文章源自JAVA秀-https://www.javaxiu.com/1018.html

Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)文章源自JAVA秀-https://www.javaxiu.com/1018.html

配置文件的在build(InputStream, env, Properties)构造方法中进行解析,InputStream和Reader方式除了流不一样之外均相同,本处以InputStream为例,追踪一下源码。文章源自JAVA秀-https://www.javaxiu.com/1018.html

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方法上,该方法对数据源工厂的字节码进行查找。文章源自JAVA秀-https://www.javaxiu.com/1018.html

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中才真正进行查找过程。文章源自JAVA秀-https://www.javaxiu.com/1018.html

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解析配置文件之前,我们忽略了一段重要的代码。文章源自JAVA秀-https://www.javaxiu.com/1018.html

Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)文章源自JAVA秀-https://www.javaxiu.com/1018.html

查看创建XMLConfigBuilder的过程,根据继承中初始化的规则,将会在父类BaseBuilder构造方法中创建Configuration对象,而Configuration对象的构造方法中将会注册框架中的一些重要参数。文章源自JAVA秀-https://www.javaxiu.com/1018.html

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

Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)文章源自JAVA秀-https://www.javaxiu.com/1018.html

至此,数据源创建结束。接下来就看看怎么用。文章源自JAVA秀-https://www.javaxiu.com/1018.html

三、详解

  • Mybatis datasource结构

Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)文章源自JAVA秀-https://www.javaxiu.com/1018.html

  • Mybatis JNDI
  • Mybatis UNPOOLED

Mybatis UNPOOLED数据源创建的思想,先通过默认构造方法创建数据源工厂(此时UNPOOLED dataSource随之创建),将mybatis-config.xml中数据源的配置信息通过setProperties传给工厂,然后通过工厂getDataSource。回顾一下这一段源码。文章源自JAVA秀-https://www.javaxiu.com/1018.html

Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)文章源自JAVA秀-https://www.javaxiu.com/1018.html

最终是利用简单的反射通过默认无参的构造方法实例化了数据源工厂,此时在数据源工厂中也实例化了UNPOOLED数据源对象。resolveClass(type)这句话,从configuration中拿到UNPOOLED对应的value,即org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory.class,然后通过无参构造器实例化工厂对象,在工厂的无参构造中也直接实例化了dataSource对象,即org.apache.ibatis.datasource.unpooled.UnpooledDataSource,然后调用setProperties方法,把配置文件中配置的参数(SqlSessionFactoryBuilder.builder中如果传入Properties也会被putAll,同key则覆盖value)进行dataSource设置。配置数据源,最重要的是connection的获取和管理,通过UNPOOLED方式来配置数据源,实际上和直接是用JDBC没有太多区别,操作的都是原生的、没有任何修饰的connection。文章源自JAVA秀-https://www.javaxiu.com/1018.html

Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)文章源自JAVA秀-https://www.javaxiu.com/1018.html

  • POOLED

POOLED工厂直接继承UNPOOLED工厂,只是在POOLED工厂的默认构造中实例化org.apache.ibatis.datasource.pooled.PooledDataSource覆盖了UNPOOLED中实例化的dataSource对象。其他的一模一样,紧接着调用setProperties方法等。直接关注连接对象,通过POOLED,因为连接池需要存放连接对象,因此连接对象的close方法需要进行改写,连接池的状态也需要进行管理(PoolState封装了连接池的状态)。文章源自JAVA秀-https://www.javaxiu.com/1018.html

Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)文章源自JAVA秀-https://www.javaxiu.com/1018.html

至于获取连接,会care PoolState中空闲链表中是否还有可用的connection,有则直接返回,没有则看是否已经到达配置的最大连接数,没有到达则new一个新的,这部分源码较长但是逻辑简单,就不贴出来了。直接看connection的代理部分吧。PooledConnection直接实现了JDK动态代理中的InvocationHandler,其invoke方法中重写了close方法,将连接对象还回池中,当非close方法的时候,会检查一下connection的状态是否正常,正常则直接调用原逻辑。文章源自JAVA秀-https://www.javaxiu.com/1018.html

class PooledConnection implements InvocationHandler {  
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
	    String methodName = method.getName();  
	    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();  
	        }  
	        return method.invoke(realConnection, args);  
	      } catch (Throwable t) {  
	        throw ExceptionUtil.unwrapThrowable(t);  
	      }  
	    }  
     }  
}

至此,Mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)的流程全部记录完毕了。文章源自JAVA秀-https://www.javaxiu.com/1018.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定