mybatis执行过程,源码分析

Mybatis认识

我们知道,JDBC有四个核心对象:

(1)DriverManager,用于注册数据库连接
(2)Connection,与数据库连接对象
(3)Statement/PrepareStatement,操作数据库SQL语句的对象
(4)ResultSet,结果集或一张虚拟表

而MyBatis也有四大核心对象:

(1)SqlSession对象,该对象中包含了执行SQL语句的所有方法。类似于JDBC里面的Connection。
(2)Executor接口,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement。
(3)MappedStatement对象,该对象是对映射SQL的封装,用于存储要映射的SQL语句的id、参数等信息。
(4)ResultHandler对象,用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自定义返回类型。

Mybatis执行原理跟踪

在这里插入图片描述

1.项目启动时,读取配置

  1. 通过jdbc连接信息注入bean:SqlSessionFactory
  2. 将扫描到的mapper.java接口信息,将接口中的方法都转换成MapperStatement对象,用于记录这个Mapper.java接口方法的方法参数、返回值和sql语句等等信息

2.获取SqlSession

SqlSession定义了事务提交回滚sql执行等能力,并且有一个ExecutorType的参数,用于控制Executor的类型

 // 批处理,并且不自动提交事务
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
M mapper = sqlSession.getMapper(mapperClass);

Executor:

  • SimpleExecutor:默认类型,对于每一条执行的sql每执行一次都会重新预编译加载一次
  • ReuseExecutor:执行相同的sql语句,不会重复编译加载
  • BatchExecutor:批量执行策略,将批量的多条sql语句构成的Statement暂存到list中,flush的时候一次性提交到mysql服务器,避免重复的和mysql服务器建立连接交互
  • CachingExecutor:二级缓存策略,其中维护了一个map来保存查询结果,二级缓存在mapper文件层生效

3.反向代理方法执行

getMapper方法跟进去,最终可以看到实际上返回的Mapper实例其实是一个MapperProxy的反向代理出来的实例对象,每当执行任何方法,都会从MapperProxy的invoke中过,判断该方法是否是Object的方法,是否是接口default方法,如果都不是的话则直接进入mybatis的执行流程,通过invoke的反射信息拿到Method对象,通过Method对象拿到MapperStatement的id,即类名全限定名+方法名(这里不区分方法签名,所以在mybatis中不支持方法重载)

MapperStatement中包含了我们要执行的Mapper.java接口的方法名,对应的sql,参数(参数最终都会注入到Map中,所以方法重载没有用),返回值,通过这些信息,生成StatementHandler对象用于构建sql,生成ParameterHandler用于处理参数,最终将参数和预编译的sql交由原生jdbc执行,最终拿到返回结果

4.ResultHandler结果集处理

在拿到返回数据后,通过ResultHandler对结果集进行封装,使用TypeHandler,使用反射中的set方法将属性注入POJO

实际上查询的结果封装由ResultSetHandler来处理,结合mapper.xml中的resultMap标签对应的对象ResultMap,将字段名称做映射,配合TypeHandlerRegister中注册的TypeHandler,按照数据库字段与TypeHandler映射的字段,将ResultSet中的结果先交给TypeHandler的getNullableResult方法处理成我们的理想java类型字段,再通过反射,使用标准java bean的set方法将字段设置到POJO中

MyBatis源码分析

示例代码

public void test () {
  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  User user = userMapper.getUserById(1L);
}

第一阶段

通过SqlSession获取Mapper代理实例

在上述的代码中,第一步,通过sqlSession获取UserMapper实例,因为UserMapper是我们定义的一个接口,所以真正使用时,使用的是mybatis为我们生成的UserMapper的代理实例,从这里入手看源码。

通过断点发现SqlSession的实例是DefaultSqlSession

通过如下调用栈跟踪源码

DefaultSqlSession.getMapper(Class)

Configuration.getMapper(Class, SqlSession)

MapperRegistry.getMapper(Class, SqlSession)

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   // knownMappers实际上是项目启动时,mybatis能扫描到的需要被代理的mapper.java接口
   // 这里拿到当前需要代理的接口的【代理mapper工厂类】
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // 从这里跟进去
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

从mapperProxyFactory.newInstance(SqlSession)跟进去

mapperProxyFactory.newInstance(SqlSession)

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

通过这两个newInstance可以看出,最终生成的代理类是【MapperProxy】对象,

综上所述,最终通过getMapper(Class type),获取到的结果就是MapperProxy的代理对象,那么我们通过该对象执行的所有方法其实最终都是通过MapperProxy的invoke方法,下面我们对invoke方法进行解析

第二阶段

MapperProxy对象的invoke方法

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 假如MapperProxy对象是UserMapper的代理类,则此处判断我们调用的该方法属于哪个类,比如在我们没有重写toString方法时,调用toString方法,则toString方法属于Object.class,此处就是在判断我们调用的方法是否属于Object提供的方法
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (method.isDefault()) {
      // isDefault判断调用的方法是不是interface中的default默认实现的方法,此处不重要
      if (privateLookupInMethod == null) {
        return invokeDefaultMethodJava8(proxy, method, args);
      } else {
        return invokeDefaultMethodJava9(proxy, method, args);
      }
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 从缓存map中获取到MapperMethod对象
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  // 执行MapperMethod对象的execute
  return mapperMethod.execute(sqlSession, args);
}

MapperMethodexecute

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

第三阶段

有空再补充

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值