MyBatis入门到源码分析 | 【Mybatis加载核心配置文件流程】

Mybatis入门案例

创建maven工程,引入依赖配置项

pom.xml中引入maven依赖:

<dependencies>
        <!--MyBatis坐标-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
</dependencies>

数据库

CREATE DATABASE mybatis;
USE mybatis;

CREATE TABLE `t_user` (
  `uid` INT(11) NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(40) DEFAULT NULL,
  `sex` VARCHAR(10) DEFAULT NULL,
  `birthday` DATE DEFAULT NULL,
  `address` VARCHAR(40) DEFAULT NULL,
  `level` VARCHAR(10) DEFAULT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=INNODB  DEFAULT CHARSET=utf8;


insert  into `t_user`(`uid`,`username`,`sex`,`birthday`,`address`,`level`) values (1,'zs','男','2018-08-08','北京','LOW'),(2,'ls','女','2018-08-30','武汉','MIDDLE'),(3,'ww','男','2018-08-08','北京','LOW');

javabean

User.java类

public class User implements Serializable {
    private int uid; //用户id
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
    // get、set...
}

jdbc.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=root

mybatis核心配置文件

resources目录下创建:mybatis-config.xml,并写入以下内容:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 引入外部配置文件 -->
    <properties resource="jdbc.properties" />

    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="useActualParamName" value="true"/>
        <setting name="logImpl" value="SLF4J"/>
        <!-- 自定义的VFS -->
        <setting name="vfsImpl" value="org.byron4j.vfs.MyVFS"/>
    </settings>



    <!--别名方式一:单个-->
    <typeAliases>
        <typeAlias alias="user2" type="org.byron4j.bean.User"/>
        <typeAlias alias="int" type="java.lang.Integer"/>

        <!--方式二,该包下的所有类都默认取别名为类名,并且不区分大小写
            如:  org.byron4j.bean.Product 别名将为  product、Product、ProDuCt  都可以
        -->
        <package name="org.byron4j.bean"/>
    </typeAliases>

    
    <typeHandlers>
        <!--方式一: 单个处理器-->
        <!--<typeHandler handler="org.byron4j.handler.MyHandler" javaType="org.byron4j.bean.User"/>-->
        

        <typeHandler handler="org.byron4j.handler.MyDateHandler" javaType="long" jdbcType="TIMESTAMP"/>

        <!--统一自动查找-->
        <package name="org.byron4j.handler"/>
    </typeHandlers>

    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>

        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--<databaseIdProvider type="DB_VENDOR" />-->


    <mappers>
        <mapper resource="org/byron4j/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

mapper.xml映射文件

resources目录下创建目录org/byron4j/mapper目录(注意该目录需要和接口的包层级一致),新建一个UserMapper.xml,写入以下内容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace: 名称空间,接口的全限定名-->
 <mapper namespace="org.byron4j.mapper.UserMapper">
    <!--
        id属性: 和接口的方法名一致
        resultType属性:返回类型,写全限定名,集合则写元素的泛型类型
        标签体:sql语句
    -->
     <!-- 这里使用了别名User01不区分大小写;只有一个参数时可以随便写参数名比如这里aaa,但是实际开发中建议和参数名保持一致 -->
    <select id="findById" resultType="User01">
        select *  from t_user where uid = #{aaa}
    </select>

</mapper>


Mapper.java接口

org.byron4j.mapper创建一个接口:UserMapper.java

public interface UserDao {

    User findById(Integer id);
}

测试类

编写一个测试类:

@Test
    public void test() throws Exception {


        // 1. 读取配置文件作为流
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");

        // 2. 根据核心配置文件获取SqlSessionFactory对象
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is);


        
        // 3.获取SqlSession对象(类似connection)
        SqlSession sqlSession = build.openSession();
        // 4. 获取代理对象
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        // 5. 操作,释放资源
        User user = mapper.findById(2);
        System.out.println(user);
        sqlSession.close();
        is.close();
    }

这是一个简单入门案例,会用了之后。我们就可以来学习Mybatis加载核心配置文件流程了。

Mybatis加载核心配置文件流程

1.入口

org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String)
这个方法第一个参数是一个mybatis配置文件代表的输入流;第二个参数是环境,这里我们缺省。

它调用了下面的方法:
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)

在我们的案例中只有第一个参数有值,后面的都是null。

下面开始跟踪源代码

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
	  // 创建一个【XMLConfigBuilder】解析器类
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
}

2.创建一个【XMLConfigBuilder】解析器类

继续跟踪org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(核心配置xml文件, 环境env, 不指定properties则为null) 方法:

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()),
         environment, props);
  }

方法体又调用了重载构造器:org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)
这里的第一个参数是一个XPathParser类型的解析器(看名字知道是XPath的解析器)。

这里用到了XPathParser构造器,我们接下来看看。

3.【XPathParser】解析器的构造器

构造器:org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, true, java.util.Properties为null, org.xml.sax.EntityResolver)
第四个参数是一个实体解析器:org.apache.ibatis.builder.xml.XMLMapperEntityResolver,实现了org.xml.sax.EntityResolver 接口;

所以我们又需要来查看一下XMLMapperEntityResolver这个类是啥玩意。

4.【XMLMapperEntityResolver】实体解析器

org.apache.ibatis.builder.xml.XMLMapperEntityResolver,实现了org.xml.sax.EntityResolver 接口;实现这个接口后,可以用于自定义拦截解析XML,比如使用了mybatis的定义约束就可以条件判断做一些其他的逻辑。
以下是一个简单地示例,仅仅做一个说明:

public class MyResolver implements EntityResolver {
     public InputSource resolveEntity (String publicId, String systemId)
     {
       if (systemId.equals("http://www.myhost.com/today")) {
                // 包含了自定义的,则自己解析处理
         MyReader reader = new MyReader();
         return new InputSource(reader);
       } else {
                // use the default behaviour
         return null;
       }
     }
}

XMLMapperEntityResolver这个解析器就是判断了下systemId(xml的文档声明)包含org/apache/ibatis/builder/xml/mybatis-3-config.dtd就使用mybatis自定义的InputSource(xml文件配置输入源):

private InputSource getInputSource(String path, String publicId, String systemId) {
    InputSource source = null;
    if (path != null) {
      try {
		// path是mybatis-3-config.dtd的路径
        InputStream in = Resources.getResourceAsStream(path);  
        source = new InputSource(in);
        source.setPublicId(publicId);
        source.setSystemId(systemId);        
      } catch (IOException e) {
        // ignore, null is ok
      }
    }
    return source;
}

所以你应该可以理解通过XMLMapperEntityResolver只是获取了一个InputSource对象(SAX解析器将使用这个InputSource对象来确定如何读取XML输入)。

5.重新回到第【3】步来:构造XPathParser

XMLMapperEntityResolver了解了之后,我们现在回到XPathParser这里,构造XPathParserorg.apache.ibatis.parsing.XPathParser#XPathParser(java.io.InputStream, boolean, java.util.Properties, org.xml.sax.EntityResolver)

构造器:(在XPathParser类的123行处)

// inputStream是我们项目中的mybatis核心配置文件,如:`D:\007\mybatis_demo01\target\classes\SqlMapperConfig.xml`
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
	// 这个构造器会初始化内部的一个属性:javax.xml.xpath.XPath
    commonConstructor(validation, variables, entityResolver);
	// 初始化内部属性:org.w3c.dom.Document,得到一个dom文档
    this.document = createDocument(new InputSource(inputStream));
}

6.重新回到第【2】步来,81行左右:

XPathParser的构造明白后,重新回到第【2】步,看一下XMLConfigBuilder的构造逻辑

代码位置XMLConfigBuilder:org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)
实际调用了85行左右的代码的方法:org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)
方法如下,这里主要看一下第一行代码也是Configuration的创建:

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    // 设置一些属性
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

这个方法里面的逻辑我们接着来细看下。

6.1. 第一行super(new Configuration());

第一行代码调用了父类的构造器,即:org.apache.ibatis.builder.BaseBuilder#BaseBuilder

public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}

6.2. Configuration 类是Mybatis的核心配置类,再重要不过了!

我们来看看直接new出来的配置信息类的无参构造器: new 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);
}

这里面的都是对应我们在mybatis核心配置文件(mybatis-config.xml)中配置的属性值对应的具体的java类的映射。

Configuration类还定义了很多的属性,这里主要是关于加载的流程分析,不展开讨论这些属性,只在下面代码注释中列举几个说明下:

public class Configuration {

  // 环境配置:<environment id="dev">
  protected Environment environment;

  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;

  // 输出日志时的前缀设置
  protected String logPrefix;
  // 指定日志实现类;否则默认按顺序:SLF4J、Apache Commons Logging、Log4j 2、Log4j、JDK logging 查找实现;
  protected Class <? extends Log> logImpl;
  // 虚拟文件系统,会读取接口Mapper.class文件
  protected Class <? extends VFS> vfsImpl;
  // 默认一级缓存为session级别(另一个值是statement级别)
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  // 在调用这些方法的时候,延迟加载的对象会在调用这些方法之前被加载出来
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  // 默认的执行器的类型,SIMPLE(默认), REUSE, BATCH
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  // 设置 mapper.xml中表的列(column)和javabean的属性(properties)自动映射的行为
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  protected boolean lazyLoadingEnabled = false;
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  protected String databaseId;
  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
   */
  protected Class<?> configurationFactory;

  
  // 映射器注册表:使用代理模式;是由MapperProxy代理创建对应的接口Mapper的实例的
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  // 拦截器链,里面list属性interceptors封装了所有的拦截器对象(org.apache.ibatis.plugin.Interceptor)
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  // 类型处理器注册表
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  // 类型别名注册表
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  // 语言驱动程序注册表(这些对象一般指org.apache.ibatis.scripting.LanguageDriver的子类)
  // mybatis默认org.apache.ibatis.scripting.xmltags.XMLLanguageDriver类型
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<String>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();

  /*
   * A map holds cache-ref relationship. The key is the namespace that
   * references a cache bound to another namespace and the value is the
   * namespace which the actual cache is bound to.
   */
  protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
}

7.再回到第一步SqlSessionFactoryBuilder#build

截至目前为止,已经构造好了 XMLConfigBuilder 对象了。

我们再回到第一步org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties),也就是SqlSessionFactoryBuilder类的75行左右。

Configuration对象public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
	  // parser已经拿到了;【截至目前为止,已经构造好了 XMLConfigBuilder  对象了。】
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
	  // 开始追踪这里....[parser.parse()得到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.
      }
    }
}

7.1 查看org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)方法

build(parser.parse()) 这行代码就是调用这个方法:

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

这个方法传入了一个Configuration对象作为参数,这个参数我们知道是通过parser.parse()得到的。
所以来关注下parser.parse()

7.2 parser.parse()方法

其实就是:org.apache.ibatis.builder.xml.XMLConfigBuilder#parse方法。在XMLConfigBuilder类的94行左右。

public Configuration parse() {
    if (parsed) {// mybatis核心配置文件只允许加载一次
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 是使用XPath获取根节点:就是config.xml里面的根节点: `<configuration>`。
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

parser.evalNode("/configuration") 这行代码是使用XPath获取根节点:就是config.xml里面的根节点: <configuration>

7.3 然后再执行这个方法XMLConfigBuilder#parseConfiguration

再来看看这行代码parseConfiguration(parser.evalNode("/configuration"));

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration,在XMLConfigBuilder类的103行左右。

这个方法就是解析读取mybatis-config.xml文件的各个配置项,并且赋值到configuration里面去(底层是configuration.setXXX方法设置):

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      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);
    }
}

这里我们就选取这一行代码 environmentsElement(root.evalNode("environments")); 简单追踪下:

org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement方法如下,你应该可以看到一些熟悉的字眼(数据源的配置信息):

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

就是获取environments节点的信息:

<environments default="dev">
	<environment id="dev">
		<transactionManager type="JDBC"/>
		<dataSource type="POOLED">
			<property name="driver" value="${driver}"/>
			<property name="url" value="${url}"/>
			<property name="username" value="${username}"/>
			<property name="password" value="${password}"/>
		</dataSource>
	</environment>

	<environment id="test">
		<transactionManager type="JDBC"/>
		<dataSource type="POOLED">
			<property name="driver" value="${driver}"/>
			<property name="url" value="${url}"/>
			<property name="username" value="${username}"/>
			<property name="password" value="${password}"/>
		</dataSource>
	</environment>
</environments>

这就是MyBatis核心配置对象Configuration解析加载的主要流程。

发布了251 篇原创文章 · 获赞 285 · 访问量 122万+

猜你喜欢

转载自blog.csdn.net/zixiao217/article/details/103978794
今日推荐