Mybatis源码阅读系列(六)

12 篇文章 0 订阅
9 篇文章 0 订阅

前面已经看完了整个MapperProxy生成的过程,我们知道了当实际调用XXXMapper方法的时候会调用MapperMethod.execute方法进行数据库操作,那么这些具体是怎么实现的呢?还有Mybatis里面的@Param注解是如何实现的?如果没有写注解那么默认值是啥呢?带着这些问题我们继续往下看。

Executor

Mybatis里面所有和数据库交互都是交给Executor去执行的,它是一个接口,里面有各种和数据库相关的操作定义。

它的实现对应了你在配置mybatis时候的executeType,这个是一个枚举,主要有三个值,默认是simple,batch是批处理的方式,这个在大数据量处理的时候是非常常用的方式,比如上千W的数据插入和更新等。

注意批处理和mybatis里面的<foreach>标签有本质差异,foreach是将多个参数放到一起传输,但是还是一条一条sql执行,当数据量很大的时候,执行效率会变低,因为网络连接建立断开的代价太大,但是批处理是将一批量数据打包一起发送,减少了网络消耗,所以效率得到了很大提升(个人观点,欢迎指正)。

public enum ExecutorType {

  SIMPLE,

  REUSE,

  BATCH

}

在这里插入图片描述

BaseExecutor是一个基础实现,这三个都是基于它实现的。cachingExecutor是缓存用的。是否开启你可以进行配置。

这个Executor是在获取sqlsession的时候创建的,你也可以指定对应的类型获取sqlsession通过使用SqlSessionFactoryopenSession(ExecutorType execType)方法。类似于下面这种写法:

    、、、、、、省略
    SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    Mapper mapper = sqlSession.getMapper(Mapper.class);
    、、、、、、省略

这种写法实际调用的是下面这个方法:

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);
    final Executor executor = configuration.newExecutor(tx, execType);
    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();
  }
}

在这里会根据传入的execType和事务信息,是否自动提交等配置去生成对应的Executor,最后创建出DefaultSqlSession供我们使用。

ParamNameResolver

ParamNameResolver这个类是解析所有关于param的相关操作的。比如@Param参数的读取。

它里面有一个名为namesSortedMap,它会按照顺序存储mapper接口的参数,如果是你使用了@Param参数,那么会使用设置的名称作为value,否则使用序号作为value,这个值和参数的需要不一定完全对的上,如果你使用了RowBoundsResultHandler作为参数将会跳过。参考下面的官方示例:

aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}
aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}
aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}

它解析的时候会使用getNamedParams方法,除了使用你设置的@Param作为key之外,还会使用param+i的方式作为参数,比如param1,param2等,所以你在xml的sql语句里面直接用param+i也可以找到对应参数。

mybatis里面的所有参数映射会进行两步操作,你可以在MapperMethod.execute方法的param变量查看第一步的映射关系,第二步则是在executor.update(ms, wrapCollection(parameter));具体执行的时候会将集合类的参数再去封装一次,利用特殊的key去区分,比如collection,list,array等,具体看你的类型。产生最后的param。下面是源码:

public static Object wrapToMapIfCollection(Object object, String actualParamName) {
  if (object instanceof Collection) {
    ParamMap<Object> map = new ParamMap<>();
    map.put("collection", object);
    if (object instanceof List) {
      map.put("list", object);
    }
    Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
    return map;
  }
  if (object != null && object.getClass().isArray()) {
    ParamMap<Object> map = new ParamMap<>();
    map.put("array", object);
    Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
    return map;
  }
  return object;
}

这里如果是结合会用collection标识,如果是List会用list标识,如果是数组会用array标识,当然,你自己配置了@Param注解之后会具体的注解作为key也会放到map里面。比如你传的一个list,但是没有自己配置@Param,你可以在xml文件里面直接使用{list[0]}这种使用里面对象。

最终数据库操作都是用的这个返回的param值。

在解析接口参数的时候,如果没有用注解且只有一个参数,是直接返回的整个类,没有用map,所以直接可以用类里面的属性,而不需要再指定传过来的参数名。比如我参数是一个Person,里面有name属性,我接口是addPerson(Person person);这时候再xml里面直接可以用#{name}而不需要用#{person.name}。如果是多个参数或者集合的话还是返回的map。

public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  if (args == null || paramCount == 0) {
    return null;
  }
  if (!hasParamAnnotation && paramCount == 1) {
    Object value = args[names.firstKey()];
    return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : 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++;
    }
    return param;
  }
}

总结一下,如果是一个类型参数,可以直接用属性拿到值,如果是多个的话,需要用类型参数名.属性拿到值,Mybatis里面默认开启了useActualParamName配置的,它会直接用你传的参数作为key。基本属性直接拿来用就行。

比如对于

addPerson(Person person,String aa);

你在xml里面使用就不能直接用#{name}了,而是必须使用#{person.name}#{aa}才能拿到值。

总结

今天介绍了ExecutorParamNameResolver,他们在Mybatis里面使用的地方很多,也是很重要的东西,后续还有一个MetaObject类,这个是Mybatis里面反射的主要实现类,它可以十分方便快捷的对数据进行赋值和取值,而且不限定类型,我这两天也在看这个类,看着有点复杂,等我研究差不多在来写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值