MyBatis基本源码流程

发布时间:2022-09-06 13:00

前言

在进行Mybatis的源码流程之前,我们首先应该知道mybatis的使用方法。

MyBatis基本源码流程_第1张图片

<?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=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.stefan.dao.UserDao">
    <!--select查询语句-->
    <select id="getUserList" resultType="com.stefan.pojo.User">
        select * from demo.user
    </select>
</mapper>
<?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.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/demo?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
        
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/demo?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册!-->
    <mappers>
        <mapper resource="com/stefan/dao/UserMapper.xml"/>
    </mappers>
</configuration>

我们最常用的应该是上面这种方法,这样就可以很简单的读到数据库里面的内容并且把他们自动的封装成对象返回。

一、Mybatis核心类

我们首先来认识一下Mybatis里面的几个比较重要的类
1.Configuration类

1.这个类里面有非常多的内容,这里面封装了mybatis-config.xml的所有内容,比如 environment ,再有typeAliases,还有mappers等等内容。他还对mappers标签里面的内容进行了汇总。
在这里插入图片描述
在这里插入图片描述
MyBatis基本源码流程_第2张图片

Configuration还负责创建了mybatis其他的核心对象:newParameterHandler(),newResultSetHandler(),
newStatementHandler(),newExecutor()方法等等

2.MappedStatement类
MappedStatement并不是将每个mapper.xml文件封装为一个个对象,而是将每个文件内的每个CRUD标签单独封装成一个MappedStatement对象。标签里的属性就是MappedStatement类里面的属性。自然,这个对象里面的属性也和crud标签所对应。
MyBatis基本源码流程_第3张图片
值得注意的是,此类里面的id属性,是通过命名空间.id保证其唯一性的。还有 这里的StatementType可以是 STATEMENT, PREPARED,CALLABLE中的一种。默认是PrepareStatement。
MyBatis基本源码流程_第4张图片
自然,MappedStatement还对sql语句进行了封装,封装到了BoundSql这个类里面。
MyBatis基本源码流程_第5张图片
不止如此,MappedStatement中也包含有Configuration属性,也就是说,两者是双向关联的关系,所以能通过MappedStatement也能找到Configuration。

3.操作类对象
Mybatis里面其实封装了很多操作类对象,表面看上去是SqlSession来帮我们完成了一系列的操作,其实是SqlSession调用了其他的一些操作类对象。如下图
MyBatis基本源码流程_第6张图片

  1. Executor
    这个类,通俗一点讲就是执行器,是整个Mybatis处理功能的核心。
    1.增删改update 查 query
    2.事务操作(提交回滚)
    3.缓存相关操作
    这个接口主要有三个实现类:
    BatchExecutor:JDBC中的批处理的操作
    SimpleExecutor:常用Executor,Mybatis推荐默认
    ReuseExecutor:复用Statement

Configuration类里面对他的默认类型进行了定义

this.defaultExecutorType = ExecutorType.SIMPLE;
  1. StatementHandler
    StatementHandler是Mybatis封装了JDBC的Statement,真正的Mybatis进行数据库访问操作的核心
    功能:增删改查
    StatementHandler也是接口,也有一下几个实现类
    PreparedStatementHandler,SimpleStatementHandler(常用),CallableStattementHandler

二、Mybatis流程

1.我们知道,mybatis想要读取到xml里面的内容,就需要去解析。我们所知的解析xml的方式,主要是DOM,SAX和XPath三种。而mybatis中使用的就是第三种XPath。我们先用一个简单的示例来了解一下。

(1) 首先定义一个xml文件。
MyBatis基本源码流程_第7张图片
(2) 利用XPath解析MyBatis基本源码流程_第8张图片
利用XPathParser的evalNodes方法,我们就可以讲xml解析,封装在一个XNode的集合里面进行使用。而里面所传的参数叫做XPath语法,大家可以自行百度,这里不再赘述。
2.下面我们一行一行来分析。
在这里插入图片描述
在这句上面,我们已经拿到了mybatis-config.xml的输入流,利用它来得到一个SqlSessionFactory对象,我们进入到这个方法。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            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;
    }

我们很清楚的可以看到,它创建出一个XMLConfigBuilder 对象去进行解析xml,那么这个对象里就一定封装了XPathParser对象。
MyBatis基本源码流程_第9张图片
进入到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;
        }
    }

this.parser.evalNode("/configuration")这句代码呢就已经把我们的XML文件封装成一个XNode对象了,我们进入到parseConfiguration方法。
MyBatis基本源码流程_第10张图片
这里面就就很明显了,利用传过来的XNode对象,一个个的去解析各个标签,然后用各个方法将他封装给Configuration对象。值得注意的是,他还会在最后一句代码中去解析mappers标签,this.mapperElement(root.evalNode(“mappers”));,所以这就是为什么我们只需要读取mybatis-config.xml文件而不用去读取xxxDaoMapper.xml文件的原因。接下来我们进入这个方法。

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");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } 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;
            }
        }
    }

这个方法,就是用来解析xxxMapper.xml的。
MyBatis基本源码流程_第11张图片
MyBatis基本源码流程_第12张图片
我们可以看到,mappers这个标签下面有两个标签,package和mapper,而mapper下面又有三个属性,所以package和吗,mapper肯定是要分开解析的。源码中也有体现。
MyBatis基本源码流程_第13张图片
再下面,就是对mapper的三个属性的解析,去拿到xxxDaoMapper.xml的资源了。但是,值得注意的是,他的判断语句。在这里插入图片描述
在这里插入图片描述
MyBatis基本源码流程_第14张图片
也就是说,每一个mapper标签,都只能在这三个属性中任意存在一个,否则就会抛出异常。
那么,对于package标签来说,拿到内容之后,会把它封装给我们上面所提到的Configuration类。对于mapper标签来说,它会进一步的解析,通过mapperParser.parse();方法。我们进入。
MyBatis基本源码流程_第15张图片
熟悉的代码再一次出现,this.parser.evalNode("/mapper"),我们知道,这句代码就是把一个标签封装成一个XNode对象。传入到configurationElement方法中。

private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if (namespace != null && !namespace.equals("")) {
                this.builderAssistant.setCurrentNamespace(namespace);
                this.cacheRefElement(context.evalNode("cache-ref"));
                this.cacheElement(context.evalNode("cache"));
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                this.sqlElement(context.evalNodes("/mapper/sql"));
                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);
        }
    }

这下就比较明了了,一步步的解析mapper标签里的属性,最后会把他们封装到Configuration中,最后返回到最初build方法的位置,接下来,Configuration已经封装了一切想到的东西,xml文件解析完成。
解析完成之后,会调用this.build(parser.parse());方法,去创建SqlSessionFactory。
MyBatis基本源码流程_第16张图片
很明显,这个SqlSessionFactory是一个DefaultSqlSessionFactory。创建完成之后,返回到我们最开始的位置。执行下一句代码,去创建SqlSession,我们进入到openSession方法。

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

openSession方法中又会调用这个方法,他会先处理一些关于事务的操作,最后创建一个Executor,也就是执行器,执行我们的crud操作。最后再创建一个DefaultSqlSession类型的SqlSession。然后返回。
在这里插入图片描述
前面的准备工作已经完成,下面就要开始干活了。我们知道,我们的UserDao是一个接口,那么他就一定要去创建代理对象。我们跟着源码去验证。DeBug进入。
MyBatis基本源码流程_第17张图片
一路进去之后,就到了这个方法。我们看到了一个熟悉的newInstance方法,我们进去之后。
在这里插入图片描述
首先创建一个MapperProxy对象,这个对象,它实现了InvocationHandler接口,我们知道,在newInstance创建代理的时候,代理对象调用处理程序的时候,就会去调用这个接口里的invoke方法。
然后下一步newInstance。

protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

熟悉的创建代理对象。返回代理对象。一路返回到getMapper方法。此时的UserDao就是一个代理对象了。
创建好代理对象之后,我们就可以去执行具体方法了。这里以查询所有用户的方法getUserList为例。
上面提到,调用方法的时候会直接进入invoke方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (method.isDefault()) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

try里面的语句只是进行一些健壮性的操作,比如被调用的方法是否是Object里面的方法,如果是就直接执行,没必要继续走下面的代码。如果不是就继续。首先创建一个MapperMethod的对象。我们有必要说明一下这个对象。

  • MapperMethod对象
    MyBatis基本源码流程_第18张图片
    里面有这样两个属性。SqlCommand和MethodSignature,这两个对象都是静态内部类
    (1)SqlCommand类
    MyBatis基本源码流程_第19张图片
    这里面有两个属性,name属性其实就是namespace.id,用来唯一标识一个mapper或者说是sql语句。而type属性,就是表示这个sql是insert ,update,delete和select中的哪一个。

    (2)MethodSignature类
    MyBatis基本源码流程_第20张图片
    这里面的属性其实就是记录一些方法返回值和参数,比如是否是多值返回,是否返回空,是否返回一个map等等。

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号