IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> mybatis源码解析之调用流程(初探SqlSession创建) -> 正文阅读

[Java知识库]mybatis源码解析之调用流程(初探SqlSession创建)

原创不易,转载请注明出处


前言

我们都知道,在mybatis中,我们写的mapper接口,mybatis在初始化的时候会为其创建动态代理实现类,然后我们在service中调用dao操作mysql,其实都是调用的mybatis为我们创建的动态代理实现类,同时,我们在《mybatis执行原理精讲》一文介绍执行原理的时候,曾说到这个动态代理实现类会调用SqlSession执行我们的sql,接下来我们要探索下dao代理类是怎样找到sqlSession并调用的。

1. 代理类调用入口

比如说我们现在有个注册用户的需求,需要用户信息保存在mysql中,在项目中我们一般会这么写(这里都是demo级别)

@Mapper
public interface UserDao {
    boolean addUser(UserEntity userEntity);
}

写一个用户相关的mapper接口,里面有一个addUser的方法。

<?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.example.testcanal.dao.UserDao" >
    <insert id="addUser" useGeneratedKeys="true" keyProperty="id" parameterType="com.example.testcanal.model.UserEntity">
        insert into user  (username,password)  values (#{userName},#{password})
    </insert>
</mapper>

写一个UserMapper.xml ,里面就是insert语句。
这里service层的代码我们就不写了,在项目启动的时候,mybatis会进行初始化,扫描配置类,mapper类等等创建SqlSessionFactory,并且是为mapper 接口创建动态代理实现类,这些东西我们在《springboot整合mybatis原理分析》都有介绍到,基于这篇文章,我们看看执行一个addUser调用是怎样执行的。

//使用jdk 动态代理创建对应的mapper 动态代理类
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

这里我们把创建dao接口动态代理实现类的代码贴出来,使用的是jdk的动态代理api,所以说只需要关心那个invocationHandler的实现可以了,因为关于这个dao的所有方法调用会走到这里面的invoke方法。这里使用的invocationHandler 实现类是MapperProxy ,我们直接看下invoke方法

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 如果是Object类的方法,直接反射执行
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        //先获取method对应的Invoker
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

判断调用的这个方法是不是Object类的方法,如果是的话,直接执行就ok了,我们这里肯定不是,获取一个invoker,执行invoke方法,看看cachedInvoker方法是怎样获取一个invoker的。

MapperMethodInvoker invoker = methodCache.get(method);
    if (invoker != null) {
      return invoker;
    }
    return methodCache.computeIfAbsent(method, m -> {
      // 如果是接口的default方法的话
      if (m.isDefault()) {
        try {
          if (privateLookupInMethod == null) {
            return new DefaultMethodInvoker(getMethodHandleJava8(method));
          } else {
            return new DefaultMethodInvoker(getMethodHandleJava9(method));
          }
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException
            | NoSuchMethodException e) {
          throw new RuntimeException(e);
        }
        //普通方法
      } else {
        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
    });

先从map缓存中获取这个方法对应的invoker,methodCache 是个map,缓存方法与invoker对应关系的,很显然,开始的时候我们并没有创建这个invoker,这个时候就会创建一个invoker 并放入缓存中。
我们普通方法是创建的PlainMethodInvoker ,构造方法中传入了一个MapperMethod 对象。
关于这个PlainMethodInvoker 其实没什么东西,就是个调用转发的作用,我们看看它代码

private static class PlainMethodInvoker implements MapperMethodInvoker {
  private final MapperMethod mapperMethod;
  public PlainMethodInvoker(MapperMethod mapperMethod) {
    super();
    this.mapperMethod = mapperMethod;
  }
  // 直接调用的MapperMethod 的execute方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
  }
}

invoke方法中,就是直接调用的MapperMethod的execute方法,需要注意的是这里这个sqlSession 是SqlSessionTemplate,并不是真正执行意义上的SqlSession,这个是在springboot整合mybatis项目初始化的时候自动装配类创建的一个SqlSession模板类。
我们这里要看下MapperMethod的execute 方法了

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      // 插入操作
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          // 将参数转换成param  其实就是封装成map
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

代码很长,逻辑很简单,其实就是将我们调用的方法转换成SqlSession的调用。

这里有一个问题,它是怎么知道执行sql的类型的?
其实创建MapperMethod对象的构造方法中,创建了一个SqlCommand对象,这个对象会根据你接口名+方法名去
configuration中获取项目初始化解析mapper xml 创建的MappedStatement。
关于MappedStatement是怎么被创建出来的可以阅读《mybatis源码解析之初始化(解析mapper文件)》这篇文章,在MappedStatement
有你这个sql类型,毕竟在创建MappedStatement
的时候是要解析你sql信息的,但是它是通过你标签判断出来的,就像<insert> <update> <delete> <select>
标签,拿到你标签就知道你这个方法是什么类型了。

我们这里是insert语句,所以执行的就是sqlSession的insert(command.getName(), param) 方法。其他的语句也同理。

command.getName() 是MappedStatement id ,也就是接口全类名.方法名

在进入sqlSession 的insert方法之前,我们要看下上面执行的convertArgsToSqlCommandParam 方法,这个方法很重要,处理你参数的。

 public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    // 没有参数
    if (args == null || paramCount == 0) {
      return null;
     // 没有@Param 注解 并且 参数数量就一个的时候
    } else if (!hasParamAnnotation && paramCount == 1) {
      Object value = args[names.firstKey()];
      //这里就是将是 集合/数组/list的参数 封装到map中
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      //  0->参数值    +    param1->参数值
      return param;
    }
  }

判断你参数个数,如果是0的话,就返回null,
如果就一个参数 并且没有@Param注解的话,如果是集合/list/数组的话,封装到一个map中,如果是普通的bean,直接返回了
如果存在@Param注解或者是多个参数的话,就将参数名-》对应的值 塞到map中
比如说我有下面这么一个根据条件查询用户信息的方法

 UserEntity selectByCondition(@Param("userName") String username, String password);

这个时候它解析到的参数map中就有这么几个kv:
userName -> username值 (有@Param注解优先使用直接中的值,没有的话采用参数名)
password -> password值
param1 -> username值
param2 -> password值
封装到map中并返回
好了,接下来就看下sqlSession的insert方法了。

2. SqlSession创建

上面我们有说到这个sqlSession其实SqlSessionTemplate
我们直接找到

  @Override
  public int insert(String statement, Object parameter) {
    return this.sqlSessionProxy.insert(statement, parameter);
  }

调用的是sqlSessionProxy 的insert方法,其实这个SqlSessionTemplate 什么活也不干的,都交给了sqlSessionProxy 这个代理类
创建SqlSessionTemplate对象的时候,会创建一个sqlSessionProxy 对象。

 //生成一个sqlSession代理类
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());

对应的invocationHandler的实现类是SqlSessionInterceptor(是SqlSessionTemplate 里面的一个内部类)。我们看下它的invoke方法
由于我们这篇文章是分析SqlSession的创建 ,这里它的invoke方法我们就不全部贴出来了

 // 获取sqlSession
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

调用getSqlSession 方法获取sqlSession,将sqlSessionFactory 传入了进去

 public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }
    // 如果没有的话,找 sessionFactory 创建一个新的
    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);
    // 将创建出来的session 注册到当前的事务管理器中
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

这里就是从spring的事务管理器中获取一个sqlSession,如果没有的话就创建一个,这个getResource 是从ThreadLocal中获取。
我们一开始肯定没有的,这个时候就会找sessionFactory 创建一个SqlSession,然后塞到这个spring事务管理器中。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      
      //创建executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建DefaultSqlSession对象
      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();
    }
  }

可以看到创建了一个Executor ,然后创建一个DefaultSqlSession ,将Executor 组件传了进去。
看看Executor构造方法

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    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);
    }// 如果开启缓存的话
    if (cacheEnabled) {// 使用CachingExecutor包装
      executor = new CachingExecutor(executor);
    }// 使用 插件包装
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

上面一顿判断执行类型,我们这里是Simple。所以就创建一个SimpleExecutor
如果开启缓存的话(这里默认是开启的),就是使用CachingExecutor 装饰一下,包装一下,装饰者设计模式
最后,如果你有插件的话,在CachingExecutor 基础上进行装饰,然后返回。
SimpleExecutor与CachingExecutor 的创建都是很简单的,没有逻辑,我们这里就不看了

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

DefaultSqlSession 也有什么逻辑,就是赋值逻辑。
好了,到这里我们这个能干活的SqlSession就创建出来了。

总结

本文主要是找了找mapper 接口动态代理实现类执行入口,一步步的找到获取sqlSession逻辑与创建SqlSession的逻辑。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-26 11:56:40  更:2021-07-26 11:58:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/25 18:08:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码