张坤的个人博客

  • 首页
  • 分类
  • 标签
  • 日志

  • 搜索
Jenkins RabbitMQ Zookeeper IDEA Logstash Kibana ELK NIO Netty Spring Cloud Golang DataX Elasticsearch React Native Mysql H2 Socket Spring Boot Kafka Mybatis Sqlmap Vue Postgresql Docker Vert.x Flutter Flink Redis

Mybatis源码日记(一)

发表于 2020-05-29 | 分类于 Mybatis | 0 | 阅读次数 30

Mybatis执行对数据库的操作主要通过Sqlsession完成的,Sqlsession是个接口,它有两个实现类,DefaultSqlSession和SqlSessionManager。

  • DefaultSqlSession:Mybatis在构建的时候,默认创建了DefaultSqlSessionFactory,DefaultSqlSessionFactory的openSesstion()方法返回DefaultSqlSession也就是说Mybatis默认的SqlSession实现通常使用的就是这个,非线程安全。

  • SqlSessionManager:实现了SqlSession,SqlSessionFactory,这个可以开启线程安全的Sqlsession,通过startManagedSession()方法开启线程安全的SqlSession。也可以直接使用sqlSessionProxy对象执行sql语句。通过startManagedSession()开启线程安全后,切记不能openSession(),或者startManagedSession()再次获取新的SqlSession,而是通过getConnection()获取数据库连接来执行Sql。

SqlSessionManager有三个成员变量

// 用来创建Sqlsession
private final SqlSessionFactory sqlSessionFactory;
// SqlSession的实现通过这个动态代理session实例执行
private final SqlSession sqlSessionProxy;
// 先通过ThreadLocal<SqlSession> localSqlSession获取sqlsession,如果获取不到则通过:openSession新开一个session,如果存在就使用。
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

一般情况下,我们通过以下方法可以获取到Sqlsession对象

Reader resourceAsStream = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

在调用SqlSessionFactoryBuilder的build()方法时,通过源码可以发现,build方法被重载了9次。

org.apache.ibatis.session.SqlSessionFactoryBuilder:

public SqlSessionFactory build(Reader reader);
public SqlSessionFactory build(Reader reader, String environment);
public SqlSessionFactory build(Reader reader, Properties properties);
// 上面三种方法其实最后都会调用这个方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties);

public SqlSessionFactory build(InputStream inputStream);
public SqlSessionFactory build(InputStream inputStream, String environment);
public SqlSessionFactory build(InputStream inputStream, Properties properties);
// 上面这三种方法最后都会调用这个方法,和第4行方法的区别是,这里接收的是一个字节流,而第4行接收的是一个字符流
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties);

// 这个方法最终都会被第4个和第8个方法通过build(parser.parse())方法调用
public SqlSessionFactory build(Configuration config) {
    // 这里返回了DefaultSqlSessionFactory
    return new DefaultSqlSessionFactory(config);
}

第4行和第8行的具体实现其实都是一样的,只是接收的流不一样,一个是字节流,一个是字符流,以第8行方法实现为例:

org.apache.ibatis.session.SqlSessionFactoryBuilder:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // 解析mybatis-config.xml配置文件
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 调用第9行的build方法返回DefaultSqlSessionFactory对象,通过DefaultSqlSessionFactory的openSession()方法可以返回DefaultSqlSession对象
        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,可以看到XMLConfigBuilder和SqlSessionFactoryBuilder有些类似,XMLConfigBuilder的构造方法被重载了7次。

public XMLConfigBuilder(Reader reader);
public XMLConfigBuilder(Reader reader, String environment);
// 上面两个构造方法也是调用这个,用的是字符流
public XMLConfigBuilder(Reader reader, String environment, Properties props);

public XMLConfigBuilder(InputStream inputStream);
public XMLConfigBuilder(InputStream inputStream, String environment);
// 上面两个构造方法也是调用这个,用的是字节流
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props);

// 第3行和第6行方法都是调用的这个方法。这个方法被私有化了,应该是作者不允许有人从外部直接调用
private XMLConfigBuilder(XPathParser parser, String environment, Properties props);

第7行XMLConfigBuilder构造方法实现

org.apache.ibatis.builder.xml.XMLConfigBuilder

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    // parsed标识是否被解析
    this.parsed = false;
    this.environment = environment;
    // xpath解析器
    this.parser = parser;
}

回到SqlSessionFactoryBuilder,接下来是parser.parse()实现,这个方法主要是对mybatis-config.xml配置文件做了解析

org.apache.ibatis.builder.xml.XMLConfigBuilder:

public Configuration parse() {
    // 判断是否已经被解析过了
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // 接下来要开始解析了,所以提前将它标识为已解析,避免后续调用parse时又被解析一遍
    parsed = true;
    // mybatis-config.xml根节点就是configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

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></properties>
    <settings>
        <setting name="" value=""/>
    </settings>
    <typeAliases></typeAliases>
    <plugins>
        <plugin interceptor=""></plugin>
    </plugins>
    <objectFactory type=""></objectFactory>
    <objectWrapperFactory type=""></objectWrapperFactory>
    <reflectorFactory type=""></reflectorFactory>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value=""/>
                <property name="url" value=""/>
                <property name="username" value=""/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    <databaseIdProvider type=""></databaseIdProvider>
    <typeHandlers></typeHandlers>
    <mappers>
        <!-- <mapper resource=""/> -->
        <!-- mapper和package二选一 -->
        <package name=""/>
    </mappers>
</configuration>

最后看下parseConfiguration()方法做了哪些事,parseConfiguration()将mybatis-config.xml配置所有的子节点一一解析。

org.apache.ibatis.builder.xml.XMLConfigBuilder:

private void parseConfiguration(XNode root) {
    try {
        propertiesElement(root.evalNode("properties"));
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        loadCustomVfs(settings);
        loadCustomLogImpl(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);
    }
}

最后,parse()方法返回Configuration对象,在SqlSessionFactoryBuilder中build方法接收这个对象并调用,返回DefaultSqlSessionFactory对象。通过openSession()方法可以返回DefaultSqlSession,DefaultSqlSession继承自SqlSession。

参考:

https://mybatis.org/mybatis-3/zh/index.html

https://blog.csdn.net/u012746134/article/details/94658840

# Jenkins # RabbitMQ # Zookeeper # IDEA # Logstash # Kibana # ELK # NIO # Netty # Spring Cloud # Golang # DataX # Elasticsearch # React Native # Mysql # H2 # Socket # Spring Boot # Kafka # Mybatis # Sqlmap # Vue # Postgresql # Docker # Vert.x # Flutter # Flink # Redis
Sqlmap学习笔记(四)
Sqlmap学习笔记(三)
  • 文章目录
  • 站点概览
会Coding的猴子

会Coding的猴子

57 日志
19 分类
28 标签
RSS
Github
Creative Commons
© 2021 会Coding的猴子
由 Halo 强力驱动
|
主题 - NexT.Gemini v5.1.4

湘ICP备18011740号