分析mybatis运行原理

目录
  • 一、Mybatis基本认识
    • 1.1、动态代理
    • 1.2、反射
  • 二、Configuration对象作用
  • 三、映射器结构
  • 四、sqlsession执行流程(源码跟踪)
    • 4.1、Executor
    • 4.2、StatementHandler
    • 4.3、结果处理器(ResultSetHandler)
    • 4.4、总结

一、Mybatis基本认识

1.1、动态代理

  • 之前我们知道Mapper仅仅是一个接口,而不是一个逻辑实现类。但是在Java中接口是无法执行逻辑的。这里Mybatis就是通过动态代理实现的。关于动态代理我们常用的有Jdk动态代理和cglib动态代理。两种却别这里不做赘述。关于CGLIB代理在框架中使用的比较多。
  • 关于动态代理就是所有的请求有一个入口,由这个入口进行分发。在开发领域的一个用途就是【负载均衡】
  • 关于Mybatis的动态代理是使用了两种的结合。
  • 下面看看JDK和cglib两种实现

JDK实现首先我们需要提供一个接口 , 这个接口是对我们程序员的一个抽象。 拥有编码和改BUG的本领

public interface Developer {

    /**
     * 编码
     */
    void code();

    /**
     * 解决问题
     */
    void debug();
}

关于这两种本领每个人处理方式不同。这里我们需要一个具体的实例对象

public class JavaDeveloper implements Developer {
    @Override
    public void code() {
        System.out.println("java code");
    }

    @Override
    public void debug() {
        System.out.println("java debug");
    }
}

我们传统的调用方式是通过java提供的new 机制创造一个JavaDeveloper对象出来。而通过动态代理是通过java.lang.reflect.Proxy对象创建对象调用实际方法的。

通过newProxyInstance方法获取接口对象的。而这个方法需要三个参数

  • ClassLoader loader : 通过实际接口实例对象获取ClassLoader
  • Class<?>[] interfaces : 我们抽象的接口
  • InvocationHandler h : 对我们接口对象方法的调用。在调用节点我们可以进行我们的业务拦截
JavaDeveloper jDeveloper = new JavaDeveloper();
Developer developer = (Developer) Proxy.newProxyInstance(jDeveloper.getClass().getClassLoader(), jDeveloper.getClass().getInterfaces(), (proxy, method, params) -> {
    if (method.getName().equals("code")) {
        System.out.println("我是一个特殊的人,code之前先分析问题");
        return method.invoke(jDeveloper, params);
    }
    if (method.getName().equals("debug")) {
        System.out.println("我没有bug");

    }
    return null;
});
developer.code();
developer.debug();

CGLIB动态代理

cglib动态代理优点在于他不需要我们提前准备接口。他代理的实际的对象。这对于我们开发来说就很方便了。

public class HelloService {
    public HelloService() {
        System.out.println("HelloService构造");
    }

    final public String sayHello(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }

    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }
}

下面我们只需要实现cglib提供的MethodInterceptor接口,在初始化设置cglib的时候加载这个实例化对象就可以了

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}

下面我们就来初始化设置cglib

public static void main(String[] args) {
    //代理类class文件存入本地磁盘方便我们反编译查看源代码
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/root/code");
    //通过CGLIB动态代理获取代理对象过程
    Enhancer enhancer = new Enhancer();
    //设置enhancer对象的父类
    enhancer.setSuperclass(HelloService.class);
    // 设置enhancer的回调对象
    enhancer.setCallback(new MyMethodInterceptor());
    //创建代理对象
    HelloService helloService = (HelloService) enhancer.create();
    //通过代理对象调用目标方法
    helloService.sayHello();
}

仔细看看cglib和spring的aop特别像。针对切点进行切面拦截控制。

总结:

通过对比两种动态代理我们很容易发现,mybatis就是通过JDK代理实现Mapper调用的。我们Mapper接口实现通过代理到xml中对应的sql执行逻辑

1.2、反射

  • 相信有一定经验的Java工程师都对反射或多或少有一定了解。其实从思想上看不惯哪种语言都是有反射的机制的。
  • 通过反射我们就摆脱了对象的限制我们调用方法不再需要通过对象调用了。可以通过Class对象获取方法对象。从而通过invoke方法进行方法的调用了。

二、Configuration对象作用

Configuration对象存储了所有Mybatis的配置。主要初始化一下参数

  • properties
  • settings
  • typeAliases
  • typeHandler
  • ObjectFactory
  • plugins
  • environment
  • DatabaseIdProvider
  • Mapper映射器

三、映射器结构

  • BoundSql提供三个主要的属性 parameterMappings 、parameterObject、sql
  • parameterObject参数本身。我们可以传递java基本类型、POJO、Map或者@Param标注的参数。
  • 当我们传递的是java基本类型mybatis会转换成对应的包装对象 int -> Integer
  • 如果我们传递POJO、Map。就是对象本身
  • 我们传递多个参数且没有@Param指定变量名则parameterObject 类似
  • {"1":p1,"2":p2,"param1":p1,"param2":p2}
  • 我们传递多个参数且@Param指定变量名 则parameterObject类似
  • {"key1":p1,"key2":p2,"param1":p1,"param2":p2}
  • parameterMapping 是记录属性、名称、表达式、javaType,jdbcType、typeHandler这些信息
  • sql 属性就是我们映射器中的一条sql. 正常我们在常见中对sql进行校验。正常不需要修改sql。

四、sqlsession执行流程(源码跟踪)

首先我们看看我们平时开发的Mapper接口是如何动态代理的。这就需要提到MapperProxyFactory这个类了。该类中的newInstance方法

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

通过上满代码及上述对jdk动态代理的表述。我们可以知道mapperProxy是我们代理的重点。MapperProxy是InvocationHandler的实现类。他重写的invoke方法就是代理对象执行的方法入口。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
    if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
    } else if (isDefaultMethod(method)) {
    return invokeDefaultMethod(proxy, method, args);
    }
} catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
    & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
    && method.getDeclaringClass().isInterface();
}

通过源码发现。invoke内部首先判断对象是否是类 。 通过打断点发现最终会走到cacheMapperMethod这个方法去创建MapperMethod对象。继续查看MapperMethod中execute方法我们可以了解到内部实现其实是一个命令行模式开发。通过判断命令从而执行不同的语句。判断到具体执行语句然后将参数传递给sqlsession进行sql调用并获取结果。到了sqlsession就和正常jdbc开发sql进行关联了。sqlsession中ExecutorStatementHandlerParameterHandlerResulthandler四大天王

4.1、Executor

顾名思义他就是一个执行器。将java提供的sql提交到数据库。Mybatis提供了三种执行器。

Configuration.classnewExecutor源码

根据uml我们不难看出mybatis中提供了三类执行器分别SimpleExecutor、ReuseExecutor、BatchExecutor

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

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 得到configuration 中的environment
      final Environment environment = configuration.getEnvironment();
      // 得到configuration 中的事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 获取执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 返回默认的SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

通过上述源码我们知道在sqlsession获取一个数据库session对象时我们或根据我们的settings配置加载一个Executor对象。在settings中配置也很简单

<settings>
<!--取值范围 SIMPLE, REUSE, BATCH -->
	<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

我们也可以通过java代码设置

factory.openSession(ExecutorType.BATCH);

4.2、StatementHandler

顾名思义,StatementHandler就是专门处理数据库回话的。这个对象的创建还是在Configuration中管理的。

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

很明显Mybatis中StatementHandler使用的是RoutingStatementHandler这个class

关于StatementHandler和RoutingStatementHandler之间的关系我们通过源码可以看出这里和Executor一样都是适配器模式。采用这种模式的好处是方便我们对这些对象进行代理。这里读者可以猜测一下是使用了哪种动态代理。给点提示 这里使用了接口哦


在查看BaseStatementHandler结构我们会发现和Executor一模一样。同样的Mybatis在构造RoutingStatementHandler的时候会根据setting中配置来加载不同的具体子类。这些子类都是继承了BaseStatementHandler.

前一节我们跟踪了Executor。 我们知道Mybatis默认的是SimpleExecutor。 StatementHandler我们跟踪了Mybaits默认的是PrePareStatementHandler。在SimpleExecutor执行查询的源码如下


我们发现在executor查询钱会先让statementHandler构建一个Statement对象。最终就是StatementHandler中prepare方法。这个方法在抽象类BaseStatmentHandler中已经封装好了。

这个方法的逻辑是初始化statement和设置连接超时等一些辅助作用

然后就是设置一些参数等设置。最后就走到了执行器executor的doquery

PrepareStatement在我们jdbc开发时是常见的一个类 。 这个方法执行execute前我们需要设置sql语句,设置参数进行编译。这一系列步骤就是刚才我们说的流程也是PrepareStatementHandler.prepareStatement帮我们做的事情。那么剩下的我们也很容易想到就是我们对数据结果的封装。正如代码所示下马就是resultSetHandler帮我们做事情了。

4.3、结果处理器(ResultSetHandler)

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

这个方法我们可以导出来是结果xml中标签配置对结果的一个封装。

4.4、总结

SqlSession在一个查询开启的时候会先通过CacheExecutor查询缓存。击穿缓存后会通过BaseExector子类的SimpleExecutor创建StatementHandler。PrepareStatementHandler会基于PrepareStament执行数据库操作。并针对返回结果通过ResultSetHandler返回结果数据

以上就是分析mybatis运行原理的详细内容,更多关于mybatis运行原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • MyBatis缓存实现原理及代码实例解析

    一.一级缓存(本地缓存) sqlSession级别的缓存.一级缓存是一直开启的:SqlSession级别的一个Map与数据库同一次会话期间查询到的数据会放在本地缓存中.以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库: 一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询): 1.sqlSession不同 2.sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据) 3.sqlSession相同,两次查询之间执行了增删改操作(这次增删

  • 详解MyBatis工作原理

    一.Mybatis工作原理 Mybatis分层框架图 Mybatis工作原理图 源码分析:一般都是从helloworld入手 1.根据xml配置文件(全局配置文件mybatis-config.xml)创建一个SqlsessionFactory对象,mybatis-config.xml有数据源一些环境信息 2.sql映射文件EmployeeMapper.xml配置了每一个sql,以及sql的封装规则等. 3.将sql映射文件注册在全局配置文件中 4.写代码: 根据全局配置文件得到sqlsessio

  • 使用MyBatisPlus自动生成代码后tomcat运行报错的问题及解决方法

    自动生成的代码 报错 解决办法:把自动xml文件中自动生成的二级缓存注释掉 总结 到此这篇关于使用MyBatisPlus自动生成代码后tomcat运行报错的问题及解决方法的文章就介绍到这了,更多相关MyBatisPlus自动生成代码tomcat运行报错内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

  • Mybatis工具类JdbcTypeInterceptor运行时自动添加jdbcType属性

    JdbcTypeInterceptor 运行时自动添加 jdbcType 属性 拦截器签名 @Intercepts({ @Signature( type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}) }) 这类拦截器很少见,所以和其他拦截器(如分页插件)等搭配使用时不需要考虑顺序. 这个插件最适合的场景可能就是 Oracle 数据库,可以自动给所有方法添加 jd

  • Mybatis逆向工程运行代码实例

    简单的理解,MyBatis逆向工程,就是通过相应插件,自动生成MyBatis数据库连接的一些文件. mybatis需要编写sql语句,mybatis官方提供逆向工程,可以针对单表自动生成mybatis执行所需要的代码(mapper.java.mapper.xml.pojo-),提高工作效率. 命令: mvn mybatis-generator:generate 项目结构: generatorConfig.xml内容示例 <?xml version="1.0" encoding=&

  • Mybatis源码分析之存储过程调用和运行流程

    这一篇我们学习一下Mybatis调用存储过程的使用和运行流程.首先我们先创建一个简单的存储过程 DELIMITER $ CREATE PROCEDURE mybatis.ges_user_count(IN age INT, OUT user_count INT) BEGIN SELECT COUNT(*) FROM users WHERE users.age=age INTO user_count; END $ 这个存储过程的含义其实比较简单的,就是输入age,然后执行select count(

  • Mybatis SQL运行流程源码详解

    概述 源码就是能够被用来执行,生成机器能够识别的代码,通过开源源码,可以引用其功能. 重要性 1.mybatis中的sql执行,不仅要知道返回的结果是什么,还需要知道这结果是怎么来的,经过了怎样的处理,只有知道了这样的原理,碰到问题才能更好的知道问题出在那个环节. 2.能更好的扩展应用程序,可以做到代码的复用,减少开发成本和时间. 3.学习其中的设计思想能够在其他应用得已应用. 步骤 源码阅读,可以从测试用例着手,每个源码,多有大师们写的测试用例,咱们可以用大师们写的测试用例进行相关功能的测试,

  • Mybatis Mapper接口工作原理实例解析

    KeyWords: Mybatis 原理,源码,Mybatis Mapper 接口实现类,代理模式,动态代理,Java动态代理, Proxy.newProxyInstance,Mapper 映射,Mapper 实现 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.我们在使用 Mybaits 进行 ,通常只需要定义几个 Mapper 接口,然后在编写一个 xml 文件,我们在配置文件中

  • 简单了解mybatis拦截器实现原理及实例

    这篇文章主要介绍了简单了解mybatis拦截器实现原理及实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 例行惯例,先看些基本概念: 1 拦截器的作用就是我们可以拦截某些方法的调用,在目标方法前后加上我们自己逻辑 2 Mybatis拦截器设计的一个初衷是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑. 自定义拦截器 /** * mybatis 自定义拦截器 * 三步骤: * 1 实现 {@link Intercept

  • 分析mybatis运行原理

    目录 一.Mybatis基本认识 1.1.动态代理 1.2.反射 二.Configuration对象作用 三.映射器结构 四.sqlsession执行流程(源码跟踪) 4.1.Executor 4.2.StatementHandler 4.3.结果处理器(ResultSetHandler) 4.4.总结 一.Mybatis基本认识 1.1.动态代理 之前我们知道Mapper仅仅是一个接口,而不是一个逻辑实现类.但是在Java中接口是无法执行逻辑的.这里Mybatis就是通过动态代理实现的.关于动

  • Java Servlet 运行原理分析

    1 Servlet基本执行过程 Web容器(如Tomcat)判断当前请求是否第一次请求Servlet程序 . 如果是第一次,则Web容器执行以下任务: 加载Servlet类. 实例化Servlet类. 调用init方法并传入ServletConfig对象 如果不第一次执行,则: 调用service方法,并传入request和response对象 Web容器在需要删除Servlet时(例如,在停止服务器或重新部署项目时)将调用destroy方法. 2 Web容器如何处理Servlet请求 Web容

  • JSP程序运行原理、文档结构及简单输入输出实例分析

    本文实例讲述了JSP程序运行原理.文档结构及简单输入输出.分享给大家供大家参考.具体如下: 目标: 掌握Web应用的文档结构: 掌握JSP的运行原理: 掌握JSP的简单输入和输出. 主要内容: 通过一个简单实例介绍Web应用的文档结构和运行原理: 通过一个简单的注册功能介绍基本的输入输出. 实现内容:客户端验证. 1. 文档结构 每个应用都有一个根目录,例如ch2:理论上可以放在任何地方,但是需要配置,简单的做法,直接放在了webapps这个目录下,在这个目录的应用会被自动加载. 在根目录下会有

  • 通过源代码分析Mybatis的功能流程详解

    SQL解析 Mybatis在初始化的时候,会读取xml中的SQL,解析后会生成SqlSource对象,SqlSource对象分为两种. DynamicSqlSource,动态SQL,获取SQL(getBoundSQL方法中)的时候生成参数化SQL. RawSqlSource,原始SQL,创建对象时直接生成参数化SQL. 因为RawSqlSource不会重复去生成参数化SQL,调用的时候直接传入参数并执行,而DynamicSqlSource则是每次执行的时候参数化SQL,所以RawSqlSourc

  • SpringBoot详细分析自动装配原理并实现starter

    目录 约定优于配置 自动装配 手写一个starter组件 约定优于配置 SpringBoot的预定优于配置主要体现在以下几个方面: maven的目录结构: 配置文件默认存放在resources目录下 项目编译后的文件存放在target目录下 项目默认打包成jar格式 配置文件默认为application.yml或application.yaml或application.properties 默认通过 spring.profiles.active 属性来决定运行环境时的配置文件. 自动装配 相对于

  • Python程序运行原理图文解析

    本文研究的主要是Python程序运行原理,具体介绍如下. 编译型语言(C语言为例) 动态型语言 一个程序是如何运行起来的?比如下面的代码 #othermodule.py def add(a, b): return a + b #mainrun.py import othermodule a = ['xiaoke', 1, 'python'] a = 'xiaoke string' def func(): a = -5 b = 257 print(a + b) print(a) if __name

  • 详解Vue-Router源码分析路由实现原理

    深入Vue-Router源码分析路由实现原理 使用Vue开发SPA应用,离不开vue-router,那么vue和vue-router是如何协作运行的呢,下面从使用的角度,大白话帮大家一步步梳理下vue-router的整个实现流程. 到发文时使用的版本是: - vue (v2.5.0) - vue-router (v3.0.1) 一.vue-router 源码结构 github 地址:https://github.com/vuejs/vue-router components下是两个组件<rout

  • SQL查询的底层运行原理深入分析

    前言 SQL 语言无处不在.SQL 已经不仅仅是技术人员的专属技能了,似乎人人都会写SQL,就如同人人都是产品经理一样.如果你是做后台开发的,那么CRUD就是家常便饭.如果你是做数仓开发的,那么写SQL可能占据了你的大部分工作时间.我们在理解 SELECT 语法的时候,还需要了解 SELECT 执行时的底层原理.只有这样,才能让我们对 SQL 有更深刻的认识.本文分享将逐步分解SQL的执行过程,希望对你有所帮助. 数据准备 本文旨在说明SQL查询的执行过程,不会涉及太复杂的SQL操作,主要涉及两

  • Jmeter结构体系及运行原理顺序解析

    一.Jmeter 运行原理: Jmeter 时以线程的方式来运行的(由于Jmeter 是 java 开发的所以是运行在 JVM 虚拟机上的,java 也是支持多线程的) 二.Jmeter 结构体系 1.线程组 性能测试需要模拟大量用户负载的情况,线程组就是用来完成这个任务的,在线程组中我们可以设置运行的线程数(用户数),运行时长,循环次数等 2.逻辑控制器 控制循环次数等 3.配置元件 性能测试过程中为了模拟大量用户操作我们需要做参数化,那么 Jmeter 参数化就可以通过配置元件来完成,另外

随机推荐