mybatis数据源(JNDI、POOLED、UNPOOLED)源码详解

article/2025/10/6 20:21:00

一、概述

    

二、创建

    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为例,追踪一下源码。

 

[html] view plain copy

  1. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {  
  2. try {  
  3.   // mybatis-config.xml文件的解析对象  
  4.   // 在XMLConfigBuilder中封装了Configuration对象  
  5.   // 此时还未真正发生解析,但是将解析的必备条件都准备好了  
  6.   XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);  
  7.   // parser.parse()的调用标志着解析的开始  
  8.   // mybatis-config.xml中的配置将会被解析成运行时对象封装到Configuration中  
  9.   return build(parser.parse());  
  10. } catch (Exception e) {  
  11.   throw ExceptionFactory.wrapException("Error building SqlSession.", e);  
  12. } finally {  
  13.   ErrorContext.instance().reset();  
  14.   try {  
  15.     inputStream.close();  
  16.   } catch (IOException e) {  
  17.     // Intentionally ignore. Prefer previous error.  
  18.   }  
  19. }  
  20. }  

 

    在XMLConfigBuilder进一步追踪,疑问最终保留在其父类BaseBuilder的resolveClass方法上,该方法对数据源工厂的字节码进行查找。

 

[html] view plain copy

  1. public Configuration parse() {  
  2.     if (parsed) {  
  3.       throw new BuilderException("Each XMLConfigBuilder can only be used once.");  
  4.     }  
  5.     parsed = true;  
  6.     // mybatis-config.xml的根节点就是configuration  
  7.     // 配置文件的解析入口  
  8.     parseConfiguration(parser.evalNode("/configuration"));  
  9.     return configuration;  
  10.   }  
  11.   
  12.   private void parseConfiguration(XNode root) {  
  13.     try {  
  14.       propertiesElement(root.evalNode("properties")); //issue #117 read properties first  
  15.       typeAliasesElement(root.evalNode("typeAliases"));  
  16.       pluginElement(root.evalNode("plugins"));  
  17.       objectFactoryElement(root.evalNode("objectFactory"));  
  18.       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));  
  19.       settingsElement(root.evalNode("settings"));  
  20.       // environment节点包含了事务和连接池节点  
  21.       environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631  
  22.       databaseIdProviderElement(root.evalNode("databaseIdProvider"));  
  23.       typeHandlerElement(root.evalNode("typeHandlers"));  
  24.       mapperElement(root.evalNode("mappers"));  
  25.     } catch (Exception e) {  
  26.       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);  
  27.     }  
  28.   }  
  29.     
  30.   private void environmentsElement(XNode context) throws Exception {  
  31.     if (context != null) {  
  32.       if (environment == null) {  
  33.         // 如果调用的build没有传入environment的id  
  34.         // 那么就采用默认的environment,即environments标签配置的default="environment_id"  
  35.         environment = context.getStringAttribute("default");  
  36.       }  
  37.       for (XNode child : context.getChildren()) {  
  38.         String id = child.getStringAttribute("id");  
  39.         if (isSpecifiedEnvironment(id)) {  
  40.           TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));  
  41.           // 数据源工厂解析  
  42.           // 这里是重点,数据源工厂的查找  
  43.           DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));  
  44.           // 工厂模式,生成相应的数据源  
  45.           DataSource dataSource = dsFactory.getDataSource();  
  46.           Environment.Builder environmentBuilder = new Environment.Builder(id)  
  47.               .transactionFactory(txFactory)  
  48.               .dataSource(dataSource);  
  49.           configuration.setEnvironment(environmentBuilder.build());  
  50.         }  
  51.       }  
  52.     }  
  53.   }  
  54.     
  55.   private DataSourceFactory dataSourceElement(XNode context) throws Exception {  
  56.     if (context != null) {  
  57.       // dataSource标签的属性type  
  58.       String type = context.getStringAttribute("type");  
  59.       // 解析dataSource标签下的子标签<property name="" value="">  
  60.       // 实际上就是数据源的配置信息,url、driver、username、password等  
  61.       Properties props = context.getChildrenAsProperties();  
  62.       // resolveClass:到XMLConfigBuilder的父类BaseBuilder中进行工厂Class对象的查找  
  63.       // 这里是重点  
  64.       DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();  
  65.       factory.setProperties(props);  
  66.       return factory;  
  67.     }  
  68.     throw new BuilderException("Environment declaration requires a DataSourceFactory.");  
  69.   }  

 

    在父类中并没有窥探到重点,转到其实例属性typeAliasRegistry中才真正进行查找过程。

 

[html] view plain copy

  1. protected Class<?> resolveClass(String alias) {  
  2.     if (alias == null) return null;  
  3.     try {  
  4.       // 做了一下检查,转  
  5.       return resolveAlias(alias);  
  6.     } catch (Exception e) {  
  7.       throw new BuilderException("Error resolving class. Cause: " + e, e);  
  8.     }  
  9.   }  
  10.   protected Class<?> resolveAlias(String alias) {  
  11.     // BaseBuilder中的实例属性  
  12.     // 实例属性:protected final TypeAliasRegistry typeAliasRegistry;  
  13.     return typeAliasRegistry.resolveAlias(alias);  
  14.   }<span style="font-family: SimSun; background-color: rgb(255, 255, 255);">  </span>  

 

    typeAliasRegistry中实际上是在一个Map中进行KV的匹配。

 

[html] view plain copy

  1. public <T> Class<T> resolveAlias(String string) {  
  2.     try {  
  3.       if (string == null) return null;  
  4.       String key = string.toLowerCase(Locale.ENGLISH); // issue #748  
  5.       Class<T> value;  
  6.       if (TYPE_ALIASES.containsKey(key)) {  
  7.         // private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();  
  8.         // TYPE_ALIASES是一个实例属性,类型是一个Map  
  9.         value = (Class<T>) TYPE_ALIASES.get(key);  
  10.       } else {  
  11.         value = (Class<T>) Resources.classForName(string);  
  12.       }  
  13.       return value;  
  14.     } catch (ClassNotFoundException e) {  
  15.       throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);  
  16.     }  
  17.   }  

 

    那么问题就来了,工厂类什么时候被注册到这个map中的?

    实际上在SqlSessionFactoryBuilder的build(InputStream, env, Propeerties)方法中调用parse解析配置文件之前,我们忽略了一段重要的代码。

    查看创建XMLConfigBuilder的过程,根据继承中初始化的规则,将会在父类BaseBuilder构造方法中创建Configuration对象,而Configuration对象的构造方法中将会注册框架中的一些重要参数。

 

[html] view plain copy

  1. public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {  
  2.     // 转  
  3.     this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);  
  4.   }  
  5.     
  6.   private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {  
  7.     // 转调父类构造方法  
  8.     // 同时最终要的是直接new Configuration()传入父类  
  9.     // Configuration中的属性TypeAliasRegistry将会注册数据源工厂  
  10.     super(new Configuration());  
  11.     ErrorContext.instance().resource("SQL Mapper Configuration");  
  12.     this.configuration.setVariables(props);  
  13.     this.parsed = false;  
  14.     this.environment = environment;  
  15.     this.parser = parser;  
  16.   }  

[html] view plain copy

  1. public abstract class BaseBuilder {  
  2.   protected final Configuration configuration;  
  3.   protected final TypeAliasRegistry typeAliasRegistry;  
  4.   protected final TypeHandlerRegistry typeHandlerRegistry;  
  5.   
  6.   public BaseBuilder(Configuration configuration) {  
  7.     this.configuration = configuration;  
  8.     // typeAliasRegistry来自于Configuration  
  9.     // 也就是合理解释了刚才通过typeAliasRegistry来找数据源工厂  
  10.     this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();  
  11.     this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();  
  12.   }  

 

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

 

三、详解

1. Mybatis datasource结构

2. mybatis JNDI

    mybatis JNDI之前已经剖析过源码,此处不再进行剖析,原文链接:点击打开链接

3. mybatis UNPOOLED

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

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

4. POOLED

    POOLED工厂直接继承UNPOOLED工厂,只是在POOLED工厂的默认构造中实例化org.apache.ibatis.datasource.pooled.PooledDataSource覆盖了UNPOOLED中实例化的dataSource对象。其他的一模一样,紧接着调用setProperties方法等。

    直接关注连接对象,通过POOLED,因为连接池需要存放连接对象,因此连接对象的close方法需要进行改写,连接池的状态也需要进行管理(PoolState封装了连接池的状态)。

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

[html] view plain copy

  1. class PooledConnection implements InvocationHandler {  
  2.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  3.     String methodName = method.getName();  
  4.     if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {  
  5.       dataSource.pushConnection(this);  
  6.       return null;  
  7.     } else {  
  8.       try {  
  9.         if (!Object.class.equals(method.getDeclaringClass())) {  
  10.           // issue #579 toString() should never fail  
  11.           // throw an SQLException instead of a Runtime  
  12.           checkConnection();  
  13.         }  
  14.         return method.invoke(realConnection, args);  
  15.       } catch (Throwable t) {  
  16.         throw ExceptionUtil.unwrapThrowable(t);  
  17.       }  
  18.     }  
  19.   }  
  20. }  

 

5. Mybatis集成其他数据源

    参看:点击打开链接

    其实和第一节息息相关,直接使用mybatis的时候,在配置标签<dataSource type="POOLED">,然后再Mybatis初始化的时候从Configuration中得到的POOLED=org.apache.ibatis.datasource.pooled.PooledDataSourceFactory.class,初始化这个对象就是直接通过POOLED得到class,然后newInstance通过默认构造方法直接实例化工厂对象,然后通过实例化出来的factory.setProperties(prop)把dataSource标签中的配置参数一一注入工厂对象。

    源码思想很简单,这里就不再贴出来了,因此要在纯mybatis环境下要继承其他数据源,例如C3P0或DBCP,只需要将其datasource继承org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory即可,因为setProperties方法在UnpooledDataSourceFactory中定义,并且默认POOLED也是继承于此。

    Mybatis是否支持自定义的数据源,关键在于两点,一是该数据源是否有默认构造函数(无参构造函数),二是可以通过get/set方式来进行数据源配置。满足以上两点,就足够了。mybatis内置的数据源实在是弱得看不下去,所以集成其他数据源的时候,无论是传统的JNDI/DBCP/C3P0还是当下牛逼闪闪的HikariCP也罢,都可以集成到mybatis中使用!当然,如果你使用Spring来处理数据源,那么这里就可以不用考虑了,Spring-mybatis的jar包已经帮你处理好了···

 

    其实整个逻辑还是很简单的,源码揭露一切。

 

 

附注:

    本文如有错漏,烦请不吝指正,谢谢!


http://chatgpt.dhexx.cn/article/Df7ZU58I.shtml

相关文章

XAConnectionFactory: failed to create pooled connection - DBMS down or unreachable 的解决方法

问题描述 项目启动出现报错&#xff1a;XAConnectionFactory: failed to create pooled connection - DBMS down or unreachable? 原因分析&#xff1a; Druid连接池问题&#xff0c;当Druid与Atomikos搭配时&#xff0c;如果MySQL版本高于8.0.11则不被支持 查看数据库使用…

没有手动提交事务,Mybatis 的 POOLED 连接池炸了

错误原因&#xff1a; 事务不关&#xff0c;并且非事务交替进行 总的来说&#xff0c;就是先开启了事务连接&#xff0c;未提交或关闭&#xff0c;导致连接池连接全部占满。 此时进行一次非事务连接操作&#xff0c;但是因为此时已经没有可以空闲的连接&#xff0c;并且创建的连…

【Flink】报错 No pooled slot available and request to ResourceManager for new slot failed

文章目录 1.场景11.1 概述1.2 问题1.场景1 1.1 概述 改报错请参考:【Flink】Flink 1.9 升级 到 flink 1.12.4 报错 shaded netty4 AbstractChannel AnnotatedConnectException 错误描述 报错信息: java.util.concurrent.CompletionException:

Oracle 关于Pooled connection request timed out

发生场景&#xff1a; 系统异常卡死&#xff0c;报错&#xff1a; 通过查找日志和业务接口定位&#xff0c;是因为数据库连接池溢出导致链接不上&#xff0c;系统卡死 异常测试代码如下格式&#xff1a; 测试了一个1000次的链接&#xff0c;每次连接都持续30秒&#xff0c;链…

MyBatis POOLED连接池深入了解

往期内容&#xff0c;如下 一、MyBatis简介 二、MyBatis环境搭建 三、MyBatis入门案例 四、MyBatis自定义 五、MyBatis CRUD操作 六、Mybatis中参数和返回值的深入了解 七、MyBatis 配置文件标签 我们在实际开发中都会使用连接池&#xff0c;因为它可以减少我们获取连接所消耗的…

unpooled与pooled

unpooled每次都是重新获取一个连接&#xff0c;底层源码如下 pooled去判断有没有&#xff0c;有就拿出来用&#xff0c;没有就创建新的&#xff0c;每次用完再还回去 mybatis poolde连接池原理 先去看空闲的有没&#xff0c;有就直接用&#xff0c;没有就去活动连接池里把最老…

.NET性能优化-推荐使用Collections.Pooled

简介 性能优化就是如何在保证处理相同数量的请求情况下占用更少的资源&#xff0c;而这个资源一般就是CPU或者内存&#xff0c;当然还有操作系统IO句柄、网络流量、磁盘占用等等。但是绝大多数时候&#xff0c;我们就是在降低CPU和内存的占用率。 之前分享的内容都有一些局限性…

使用 TFDConnection 的 pooled 连接池

使用 TFDConnection 的 pooled 连接池 从开始看到这个属性&#xff0c;就一直认为他可以提供一个连接池管理功能&#xff0c; 苦于文档资料太少&#xff0c; 甚至在帮助中对该属性的使用都没有任何介绍&#xff0c;如果你搜索百度&#xff0c;也会发现基本没资料。 最后终于在…

Mybatis 连接池POOLED

1、连接池&#xff1a; 我们在实际开发中都会使用连接池。 因为它可以减少我们获取连接所消耗的时间。 2、mybatis中的连接池 在 Mybatis 中也有连接池技术&#xff0c;但是它采用的是自己的连接池技术。 在 Mybatis 的 SqlMapConfig.xml 配置文件中&#xff0c;通过来实 现 My…

Mybatis连接池介绍与分类 Mybatis使用POOLED UNPOOLED配置连接池的原理分析

一、连接池 1.概念&#xff1a;其实就是一个容器&#xff08;集合&#xff09;&#xff0c;存放数据库连接的容器。 当系统初始化好后&#xff0c;容器被创建&#xff0c;容器中会申请一些连接对象&#xff0c;当用户来访问数据库时&#xff0c;从容器中获取连接对象&#xf…

阶段3 1.Mybatis_07.Mybatis的连接池及事务_3 mybatis连接池的分类

2、mybatis中的连接池 mybatis连接池提供了3种方式的配置&#xff1a; 配置的位置&#xff1a; 主配置文件SqlMapConfig.xml中的dataSource标签&#xff0c;type属性就是表示采用何种连接池方式。 type属性的取值&#xff1a; POO…

Monkey Test简单介绍

什么是Monkey Test&#xff1f;顾名思义&#xff0c;就像一只猴子一样&#xff0c;它的下一步具有随机性。所以Monkey Test可以简单地理解为动作随机性测试&#xff01; Monkey是android模拟器或设备上运行的一个程序。它可以生成伪随机用户事件&#xff08;例如点击、触碰或滑…

monkey工具详解

第一部分&#xff1a;背景 1.为什么要开展压力测试&#xff1f; 提高产品的稳定性 提高产品的留存率 2.什么时候开始压力测试&#xff1f; 首轮功能测试通过后 下班后的夜间进行 第二部分&#xff1a;理论 1.什么是monkey&#xff1f; Monkey是发送伪随机用户事件的工…

monkey的基本使用

一、monkey介绍 1.功能&#xff1a;采用伪随机测试的方式&#xff0c;来完成app的稳定性测试 2.执行原理&#xff1a;通过monkey的shell脚本去执行安卓系统中自带的monkey.jar工具 3.通常用于盲测&#xff0c;压力测试和冒烟测试 1&#xff09;盲测&#xff1a;忽略功能和业务逻…

Monkey使用详解

App monkey 使用篇 安装包下载&#xff1a; 下载mumu模拟器地址&#xff1a;http://mumu.163.com/baidu/ 下载adb安装包 地 址&#xff1a;http://www.downza.cn/soft/219906.html Adb环境变量 配 置&#xff1a;在path里新建adb安装路径即可。 cmd 打开命令提示符窗口输入&a…

monkey的基本定义及基本使用(菜鸟学习中)

一.monkey的定义 1.(转自 https://blog.csdn.net/beyond_f/article/details/78543070 ) Monkey程序由Android系统自带&#xff0c;使用Java语言写成&#xff0c;在Android文件系统中的存放路径是&#xff1a;/system/framework/monkey.jar&#xff1b; Monkey.jar程序是由一…

Android测试--monkey详细到炸的总结

一、Monkey简介&#xff1a; Monkey是Android中的一个命令行工具&#xff0c;可以运行在模拟器里或者现实设备中&#xff0c;向系统发送伪随机的用户事件流&#xff08;点击、滑动、Application切换、横竖屏、应用关闭&#xff09;实现对正在开发的应用程序进行压力测试。monk…

APP稳定性测试利器 Monkey介绍、实战使用、日志分析

第一、Monkey简介 Monkey是什么&#xff1f; Monkey 是安卓官方提供的一个命令行工具&#xff0c;可以运行在Android模拟器和实体手机上。通过Monkey 来模拟用户的触摸、点击、滑动、系统按键的操作&#xff0c;来对APP进行压力测试、稳定性测试。换句话说&#xff0c;就是在乱…

Clumsy弱网、丢包测试工具

一、下载clumsy安装包&#xff0c;解压后打开clumsy.exe 二、ping www.baidu.com查看是否模拟成功 三、pc端模拟丢包和网络延迟 四、APP端模拟丢包和网络延迟 1.手机连接抓包工具charles 2.打开clumsy&#xff0c;在过滤器filtering中输入outbound and ip.DstAddr 192.168.…

【弱网】clumsy的filter语法设置

clumsy 官方说明 jagt WinDivert 的语法 https://github.com/basil00/Divert/wiki/WinDivert-Documentation#7-filter-languageDivert大神们的改版 clumsy-regoutbound 发送 inbound