保姆级教程:怒肝一夜,深入Mybatis源码SqlSession


前言

它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低。

配置文件解析,并保存于Configuration中

第一步是读取mybatis-config.xml配置文件,这里我们就没必要阅读这部分源码了,这里解析转换成InputStream,接下来就是基于这个输入流进行一系列牛逼的操作。

InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);

SqlSessionFactory是一个接口,没有构造方法

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.ibatis.session;

import java.sql.Connection;

public interface SqlSessionFactory {
    SqlSession openSession();

    SqlSession openSession(boolean var1);

    SqlSession openSession(Connection var1);

    SqlSession openSession(TransactionIsolationLevel var1);

    SqlSession openSession(ExecutorType var1);

    SqlSession openSession(ExecutorType var1, boolean var2);

    SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);

    SqlSession openSession(ExecutorType var1, Connection var2);

    Configuration getConfiguration();
}

直接点build方法进去,发现它调用了另外一个build方法

// 第一个build
public SqlSessionFactory build(InputStream inputStream) {
    return this.build((InputStream)inputStream, (String)null, (Properties)null);
}

// 第二个build
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
   SqlSessionFactory var5;
   try {
       // 创建XMLConfigBuilder对象 parser
       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

       // 第三个build
       var5 = this.build(parser.parse());
   } catch (Exception var14) {
       throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
   } finally {
       ErrorContext.instance().reset();

       try {
           inputStream.close();
       } catch (IOException var13) {
       }

   }

   return var5;
}

// 第三步的build 创建了一个DefaultSqlSessionFactory实例
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

使用alt + 7 查看SqlSessionFactoryBuilder类所有方法
在这里插入图片描述
SqlSessionFactoryBuilder提供了三种读取配置信息的重载方法,方法传参分别是:Reader、InputStream、Configuration。
创建XMLConfigBuilder对象,我们查看该对象的关系图
在这里插入图片描述
BaseBuilder的子类基本都是以Builder结尾的,这里使用的是建造者模式
这里我们大致猜出这个类是用来解析Mybatis全局配置文件用的,直接点进去看一下

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

Mybatis对应解析包如下,路径org.apache.ibatis.parsing
在这里插入图片描述
XPathParser基于XPath语法解析,用于解析Mybatis中
mybatis-config.xml以及mapper.xml文件。

XPathParser主要功能如下:

在这里插入图片描述
继续源码分析

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

构造了一个XMLConfigBuilder实例,给属性设置相应值。
再回到上一层代码

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

        // 上面已经分析完了,接下来我们看parser.parse()方法
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            inputStream.close();
        } catch (IOException var13) {
        }

    }

    return var5;
}

上面已经分析完了,接下来我们看parser.parse()方法
先看parser.parse()

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

// 该方法就是将mybatis-config.xml配置填充到configuration对象这一步
private void parseConfiguration(XNode root) {
    try {
        this.propertiesElement(root.evalNode("properties"));
        Properties settings = this.settingsAsProperties(root.evalNode("settings"));
        this.loadCustomVfs(settings);
        this.typeAliasesElement(root.evalNode("typeAliases"));
        this.pluginElement(root.evalNode("plugins"));
        this.objectFactoryElement(root.evalNode("objectFactory"));
        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
        this.settingsElement(settings);
        this.environmentsElement(root.evalNode("environments"));
        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        this.typeHandlerElement(root.evalNode("typeHandlers"));
        this.mapperElement(root.evalNode("mappers"));
    } catch (Exception var3) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
}

这张图找的网图,如有侵权联系删除
结合下面这张图,可以很直观的理解源码
在这里插入图片描述
parseConfiguration该方法就是将mybatis-config.xml配置填充到configuration对象,下面来看下这些标签内容是如何存入到configuration中?
在这里插入图片描述
propertiesElement()

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        Properties defaults = context.getChildrenAsProperties();
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");
        if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
        }

        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
        }

        Properties vars = this.configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }

        this.parser.setVariables(defaults);
        this.configuration.setVariables(defaults);
    }

}

typeAliasesElement()

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

        while(var2.hasNext()) {
            XNode child = (XNode)var2.next();
            String alias;
            if ("package".equals(child.getName())) {
                alias = child.getStringAttribute("name");
                this.configuration.getTypeAliasRegistry().registerAliases(alias);
            } else {
                alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");

                try {
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        this.typeAliasRegistry.registerAlias(clazz);
                    } else {
                        this.typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException var7) {
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
                }
            }
        }
    }

}

pluginElement()

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

        while(var2.hasNext()) {
            XNode child = (XNode)var2.next();
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
            this.configuration.addInterceptor(interceptorInstance);
        }
    }

}

这里只介绍配置文件是如何解析填充到configuration对象中,我还会出一篇专门讲解parseConfiguration方法的博客,即下面的文章,请务必加关注。
位于XMLConfigBuilder.java

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

接下来我们看下Mybatis是如何解析mapper.xml文件的
先展示下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>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false" />
				<property name="username" value="root" />
				<property name="password" value="xxx" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="mapper/UserMapper.xml"/>
	</mappers>
</configuration>

解析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;
                
                // 首先判断mapper.xml配置方式是否是package
                // 如果是,取出package对象包路径存入configuration对象中
                if ("package".equals(child.getName())) {
                    resource = child.getStringAttribute("name");
                    this.configuration.addMappers(resource);
                } else {

                    // 说明不是package的配置方式
                    // 接下来还有三种配置方式,统统取出来对应的值
                    // 1.resource 2.url 3.class
                    resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    XMLMapperBuilder mapperParser;
                    InputStream inputStream;

                    // 根据resource方式存放
                    if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);

                        // 根据resource存放目录,读取***mapper.xml
                        inputStream = Resources.getResourceAsStream(resource);

                        // 映射器比较复杂,调用XMLMapperBuilder
                        // 注意在循环中,每一个<mapper>标签都需要重新new XMLMapperBuilder对象,来解析
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                        mapperParser.parse();

                    // 根据url方式存放
                    } else if (resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        inputStream = Resources.getUrlAsStream(url);
                        
                        // 映射器比较复杂,调用XMLMapperBuilder
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                        mapperParser.parse();

                    // 根据class方式存放,使用类路径
                    } 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);

                        // 直接把这个映射存入configuration
                        // 把这些UserMapper接口保存到configuration对象中
                        this.configuration.addMapper(mapperInterface);
                    }
                }
            }

            return;
        }
    }
}

配置文件mybatis-config.xml和我们定义的映射文件*mapper.xml文件至此就全部解析完毕了。最终都是保存到Configuration大对象中,Configuration在这里是单例模式,整个Mybatis中只有一个Configuration对象。

回到SqlSessionFactoryBuilder

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

        // 上面所有说的代码都是这里的
        // 这里的build方法就是传入一个Configuration对象,然后构建一个DefaultSqlSession对象
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            inputStream.close();
        } catch (IOException var13) {
        }

    }

    return var5;
}

再回到程序结构上一层,就是我们的起始点

读取配置文件mybatis-config.xml

@Before
public void init() throws IOException {
    String resource = "/";
    InputStream resourceAsStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
}

这里的代码

SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);

相当于

SqlSessionFactory build = new DefaultSqlSessionFactory();

至此,配置文件解析完毕。

配置文件解析全过程

网图,如有侵权联系删除
在这里插入图片描述

如何构建SqlSession

上述我们已经解析完成配置文件,并拿到了想要的SqlSessionFactory对象,下面我们看看构建SqlSession对象全过程。
上菜!

@Test
public void testFindById() {
    SqlSession sqlSession = factory.openSession();
    sqlSession.selectOne("findById", 1);
}

上面我们知道factory的实现是DefaultSqlSessionFactory

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.ibatis.session.defaults;

import java.sql.Connection;
import java.sql.SQLException;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

    public SqlSession openSession(boolean autoCommit) {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
    }

    public SqlSession openSession(ExecutorType execType) {
        return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, false);
    }

    public SqlSession openSession(TransactionIsolationLevel level) {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), level, false);
    }

    public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
        return this.openSessionFromDataSource(execType, level, false);
    }

    public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
        return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, autoCommit);
    }

    public SqlSession openSession(Connection connection) {
        return this.openSessionFromConnection(this.configuration.getDefaultExecutorType(), connection);
    }

    public SqlSession openSession(ExecutorType execType, Connection connection) {
        return this.openSessionFromConnection(execType, connection);
    }

    public Configuration getConfiguration() {
        return this.configuration;
    }

    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

    private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
        DefaultSqlSession var8;
        try {
            boolean autoCommit;
            try {
                autoCommit = connection.getAutoCommit();
            } catch (SQLException var13) {
                autoCommit = true;
            }

            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            Transaction tx = transactionFactory.newTransaction(connection);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var14, var14);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

    private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
        return (TransactionFactory)(environment != null && environment.getTransactionFactory() != null ? environment.getTransactionFactory() : new ManagedTransactionFactory());
    }

    private void closeTransaction(Transaction tx) {
        if (tx != null) {
            try {
                tx.close();
            } catch (SQLException var3) {
            }
        }

    }
}

重点关注这块代码

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        Environment environment = this.configuration.getEnvironment();
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        Executor executor = this.configuration.newExecutor(tx, execType);
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }

    return var8;
}

创建事务Transaction

TransactionFactory关系图
在这里插入图片描述
事务工厂类型有两种实现:ManagedTransactionFactoryJdbcTransactionFactory
第一种:ManagedTransactionFactory生产ManagedTransaction,会把事务交给容器来管理,比如JBoss、Weblogic。

第二种:JdbcTransactionFactory生产JdbcTransaction,会交由Connection对象的commit、rollback、close方法来管理事务。

但是,如果我们是使用Spring+Mybatis,则没有必要配置,因为我们会在application.yml中配置数据源和事务管理器,从而覆盖Mybatis的配置,即mybatis的配置文件配置。

把事务作为参数传递给对象configuration中方法newExecutor创建执行器对象Executor

Executor executor = this.configuration.newExecutor(tx, execType);

创建执行器

// 在Configuration类中
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? this.defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Object executor;
    
    // first
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        executor = new SimpleExecutor(this, transaction);
    }

    // second 如果缓存开启,这里使用装饰者模式对executor进行装饰
    if (this.cacheEnabled) {
        executor = new CachingExecutor((Executor)executor);
    }

    // last 这里会对Executor植入插件逻辑
    Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
    return executor;
}

执行器共有三种类型,SIMPLE是默认类型
在这里插入图片描述
Executor类关系图
在这里插入图片描述
至此,执行器创建完成了。

创建DefaultSqlSession

var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);

直接点进去

public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private final Executor executor;
    private final boolean autoCommit;
    private boolean dirty;
    private List<Cursor<?>> cursorList;

    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
    }

发现这个对象最重要的两个属性configuration配置类、Executor执行器
至此,SqlSession对象创建也搞定了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis使用SqlSession来执行数据库操作。SqlSessionMyBatis中的核心接口,它提供了多种方法来执行SQL语句、提交事务、获取映射器等操作。 首先,你需要通过SqlSessionFactory来创建SqlSession对象。SqlSessionFactory是由MyBatis配置文件和映射文件构建而成的,它负责创建SqlSession对象。 下面是使用SqlSessionFactory创建SqlSession的代码示例: ```java String resource = "mybatis-config.xml"; // MyBatis配置文件的路径 InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); ``` 一旦你获得了SqlSession对象,你就可以使用它来执行各种数据库操作。以下是一些常用的SqlSession方法: - `selectOne(String statement, Object parameter)`:查询单条记录。 - `selectList(String statement, Object parameter)`:查询多条记录。 - `insert(String statement, Object parameter)`:插入数据。 - `update(String statement, Object parameter)`:更新数据。 - `delete(String statement, Object parameter)`:删除数据。 - `commit()`:提交事务。 - `rollback()`:回滚事务。 其中,`statement`参数是映射文件中定义的SQL语句的唯一标识,`parameter`参数是传递给SQL语句的参数。 使用完SqlSession后,记得关闭它以释放资源: ```java sqlSession.close(); ``` 这就是MyBatis中使用SqlSession进行数据库操作的基本流程。希望能对你有所帮助!如果还有其他问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

颓了呀

收到你温暖的礼物,超开心,感激

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值