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

  • A+
所属分类:Java SQL Java框架

一、概述

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

二、创建

Mybatis数据源的创建过程稍微有些曲折。

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

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

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

配置文件的在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解析配置文件之前,我们忽略了一段重要的代码。

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

查看创建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();  
}  

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

至此,数据源创建结束。接下来就看看怎么用。

三、详解

  • Mybatis datasource结构

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

  • Mybatis JNDI
  • Mybatis UNPOOLED

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

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

最终是利用简单的反射通过默认无参的构造方法实例化了数据源工厂,此时在数据源工厂中也实例化了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。

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

  • POOLED

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

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

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

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)的流程全部记录完毕了。

  • 资源分享QQ群
  • weinxin
  • 官方微信公众号
  • weinxin
沙海
网站https安全证书安装,伪静态配置
C语言郝斌老师教程
Java图书管理系统
C语言速查手册

发表评论

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