五Mybatis 核心对象之 Configuration 对象初始化详解

Posted archerLuo罗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了五Mybatis 核心对象之 Configuration 对象初始化详解相关的知识,希望对你有一定的参考价值。

  • 源码地址:https://github.com/RononoaZoro/mybatis-book/tree/master 的 mybatis-book ( mybatis-chapter04 )
  • 文章内容出自《Mybatis 3 源码深度解析》第四章
  • 自己实现代码地址:https://github.com/RononoaZoro/mybatis-book/tree/master 的 archer-mybatis

1、说明

  • 本文以 XML 格式配置文件为例子,详解 Configuration 的初始化过程
  • 其他配置文件类型(如:注解形式,yml, properties 等)请按照相同思路参考源码

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>
	<settings>
		<setting name="useGeneratedKeys" value="true"/>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
		<setting name="logImpl" value="LOG4J"/>
	</settings>

	<environments default="dev" >
		<environment id="dev">
			<transactionManager type="JDBC">
				<property name="" value="" />
			</transactionManager>
			<dataSource type="UNPOOLED">
				<property name="driver" value="org.hsqldb.jdbcDriver" />
				<property name="url" value="jdbc:hsqldb:mem:mybatis" />
				<property name="username" value="sa" />
				<property name="password" value="" />
			</dataSource>
		</environment>
		<environment id="qa">
			<transactionManager type="JDBC">
				<property name="" value="" />
			</transactionManager>
			<dataSource type="UNPOOLED">
				<property name="driver" value="org.hsqldb.jdbcDriver" />
				<property name="url" value="jdbc:hsqldb:mem:mybatis_qa" />
				<property name="username" value="admin" />
				<property name="password" value="admin" />
			</dataSource>
		</environment>
	</environments>

	<mappers>
		<mapper resource="mapper/UserMapper.xml"/>
		<!--
		<mapper resource="file:///mybatis/com/blog4java/mybatis/example/mapper/UserMapper.xml"/>
		<mapper class="com.blog4java.mybatis.com.blog4java.mybatis.example.mapper.UserMapper"/>
		<package name="com.blog4java.mybatis.com.blog4java.mybatis.example.mapper"/>
		-->
	</mappers>
</configuration>

log4j.properties

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=INFO
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c,%L] - %m%n

UserEntity.java

package com.luo.example.entity;

import lombok.Data;

import java.util.Date;

@Data
public class UserEntity {
    private Long id;
    private String name;
    private Date createTime;
    private String password;
    private String phone;
    private String nickName;
}

UserMappe.javar

package com.luo.example.mapper;

import com.luo.example.entity.UserEntity;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper {

    List<UserEntity> listAllUser();

//    @Select("select * from user where id=#{userId,jdbcType=INTEGER}")
    UserEntity getUserById(@Param("userId") String userId);

    List<UserEntity> getUserByEntity(UserEntity user);

    UserEntity getUserByPhone(@Param("phone") String phone);

}

2、Configuration 初始化入口

package com.blog4java.mybatis.configuration;

import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.Configuration;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;

public class ConfigurationExample {

    @Test
    public void testConfiguration() throws IOException {
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        // 创建XMLConfigBuilder实例
        XMLConfigBuilder builder = new XMLConfigBuilder(reader);
        // 调用XMLConfigBuilder.parse()方法,解析XML创建Configuration对象
        Configuration conf = builder.parse();
    }

}

3、Configuration 初始化详细步骤解析

3.1、将配置文件读取到 IO 流

//详细读取过程,请参考 Java 官方有关流的 API 
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");

3.2、创建解析 XML 文件所需相关对象

3.2.1、创建 XMLConfigBuilder 对象

// 创建XMLConfigBuilder实例
XMLConfigBuilder builder = new XMLConfigBuilder(reader);

3.2.2、XMLConfigBuilder 的初始化源码解析

  • 1、创建了用于解析 XML 文件的对象(如:XPathParser, XMLMapperEntityResolve, Document 等)
  • 2、创建 Configuration 空对象
  • 3、说明:
    • 以下是部分 XMLConfigBuilder 核心代码,完整代码请参考源码
    • BaseBuilder 定义了各种配置类型解析 Builder 对象的通用属性和方法

XMLConfigBuilder

public class XMLConfigBuilder extends BaseBuilder {
    private boolean parsed;
    private final XPathParser parser;
    
    
    public XMLConfigBuilder(Reader reader) {
    	this(reader, null, null);
  	}
    
    public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    	this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  	}
    
    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;
  }
}

BaseBuilder

public abstract class BaseBuilder {
  protected final Configuration configuration;
  protected final TypeAliasRegistry typeAliasRegistry;
  protected final TypeHandlerRegistry typeHandlerRegistry;

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

3.2.3、解析 mybatis-config.xml ,保存到 Configuration 对象中

  • 完整解析过程参考源码
//调用XMLConfigBuilder.parse()方法,解析XML 保存到 Configuration 对象
Configuration conf = builder.parse();
  public Configuration parse() {
    // 防止parse()方法被同一个实例多次调用
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 调用XPathParser.evalNode()方法,创建表示configuration节点的XNode对象。
    // 调用parseConfiguration()方法对XNode进行处理
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  
  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
      
      //数据源解析,并创建 DataSource 对象
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      
      //Mapper 接口与 Mapper.xml 解析,并建立起绑定关系
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

4、自己实现简化版 Mybatis 的 Configuration

4.1、测试入口

package com.luo.example;

import com.luo.ibatis.builder.xml.ArcherXMLConfigBuilder;
import com.luo.ibatis.session.ArcherConfiguration;
import org.apache.ibatis.io.Resources;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;

public class ConfigurationExample {

    @Test
    public void testConfiguration() throws IOException {
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        // 创建XMLConfigBuilder实例
        ArcherXMLConfigBuilder builder = new ArcherXMLConfigBuilder(reader);
        // 调用XMLConfigBuilder.parse()方法,解析XML创建Configuration对象
        ArcherConfiguration conf = builder.parse();
    }

}

4.2、ArcherXMLConfigBuilder 类(对应 XMLConfigBuilder)

package com.luo.ibatis.builder.xml;

import com.luo.ibatis.builder.ArcherBaseBuilder;
import com.luo.ibatis.session.ArcherConfiguration;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.JdbcType;

import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;

public class ArcherXMLConfigBuilder extends ArcherBaseBuilder {

    private boolean parsed;
    private final XPathParser parser;
    private String environment;
    private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();


    public ArcherXMLConfigBuilder(Reader reader) {
        this(reader, null, null);
    }

    public ArcherXMLConfigBuilder(Reader reader, String environment) {
        this(reader, environment, null);
    }

    public ArcherXMLConfigBuilder(Reader reader, String environment, Properties props) {
        this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
    }

    public ArcherXMLConfigBuilder(InputStream inputStream) {
        this(inputStream, null, null);
    }

    public ArcherXMLConfigBuilder(InputStream inputStream, String environment) {
        this(inputStream, environment, null);
    }

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

    private ArcherXMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new ArcherConfiguration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
//        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }

    public ArcherConfiguration parse() {
        // 防止parse()方法被同一个实例多次调用
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        // 调用XPathParser.evalNode()方法,创建表示configuration节点的XNode对象。
        // 调用parseConfiguration()方法对XNode进行处理
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    private void parseConfiguration(XNode root) {
        try {
//            issue #117 read properties first

            //properties 节点解析
//            propertiesElement(root.evalNode("properties"));

            //settings 节点解析为 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"));

            // 将 settings 节点里的详细保存到 Configuration 对象中
            settingsElement(settings);

//            // read it after objectFactory and objectWrapperFactory issue #631
            //environments 节点解析 数据源信息相关,创建 DataSource 对象
            environmentsElement(root.evalNode("environments"));

            // 数据库厂商表示节点 解析
//            databaseIdProviderElement(root.evalNode("databaseIdProvider"));

            //typeHandler 节点解析
//            typeHandlerElement(root.evalNode("typeHandlers"));

            //mapper节点 文件解析
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

    private Properties settingsAsProperties(XNode context) {
        if (context == null) {
            return new Properties();
        }
        Properties props = context.getChildrenAsProperties();
        // Check that all settings are known to the configuration class
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        for (Object key : props.keySet()) {
            if (!metaConfig.hasSetter(String.valueOf(key))) {
                throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
            }
        }
        return props;
    }

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

以上是关于五Mybatis 核心对象之 Configuration 对象初始化详解的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis四大核心对象之ParameterHandler

MyBatis基础入门《五》核心配置文件

MyBatis 01 快速入门

mybatis之高级结果映射

mybatis 之 设计模式

mybatis 之 设计模式