Spring-Mybatis源码解析--Mybatis配置文件解析


前言

Spring 整合Mybatis 后,如何对其配置文件进行加载和解析,如何进行数据的CRUD。


一、准备工作

1.1 依赖准备:

<dependency>
   <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>

1.2 配置文件准备:

<?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="dbconfig.properties"/>

    <environments default="development">
        <environment id="development">
            <!-- JDBC 和 POOLED 会解析别名-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <environment id="development1">
            <!-- JDBC 和 POOLED 会解析别名-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <environment id="development2">
            <!-- JDBC 和 POOLED 会解析别名-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>

    </environments>

    <mappers>
        <mapper resource="mapper/Test.xml"/>
    </mappers>
</configuration>

dbconfig.properties:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3406/mybatis
jdbc.username=root
jdbc.password=ddsoft

1.3 代码准备:

TestMapper:

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.Map;

public interface TestMapper {
    @Select("select '123'")
    public String selectOne(String str);

    public Map selectById(@Param(value = "id")  String id);

}

TestMapper.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">
<mapper namespace="com.example.springdabaihua.mapper.TestMapper">


    <select id="selectById" resultType="java.util.Map">
        select * from tb_user
        where 1=1
        <if test="id != null and id != ''">
            and id =#{id}
        </if>
    </select>
</mapper>

TestMain:

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;
import java.util.Map;

public class TestMain {
    public static void main(String[] args) throws IOException {
        String resource ="mybatis-config.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        // 解析数据源 解析 xml 中的sql 语句
        SqlSessionFactory sqlSessionFactory =   new SqlSessionFactoryBuilder().build(reader);
        // 解析 Executor 执行器
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 执行sql
        Object abc = sqlSession.selectOne("com.example.springdabaihua.mapper.TestMapper.selectOne","123");
        System.out.println("abc = " + abc);

        Map abc1 =(Map) sqlSession.selectOne("com.example.springdabaihua.mapper.TestMapper.selectById","1");
        abc1.entrySet().stream().forEach(e->{
            System.out.println("e.toString() = " + e.toString());
        });
//        System.out.println("abc1 = " + abc1);
    }
}

二、配置文件加载:

2.1 SqlSessionFactoryBuilder().build(reader)

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
		// 配置文件加载读取 成为 Document 文件
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
		// 具体标签值解析并设置
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            if (reader != null) {
                reader.close();
            }
        } catch (IOException var13) {
        }

    }

    return var5;
}

2.2 parser.parse() :

// 解析资源并返回 Configuration

public Configuration parse() {
    if (this.parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    } else {
        this.parsed = true;
        // 解析configuration 标签
        this.parseConfiguration(this.parser.evalNode("/configuration"));
        return this.configuration;
    }
}

parseConfiguration:

private void parseConfiguration(XNode root) {
    try {
		// 解析获取 数据库连接等
        this.propertiesElement(root.evalNode("properties"));
		// 解析 全局配置
        Properties settings = this.settingsAsProperties(root.evalNode("settings"));
		// 读取虚拟文件,一般不用
        this.loadCustomVfs(settings);
		// 加载 日志的实现
        this.loadCustomLogImpl(settings);
		// 加载别名处理器 TypeAliasRegistry  我们在Mapper.xml 文件中可以 直接在 
// <select id="selectById" resultMap="int"> resultMap 中直接使用一些类型 mybatis 会进行类型的自动转换 别名转换为 基本的数据类型
        this.typeAliasesElement(root.evalNode("typeAliases"));
		// 解析插件 分页等 加入到 InterceptorChain 责任链中
        this.pluginElement(root.evalNode("plugins"));
		//  工厂类配置一般不配置直接使用默认的 Default 开头的
        this.objectFactoryElement(root.evalNode("objectFactory"));// 
        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
		// 加载全部的配置文件
        this.settingsElement(settings);
		// 加载 properties key-value 值 得到TransactionFactory 事务工厂 和 DataSourceFactory 数据源
		// 数据库事务及连接处理
        this.environmentsElement(root.evalNode("environments"));
		//  数据库厂商id 一般不使用
        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
		// 解析类型处理器 TypeHandlerRegistry  java 类型到 sql 数据类型转换的类型处理器
		// spring 可以自动转换 对传入sql 的参数进行转换,对返回的结果集数据转换为java 类型
        this.typeHandlerElement(root.evalNode("typeHandlers"));
		// 解析mapper xml 资源 放入到 Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap(); 
		// map 的代理对象,只是放入接口类,后面通过jdk 的动态代理生成mapper 的代理对象
        this.mapperElement(root.evalNode("mappers"));
    } catch (Exception var3) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
}

这里重点对 mysql 数据源的配置 以及对mapper.xml 配置进行分析:
environmentsElement: mysql 数据源的配置

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (this.environment == null) {
            this.environment = context.getStringAttribute("default");
        }

        Iterator var2 = context.getChildren().iterator();

        while(var2.hasNext()) {
            XNode child = (XNode)var2.next();
            String id = child.getStringAttribute("id");
            if (this.isSpecifiedEnvironment(id)) {
				// 事务工厂获取  通过 transactionManager <transactionManager type="JDBC"/>
				// 通过type JDBC 别名  判定使用什么事务工厂,可以获取数据库连接
				// spring整合后 拿到spring 的 事务工厂 SpringManagedTransactionFactory
                TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
				// 数据库工厂 
                DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = (new Environment.Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
                this.configuration.setEnvironment(environmentBuilder.build());
                break;
            }
        }
    }

}

这里在 (child.evalNode(“dataSource”) 直接从环境变量里获取里数据的驱动,用户名,密码等信息,这些信息是由 this.propertiesElement(root.evalNode(“properties”)); 完成将解析后端的数据放入到 Properties variables 属性中;

mapperElement : 对于mapper.xml 解析,对于mapper 资源加载的几种方式
在这里插入图片描述
读取mapper 标签的配置:

 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        Iterator var2 = parent.getChildren().iterator();

        while(true) {
            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String resource;
                if ("package".equals(child.getName())) {
                    resource = child.getStringAttribute("name");
                    this.configuration.addMappers(resource);
                } else {
                    resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    InputStream inputStream;
                    Throwable var8;
                    XMLMapperBuilder mapperParser;
                    if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
                        inputStream = Resources.getResourceAsStream(resource);
                        var8 = null;

                        try {
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } catch (Throwable var32) {
                            var8 = var32;
                            throw var32;
                        } finally {
                            if (inputStream != null) {
                                if (var8 != null) {
                                    try {
                                        inputStream.close();
                                    } catch (Throwable var29) {
                                        var8.addSuppressed(var29);
                                    }
                                } else {
                                    inputStream.close();
                                }
                            }

                        }
                    } else if (resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        inputStream = Resources.getUrlAsStream(url);
                        var8 = null;

                        try {
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } catch (Throwable var31) {
                            var8 = var31;
                            throw var31;
                        } finally {
                            if (inputStream != null) {
                                if (var8 != null) {
                                    try {
                                        inputStream.close();
                                    } catch (Throwable var30) {
                                        var8.addSuppressed(var30);
                                    }
                                } else {
                                    inputStream.close();
                                }
                            }

                        }
                    } else {
                        if (resource != null || url != null || mapperClass == null) {
                            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                        }

                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        this.configuration.addMapper(mapperInterface);
                    }
                }
            }

            return;
        }
    }
}

从这里看到 如果是 package 则直接放入到MapperRegistry 的 knownMappers 中,如果是其他的,则依次解析 resource,url,class;本文中通过 resource 进行资源的设置;
mapperParser.parse():

public void parse() {
    if (!this.configuration.isResourceLoaded(this.resource)) {
		// mapper .xml 是否被解析过 
		// 解析mapper 标签 并将解析完成的结果放入到 Configuration 中的  Map<String, MappedStatement> mappedStatements
        this.configurationElement(this.parser.evalNode("/mapper"));
        this.configuration.addLoadedResource(this.resource);
        // mapper 接口层方法的绑定,解析了@Select @Insert 等标签 
        // 并将解析完成的结果放入到 Configuration 中的  Map<String, MappedStatement> mappedStatements
        this.bindMapperForNamespace();
    }

    this.parsePendingResultMaps();
    this.parsePendingCacheRefs();
    this.parsePendingStatements();
}

this.configurationElement(this.parser.evalNode(“/mapper”)):解析mapper.xml 中的sql

private void configurationElement(XNode context) {
    try {
		// 命名空间获取
        String namespace = context.getStringAttribute("namespace");
        if (namespace != null && !namespace.isEmpty()) {
		   //  为Mapper xml 文件中的crud 构建一个个的MappedStatement 对象
            this.builderAssistant.setCurrentNamespace(namespace);
			// 解析 mapper.xml 引用其他 mapper.xml 
            this.cacheRefElement(context.evalNode("cache-ref"));
			// 解析二级缓存
            this.cacheElement(context.evalNode("cache"));
  			 //  解析parameterMap
            this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
			// 解析 resultMap 
            this.resultMapElements(context.evalNodes("/mapper/resultMap"));
			// 解析sql
            this.sqlElement(context.evalNodes("/mapper/sql"));
			// 解析 select|insert|update|delete
            this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } else {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
    } catch (Exception var3) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
    }

}

this.bindMapperForNamespace() 方法与sql 语句的绑定:

private void bindMapperForNamespace() {
   String namespace = this.builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class<?> boundType = null;

        try {
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException var4) {
        }

        if (boundType != null && !this.configuration.hasMapper(boundType)) {
            this.configuration.addLoadedResource("namespace:" + namespace);
            // 闯入类路径进行解析
            this.configuration.addMapper(boundType);
        }
    }

}

public <T> void addMapper(Class<T> type) {
   if (type.isInterface()) {
        if (this.hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }

        boolean loadCompleted = false;

        try {
        	
            this.knownMappers.put(type, new MapperProxyFactory(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
             // sql 组装和绑定
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                this.knownMappers.remove(type);
            }

        }
    }

 }
public void parse() {
     String resource = this.type.toString();
     if (!this.configuration.isResourceLoaded(resource)) {
         this.loadXmlResource();
         this.configuration.addLoadedResource(resource);
         this.assistant.setCurrentNamespace(this.type.getName());
         this.parseCache();
         this.parseCacheRef();
         // 获取类中的所有方法
         Method[] var2 = this.type.getMethods();
         int var3 = var2.length;

         for(int var4 = 0; var4 < var3; ++var4) {
             Method method = var2[var4];
             if (this.canHaveStatement(method)) {
             	// 如果不是桥接方法,也不是 Default 方法 
                 if (this.getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent() && method.getAnnotation(ResultMap.class) == null) {
                     // 是否@Select 注解修饰,如果是则 进行参数的解析
                     this.parseResultMap(method);
                 }

                 try {
                 	// 语句的解析
                     this.parseStatement(method);
                 } catch (IncompleteElementException var7) {
                     this.configuration.addIncompleteMethod(new MethodResolver(this, method));
                 }
             }
         }
     }

     this.parsePendingMethods();
 }

三、 执行sql :

public static void main(String[] args) throws IOException {
   String resource ="mybatis-config.xml";
     Reader reader = Resources.getResourceAsReader(resource);
     // 解析数据源 解析 xml 中的sql 语句
     SqlSessionFactory sqlSessionFactory =   new SqlSessionFactoryBuilder().build(reader);
     // 解析 Executor 执行器
     SqlSession sqlSession = sqlSessionFactory.openSession();
     // 执行sql
     Object abc = sqlSession.selectOne("com.example.springdabaihua.mapper.TestMapper.selectOne","123");
     System.out.println("abc = " + abc);

     Map abc1 =(Map) sqlSession.selectOne("com.example.springdabaihua.mapper.TestMapper.selectById","1");
     abc1.entrySet().stream().forEach(e->{
         System.out.println("e.toString() = " + e.toString());
     });
     System.out.println("abc1 = " + abc1);
 }

sqlSession.selectOne() 方法执行sql:

public <T> T selectOne(String statement, Object parameter) {
    List<T> list = this.selectList(statement, parameter);
      if (list.size() == 1) {
          return list.get(0);
      } else if (list.size() > 1) {
          throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
      } else {
          return null;
      }
  }
public <E> List<E> selectList(String statement, Object parameter) {
      return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    List var6;
    try {
    	// 从解析后的map 中 获取sql
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        // 通过执行器执行并返回结果
        var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
    } catch (Exception var10) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
    } finally {
        ErrorContext.instance().reset();
    }

    return var6;
}

总结

Spring 中主要通过 SqlSessionFactoryBuilder().build(reader) 方法完成 事务管理器,数据源以及sql 语句的解析和绑定,并将解析好的sql 放入到map 中,执行时通过key 获取具体的sql 语句交由执行器执行,然后返回执行结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值