Mybatis源码学习-核心流程-代理封装阶段


所用到的设计模块:
策略模式、门面模式
策略模式: 定义了一系列的算法,并将每个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化
使用场景:
1.同一类型问题的多种处理方式,仅仅是具体行为有差别。
2.出现同意抽象类有多个子类,而又需要使用if-else或者switch-case来选择具体子类时
例如: @Resource(name=xxx) 就是一个明显的策略模式

门面模式:指提供一个统一的接口去访问多个子系统的多个不同的接口,它为子系统中的一组接口提供一个统一的高层接口

mybatis的代理封装阶段

我们使用Mybatis时,表面上是调用Mapper接口的方法,但实际上,Mybatis的内部,将Mapper接口调用的请求转发给了SqlSession

那什么是sqlSession呢?

SqlSession是MyBatis对外提供的最关键的核心接口,通过SqlSession可以让客户端与数据库进行连接,并执行数据库读写命令、获取映射器、管理事务等操作。
所有的数据库访问请求的都是由SqlSession来处理的。

SqlSession默认实现类为DefaultSqlSession

在这里插入图片描述 1.SqlSession是MyBatis的门面,是MyBatis对外提供数据访问的主要API
2.实际上,SqlSession的功能都是基于Executor来实现的(单一职责原则)
例如查询请求: 都会转发到Executor.query方法来执行
在这里插入图片描述
SqlSession由SqlSessionFactory创建,SqlSessionFactory全局唯一、单例。

SqlSessionFactory:

SqlSessionFactory使用了工厂模式,来创建SqlSession

默认实现类为DefaultSqlSessionFactory ,
在这里插入图片描述
核心方法为openSessionFromDataSource(ExecutorType, TransactionIsolationLevel, boolean)
这个方法就是从数据源获取数据库连接

//从数据源获取数据库连接
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
    	//获取mybatis配置文件中的environment对象
      final Environment environment = configuration.getEnvironment();
      //从environment获取transactionFactory对象
      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();
    }
  }

调用Mapper接口的方法时,如何将请求转发给Sqlsession?

有三个关键要素
1.SqlSession中对应的方法执行
2.命名空间跟方法名
3.传递参数

由binding模块来实现上述步骤

binding模块核心类

MapperRegistry:Mapper接口和对应的代理对象工厂的注册中心。

主要是通过addMapper方法,将接口的工厂类加入到注册中心中,便于生成代理的实例对象

//记录了mapper接口与对应MapperProxyFactory之间的关系
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

//将mapper接口的工厂类添加到mapper注册中心
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
          throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
      boolean loadCompleted = false;
      try {
    	// -----实例化Mapper接口的代理工程类,并将信息添加至knownMappers------
        knownMappers.put(type, new MapperProxyFactory<T>(type));

        //解析接口上的注解信息,并添加至configuration对象
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

MapperProxyFactory:用于生成mapper接口动态代理的实例对象;保证Mapper实例对象是局部变量

public class MapperProxyFactory<T> {

  //mapper接口的class对象
  private final Class<T> mapperInterface;
  //key是mapper接口中的某个方法的method对象,value是对应的MapperMethod,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

 // 此处省略了......

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
	// ------创建实现了mapper接口的动态代理对象--------
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
	 //每次调用都会创建新的MapperProxy对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

MapperProxy:实现了InvocationHandler接口,增强mapper接口的实现

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;//记录关联的sqlsession对象
  private final Class<T> mapperInterface;//mapper接口对应的class对象;
//key是mapper接口中的某个方法的method对象,value是对应的MapperMethod,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {//如果是Object本身的方法不增强
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // ------从缓存中获取mapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中------
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //调用execute方法执行sql
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
 
}

MapperMethod:封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息,是Mapper接口与映射配 置文件中sql语句的桥梁。

因为不记录任何状态信息,所以它可以在多个代理对象之间共享。
它有几个关键的数据结构:
内部类 - - - SqlCommand:从configuration中获取方法的命名空间、方法名以及SQL语句的类型

内部类 - - - MethodSignature:封装mapper接口方法的相关信息 (入参、返回类型)

ParamNameResolver:解析mapper接口方法中的入参,将多个参数转成Map

运行流程:
从SqlSession.getMapper(Class ) 开始,时序图如下
在这里插入图片描述
该流程的核心是在三步翻译那里 ,主要方法MapperMethod的为execute方法
一、通过sql语句的类型、返回参数类型,来确定调用SqlSession中的某个方法
二、通过SqlCommand.name 生成二维坐标
三、通过MethodSignature.paramNameResolve 将传入的多个参数转成Map进行参数传递

具体代码如下:

//三步翻译在此完成
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //第一步 根据sql语句类型以及接口返回的参数选择调用不同的方法
    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()) {//返回值为void
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {//返回值为集合或者数组
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {//返回值为map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {//返回值为游标
          result = executeForCursor(sqlSession, args);
        } else {//处理返回为单一对象的情况
          //通过参数解析器解析解析参数
          Object param = method.convertArgsToSqlCommandParam(args);//第三步翻译,讲入参转化成Map
          result = sqlSession.selectOne(command.getName(), param); // 第二步,从SqlCommand中获取二维坐标
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = OptionalUtil.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;
  }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值