您的位置 首页 java

mybatis3 源码深度解析-SqlSession 执行 Mapper 接口过程

大纲

  • Mapper 接口的注册过程
  • MapperStatement 对象创建过程
  • Mapper 接口方法的调用过程(动态代理)
  • SqlSession 执行 Mapper的过程

Mapper 接口的注册过程

  • mapper 调用代码示例
 // 提前编写好 mybatis-config.xml 
String resource = "javabus-mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
// 1 构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 2 创建会话 SqlSession
SqlSession session = sqlSessionFactory.openSession();
// 3 执行sql,并且接收返回结果
//User user = session.selectOne("cn.javabus.mapper.UserMapper.getUser", 1);
//SQL语句执行方式二 返回的是MapperProxy 代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
//JVM会首先调用代理对象的 invoke() ;详情可了解 jdk 动态代理
User user = mapper.getUser("uid1");  
  • UserMapper 是一个interface接口,所以SqlSession.getMapper()返回的是什么?
  • 接口必须通过某个类实现,然后才能调用实现类的方法. getMapper()返回的是一个 jdk 动态代理技术创建的一个动态代理对象.
  • 代理涉及概念: 1 目标对象; 2 代理对象; 3 创建代理对象的工具类(jdk 是 Proxy)
 // 1 SqlSession.getMapper() 
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  //MapperProxyFactory首先为Mapper接口创建了一个实现了InvocationHandler方法调用处理器接口的代理类MapperProxy
  return mapperProxyFactory.newInstance(sqlSession);
}

// 2 mapperProxyFactory.newInstance(sqlSession)
// 最终返回的是 Proxy.newProxyInstance() 创建的代理对象
public T newInstance(SqlSession sqlSession) {
    //创建MapperProxy代理对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    //3 使用 jdk字节码技术生成动态代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

/**
 *  @param loader 类加载器
 *  @param interfaces  目标对象接口,业务接口
 *  @param h  实现了 InvocationHandler接口的代理对象
 */@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,  
  Class<?>[] interfaces, 
  InvocationHandler h);

//mapperProxyFactory.newInstance(sqlSession) 是非静态的,每次创建 mapper 动态代理对象需要先创建 mapperProxyFactory 对象
//configuara.mapperRegistry.knownMappers 保存了 Mapper class 对象与MapperProxyFactory对应关系  
 /**
 * 1 Mapper接口代理类 
 (实现了InvocationHandler接口,使用 jdk 动态代理技术生成 Mapper动态代理对象)
 */public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  /**
   * 缓存一下要调用的方法
   */  private final Map<Method, MapperMethod> methodCache;

  /**
   * 2 通过持有 mapperInterface 接口的引用 (和常见 jdk 动态代理示例相比: 没有真实对象,但是能通过接口找到调用的 sql语句)
   * @param methodCache
   */  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  /**
   * 通过 jdk 生成动态代理实现类,会生成业务接口的实现类和方法,会在方法内部先调用invoke()
   * @param proxy  通过 jdk Proxy生成的代理对象 org.apache.ibatis.binding.MapperProxy@3bd40a57
   * @param method 要执行被代理对象的方法 UserMapper.getUser(x)
   * @param args   要执行方法的入参
   */  @Override//
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //1  如果执行的是 equals 等 Object 类的方法,就直接调用
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //2 对 Mapper接口中的方法进行封装,生成M apperMethod 对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //3 调用mapperMethod 的 execute()方法
    return mapperMethod.execute(sqlSession, args);
  }  

MapperStatement 对象创建过程

MapperStatement 是用于封装 Mapper 文件中的 sql 语句的.

当我们运行mapper文件时,每一个Mapper 接口中@Select|@Update等注解,或者 Mapper.xml select|update|insert|delete 对应的 Sql 语句,这些crud的标签都会被封装为一个MapperStatement对象

想了解MapperStatement 对象创建过程,就需要重点关注 Configuration 对象解析过程中<mappers> 标签的解析过程,主要是做了以下几件事情

1 获取<select|insert|update|delete> 标签所有属性信息

2 将<include> 标签引用的 sql 片段 替换为对应 <sql>标签对应的内容

3 通过 lang 属性创建 SqlSource (sql 语句 select * from user where id = ?)

4 获取 KeyGenerator (KeyGenerator 主键生成策略)

5 所有解析完成后,使用 MapperBuilderAssistant 创建MappedStatement对象,

添加到 Configuration#mappedStatements 保存起来

Mapper 接口方法的调用过程(动态代理)

 SqlSession session = sqlSessionFactory.openSession();
//返回的是MapperProxy jdk 动态代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
//JVM会首先调用代理对象的 invoke() 方法
User user = mapper.getUser("uid1");  
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //1  如果执行的是 equals 等 Object 类的方法,就直接调用
  if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
  }

  //2 对 Mapper接口中的方法进行封装,生成M apperMethod 对象
  //MethodSignature 构造器 做了方法参数解析等处理
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  //3 调用mapperMethod 的 execute()方法
  return mapperMethod.execute(sqlSession, args);
}

/**
 * MapperMethod 对 Mapper 接口相关方法进行封装(可以方便获取 sql 语句类型,方法签名等)
 */public class MapperMethod {
  private final SqlCommand command;//用于获取 sql 语句类型,resolveMappedStatement()可以获取 MappedStatement对象
  private final MethodSignature method;
  
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        //获取目标方法参数信息
        Object param = method.convertArgsToSqlCommandParam(args);
        //调用 sqlSession insert()方法
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
  }
}  

SqlSession 执行 Mapper的过程

 SqlSession只有一个默认实现 DefaultSqlSession

//1 DefaultSqlSession.selectList()
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  //statement cn.javabus.mapper.UserMapper.getUser
  MappedStatement ms = configuration.getMappedStatement(statement);
  //MappedStatement 作为参数,调用executor.query()方法
  return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}

//2 BaseExecutor.query()
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 首先根据传递的参数获取BoundSql对象,对于不同类型的SqlSource,对应的getBoundSql实现不同,具体参见SqlSource详解一节 TODO
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建缓存key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 委托给重载的query
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

//3 缓存中查询不到则调用 BaseExecutor.queryFromDatabase()
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    // todo doQuery是个抽象方法,每个具体的执行器都要自己去实现,我们先看SIMPLE的
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

//4  SimpleExecutor.doQuery 
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    // 根据上下文参数和具体的执行器new一个StatementHandler, 其中包含了所有必要的信息,比如结果处理器、参数处理器、执行器等等,
    // 主要有三种类型的语句处理器UNPREPARE、PREPARE、CALLABLE。默认是PREPARE类型,
    // 通过mapper语句上的statementType属性进行设置,一般除了存储过程外不应该设置
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

    // todo 获取jdbc Statement对象,以及设置入参
    stmt = prepareStatement(handler, ms.getStatementLog());

    // todo 内部调用PreparedStatement完成具体查询后,将ps的结果集传递给对应的结果处理器进行处理。
    // 查询结果的映射是mybatis作为ORM框架提供的最有价值的功能,同时也可以说是最复杂的逻辑之一。
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

//5  SimpleExecutor.prepareStatement()
 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 1 jdbc 第一步 获取JDBC连接
    // 通过DriverManager获取连接 Connection conn=DriverManager.getConnection(url,"root","hello");
    Connection connection = getConnection(statementLog);
    // 调用语句处理器的prepare方法
    // 先走RoutingStatementHandler,又委派给了 BaseStatementHandler.prepare
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 设置参数 调用了 ps.setInt(1)等设置值
    handler.parameterize(stmt);
    return stmt;
  }

//6  handler.query() 调用的是 PreparedStatementHandler.query()
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  //调用resultSetHandler处理结果集
  return resultSetHandler.handleResultSets(ps);
}

//7 resultSetHandler.handleResultSets(ps) ...  

文章来源:智云一二三科技

文章标题:mybatis3 源码深度解析-SqlSession 执行 Mapper 接口过程

文章地址:https://www.zhihuclub.com/174847.shtml

关于作者: 智云科技

热门文章

网站地图