Mybatis-原生Mybatis原理源码分析篇

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: Mybatis框架概述Mybatis是持久层的框架,它内部封装了jdbc,使开发的时候只需要关注sql语句本身,不需要话费精力去处理加载驱动、创建连接、创建statement等。下面我们也是通过一个实例来对它进行分析。首先编写一个SqlMapConfig.xml

Mybatis框架概述

Mybatis是持久层的框架,它内部封装了jdbc,使开发的时候只需要关注sql语句本身,不需要话费精力去处理加载驱动、创建连接、创建statement等。下面我们也是通过一个实例来对它进行分析。

首先编写一个SqlMapConfig.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>
 
    <!--配置Mybatis的环境-->
        <environments default="mysql">
        <!--配置mysql环境-->
        <environment id="mysql">
            <!--配置事务类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源,也就是连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/ssm? 
                   characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--配置mybatis映射的位置-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

在resources目录下新建一个mappers目录,在然后新建一个UserMapper.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.jd.dao.UserMapper">
 
    <select id="findUser" resultType="com.jd.domain.User">
        select id ,userName as userName,birthday as birthday,sex as sex,
        address as address FROM user where yn=1
    </select>
 
</mapper>

新建一个实体类

@Data
public class User {
    private Integer id;
    private Date birthday;
    private String userName;
    private String sex;
    private String address;
    private Integer yn;
}

创建一个接口类

public interface UserMapper {
    /**
     * 查询所用用户
     */
 
    public List<User> findUser();
 
}

编写测试类

public class MybatisTest {
 
    @Test
    public void test() throws IOException {
        //第一部分初始化工作,解析配置文件
 
        //1、读取配置文件
        InputStream in=Resources.getResourceAsStream("SqlMapConfig.xml");
        //2、创建SqlSessionFactory的构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3、使用构建者创建工厂对象SqlSessionFactory
        SqlSessionFactory factory= builder.build(in);
        
        //第二部分,执行sql
 
        //4、使用SqlSessionFactory创建SqlSession
        SqlSession session=factory.openSession();
        //5、使用SqlSession创建dao接口的代理对象
        UserMapper userDao =session.getMapper(UserMapper.class);
 
 
 
        //6、使用代理对象执行查询方法
        List<User> list=userDao.findUser();
 
        for (User user:list){
            System.out.println(user);
        }
        //7、释放资源
        session.close();
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
      
}

在这个位置,其实我们还有另外一种写法,

我们本次说说使用代理对象调用接口中的方法

接下来我们就要分析原生Mybatis的原理了

主要分为两个部分,

这部分代码主要做的是初始化工作,解析配置文件

这部分代码主要做的是执行sql

首先我们先分析第一部分

 //1、读取配置文件
 InputStream in=Resources.getResourceAsStream("SqlMapConfig.xml");
 //2、创建SqlSessionFactory的构建者对象
 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
 //3、使用构建者创建工厂对象SqlSessionFactory
 SqlSessionFactory factory= builder.build(in);

开始上源码:

// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
//调用了重载方法
        return this.build((InputStream)inputStream, (String)null, properties);
    }
// 2.调用的重载方法
 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
 
            //  XMLConfigBuilder是专门解析mybatis的配置文件的类
            XMLConfigBuilder e = new XMLConfigBuilder(inputStream, environment, properties);
 
           //又调用buid的一个重载方法,e.parse()的返回值是Configuration对象 注
            var5 = this.build(e.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
 
            try {
                inputStream.close();
            } catch (IOException var13) {
                ;
            }
 
        }
 
        return var5;
    }

解:Configuration的对象是什么呢

Configuration的对象跟xml配置文件的对象类似

比如xml配置文件中的标签包括:

properties(属性),settings(设置),typeAliases(类型别名),typeHandlers(类型处理器),objectFactory(对象工厂),mappers(映射器)等

Configuration类的成员变量

public class Configuration {
    protected Environment environment;
    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;
    protected boolean multipleResultSetsEnabled;
    protected boolean useGeneratedKeys;
    protected boolean useColumnLabel;
    protected boolean cacheEnabled;
    protected boolean callSettersOnNulls;
    protected boolean useActualParamName;
    protected boolean returnInstanceForEmptyRow;
    protected String logPrefix;
    protected Class<? extends Log> logImpl;
    protected Class<? extends VFS> vfsImpl;
    protected LocalCacheScope localCacheScope;
    protected JdbcType jdbcTypeForNull;
    protected Set<String> lazyLoadTriggerMethods;
    protected Integer defaultStatementTimeout;
    protected Integer defaultFetchSize;
    protected ExecutorType defaultExecutorType;
    protected AutoMappingBehavior autoMappingBehavior;
    protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
    protected Properties variables;
    protected ReflectorFactory reflectorFactory;
    protected ObjectFactory objectFactory;
    protected ObjectWrapperFactory objectWrapperFactory;
    protected boolean lazyLoadingEnabled;
    protected ProxyFactory proxyFactory;
    protected String databaseId;
    protected Class<?> configurationFactory;
    protected final MapperRegistry mapperRegistry;
    protected final InterceptorChain interceptorChain;
    protected final TypeHandlerRegistry typeHandlerRegistry;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final LanguageDriverRegistry languageRegistry;
    protected final Map<String, MappedStatement> mappedStatements;
    protected final Map<String, Cache> caches;
    protected final Map<String, ResultMap> resultMaps;
    protected final Map<String, ParameterMap> parameterMaps;
    protected final Map<String, KeyGenerator> keyGenerators;
    protected final Set<String> loadedResources;
    protected final Map<String, XNode> sqlFragments;
    protected final Collection<XMLStatementBuilder> incompleteStatements;
    protected final Collection<CacheRefResolver> incompleteCacheRefs;
    protected final Collection<ResultMapResolver> incompleteResultMaps;
    protected final Collection<MethodResolver> incompleteMethods;
    protected final Map<String, String> cacheRefMap;
    // 省略部分源码
}

从而可以看出初始化本质,就是把xml配置文件里的数据封装到Configuration这个类的属性中。

接下来我们继续分析e.prase这个方法

//3、进入XMLConfigBuilder 类中的prase
 public Configuration parse() {
        if(this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            
            this.parsed = true;
            //parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件 
            // 中的顶层标签
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }
//4、进入parseConfiguration方法,这个方法就是是把xml配置文件里的标签封装到Configuration属性中
private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties e = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(e);
            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(e);
            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);
        }
    }

到这里xml的解析就完事了,接着我们由回到build的重载方法

//5、接着回到这个build重载的方法 var5 = this.build(e.parse());
//把Configuration的对象当做入参传入,返回一个SessionFactory
public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

下面是第二部分执行sql了

SqlSession是一个接口,它有一个默认的实现类DefaultSqlSession,一个会话只要一个SqlSession,并且在使用完成后需要close()关闭。

Executor这个也是个接口,他有三个实现类,BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statements),SimpleExecutor(普通的执行器,默认)。

//使用SqlSessionFactory创建SqlSession
SqlSession session=factory.openSession();

获得session

//6、进入DefaultSqlSessionFactory 类中的openSession
  public SqlSession openSession() {
        //getDefaultExecutorType() 返回的是一个SimpleExecutor
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }
 
 
//7、进入openSessionFromDataSource这个方法
//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
//openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
 
        DefaultSqlSession var8;
        try {
            Environment e = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(e);
            tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit);
 
            //根据参数创建指定类型的Executor
            Executor executor = this.configuration.newExecutor(tx, execType);
 
            //返回的是DefaultSqlSession
            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;
    }
 

接下来回到之前的测试代码

 //使用SqlSession创建dao接口的代理对象
  UserMapper userDao =session.getMapper(UserMapper.class);
  //使用代理对象执行查询方法
  List<User> list=userDao.findUser();

我们创建的Mapper接口类,没有实现这个接口类,但是让然可以调用里面的方法,这其实是采用了设计模式中的jdk动态代理技术,如果大家对这个动态代理不是很清楚,可以看我写的这篇博客。https://blog.csdn.net/qq_30353203/article/details/103681191

在这说源码之前,先介绍下Mybatis初始化时对接口的处理,大家可以翻看上面面Configuration的源码属性里 ,就有一个MapperRegistry属性,它的内部维护了一个HashMap用于存储mapper接口的工厂类,每个接口对应一个工厂。mappers可以配置接口的包路径、或者某个具体的接口类

当解析mappers标签时,它会判断解析到的是mapper配置文件时,会再将对应配置文件中的增删改查标签一 一封装成MappedStatement对象,存入mappedStatements中。

当解析到mappers标签的时候,会做个判断,如果是解析到接口,会创建此接口对应的MapperProxyFactory对象,存入HashMap中,key = 接口的字节码对象,value = 此接口对应的MapperProxyFactory对象。

//进入MapperRegistry这个类
public class MapperRegistry {
    private final Configuration config;
    //创建一个HashMap,用于存储key是字节码对象,value是对应接口类的工厂MapperProxyFactory
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
        
        //判断类型,如果是解析到的接口类型,就存入到HashMap中
        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);
                parser.parse();
                loadCompleted = true;
            } finally {
                if(!loadCompleted) {
                    this.knownMappers.remove(type);
                }
 
            }
        }
 
    }
    
    省略部分源码
}

接下来给大家看下源码,Mybatis是如何通过jdk动态代理创建代理对象的。

//使用SqlSession创建dao接口的代理对象
 UserMapper userDao =session.getMapper(UserMapper.class);
//进入DefaultSqlSession 类,是SqlSession 接口的实现类
public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }
 
//进入Configuration类中 的getMapper方法
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }
 
//进入MapperRegistry类中,的getMapper方法
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //从刚才存到HashMap中 根据不同的key,取出value的值,也就是MapperProxyFactory
        MapperProxyFactory mapperProxyFactory = 
                (MapperProxyFactory)this.knownMappers.get(type);
        if(mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the 
             MapperRegistry.");
        } else {
            try {
                //通过动态代理工厂,实例化对象
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }
 
//进入MapperProxyFactory 中的newInstance 方法
 public T newInstance(SqlSession sqlSession) {
        //创建了JDK动态代理的Handler类
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, 
        this.methodCache);
        //调用构造函数 newInstance
        return this.newInstance(mapperProxy);
    }
 
//这个构造函数 ,重载的方法,由动态代理创建新示例返回。newProxyInstance有三个入参,最后一个入参需要实现InvocationHandler接口类。这个返回的对象就是动态代理的对象,会执行invoke方法
protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }
 
//MapperProxy 实现了InvocationHandler接口类
public class MapperProxy<T> implements InvocationHandler, Serializable {
 
 private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;
 
    //构造函数,传入的入参是SqlSession,说明每个session中的代理对象的不同的!
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }
 
    省略部分源码
 
 
}

动态代理后返回的实例,就可以调用mapper接口中的方法,说明在MapperProxy中的invoke方法中已经为我们实现了方法。

//进入MapperProxy 中的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(this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
 
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        // 重点在这:MapperMethod最终调用了执行的方
        return mapperMethod.execute(this.sqlSession, args);
    }
 
//进入excute方法
public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        
//根据命令转顺序执行不同的crud,调用的还是SqlSession中的方法.
switch(null.$SwitchMap$org$apache$ibatis$mapping$SqlCommandType[this.command.getType().ordinal()]) {
        case 1:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case 2:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case 3:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case 4:
            if(this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if(this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if(this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if(this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;
        case 5:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }
 
        if(result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method \'" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

至此,Mybatis的原理,源码分析就结束了。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
3月前
|
SQL XML Java
一文搞懂Mybatis执行原理
一文搞懂Mybatis执行原理
41 1
|
1月前
|
存储 缓存 Java
探秘MyBatis缓存原理:Cache接口与实现类源码分析
探秘MyBatis缓存原理:Cache接口与实现类源码分析
38 2
探秘MyBatis缓存原理:Cache接口与实现类源码分析
|
2月前
|
XML Java 数据库连接
【MyBatis】 框架原理
【MyBatis】 框架原理
20 0
|
2月前
|
缓存 Java 数据库连接
mybatis 数据库缓存的原理
MyBatis 是一个流行的 Java 持久层框架,它封装了 JDBC,使数据库交互变得更简单、直观。MyBatis 支持两级缓存:一级缓存(Local Cache)和二级缓存(Global Cache),通过这两级缓存可以有效地减少数据库的访问次数,提高应用性能。
285 1
|
3月前
|
SQL Java 数据库连接
一篇看懂Mybatis的SqlSession运行原理
SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似 JDBC生成的Connection对象的SqlSession对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐是使用Mapper接口形式),那么它是如何执行实现的,这就是本篇博文所介绍的东西,其中会涉及到简单的源码讲解。
41 1
|
4月前
|
SQL Java 数据库连接
MyBatis源码篇:mybatis拦截器源码分析
MyBatis源码篇:mybatis拦截器源码分析
|
4月前
|
缓存 Java 数据库连接
|
2月前
|
SQL Java 数据库连接
挺详细的spring+springmvc+mybatis配置整合|含源代码
挺详细的spring+springmvc+mybatis配置整合|含源代码
70 1
|
10天前
|
SQL Java 数据库连接
15:MyBatis对象关系与映射结构-Java Spring
15:MyBatis对象关系与映射结构-Java Spring
30 4