一、框架设计

mybatis basemapper自带方法实现原理_List


1. 接口层—和数据库交互的方式:以使用Mapper接口为例

将配置文件中的每一个<mapper> 节点抽象为一个 Mapper 接口,这个接口中声明的方法和跟Mapper.xml中的<select|update|delete|insert> 节点项对应,id值对应方法名称,parameterType 值对应方法的入参类型,而resultMap 值则对应返回值类型。

mybatis basemapper自带方法实现原理_List_02


配置好后,调用SqlSession.getMapper(XXXMapper.class) 方法,MyBatis 会根据接口声明的方法信息,通过动态代理机制生成一个Mapper 实例,当调用接口方法时,根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过 SqlSession.select/update( “statementId”, parameter) 等来实现对数据库的操作。

2. 数据处理层:可以说是MyBatis 的核心
a. 通过传入参数构建动态SQL语句;
b. SQL语句的执行以及封装查询结果集成List

2.1 参数映射和动态SQL语句生成
MyBatis 通过传入的参数值,使用 Ognl 来动态地构造SQL语句。
参数映射指的是java 数据和jdbc数据之间的转换:包括两个过程:查询阶段,将java类型的数据,转换成jdbc类型的数据,通过 preparedStatement.setXXX() 来设值;另一个就是对resultset查询结果集的jdbcType 数据转换成java 数据类型。

二、MyBatis的主要构件及其相互关系

mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过SqlSession, Executor, StatementHandler,ParameterHandler, ResultHandler和TypeHandler等几个处理器封装了这些过程.

mybatis basemapper自带方法实现原理_sql_03

  • SqlSession :主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能,通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,在使用完毕后需要close。
  • Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。
  • StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
  • MappedStatement :维护了一条<select|update|delete|insert>节点的封装。
  • Configuration:MyBatis所有的配置信息都维持在Configuration对象之中。

注: StatementHandler通过ParameterHandler与ResultHandler分别进行参数预编译 与结果处理。而ParameterHandler与ResultHandler都使用TypeHandler进行映射。

三、从MyBatis一次select 查询语句来分析MyBatis的架构设计
3.1、数据准备

i. 准备数据库数据,创建EMPLOYEES表,插入数据
ii. 配置Mybatis的配置文件,命名为Config.xml
iii. 创建Employee实体Bean 以及配置Mapper配置文件、相应接口
iv. 编写客户端代码

3.2、SqlSession 的工作过程分析:

a) 创建SqlSession对象:

SqlSession sqlSession = factory.openSession();

MyBatis封装了对数据库的访问,把对数据库的会话和事务控制放到了SqlSession对象中。

mybatis basemapper自带方法实现原理_sql_04


b) 为SqlSession传递一个配置的Sql语句 的Statement Id和参数,然后返回结果

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //1.根据Statement Id,在mybatis 配置对象Configuration中查找和配置文件相对应的MappedStatement	
      MappedStatement ms = configuration.getMappedStatement(statement);
      //2. 将查询任务委托给MyBatis 的执行器 Executor
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
}

每一条<select|update|delete|insert> 会生成一个对应的MappedStatement对象,然后会以key=“namespace.id” ,value为MappedStatement对象,维护到Configuration的一个Map中。以后需要使用的时候,只需要通过key值来获取就可。

c) 执行器Executor根据SqlSession传递的参数执行query()方法
Executor的作用是:

  • 根据传递的参数,完成SQL的动态解析,生成BoundSql对象;
  • 为查询创建缓存,以提高性能;
  • 创建JDBC 的Statement 连接对象,传递给StatementHandler对象,返回List查询结果。

Executor.query()方法最后会创建一个StatementHandler对象,负责设置Statement 对象中的查询参数、处理JDBC返回的resultSet,将resultSet 加工为List 集合返回。

//SimpleExecutor类的doQuery()方法实现
  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();
      // 根据既有的参数,创建StatementHandler对象来执行查询操作
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 创建java.Sql.Statement对象,传递给StatementHandler对象
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 调用StatementHandler.query()方法,返回List结果集
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
}

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //getConnection方法经过重重调用最后会调用openConnection方法,从连接池中获得连接
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    //对创建的Statement对象设置参数,即设置SQL 语句中 ? 设置为指定的参数
    handler.parameterize(stmt);
    return stmt;

StatementHandler对象主要完成两个工作:

  • 对JDBC的PreparedStatement 对象产生的占位符 ?进行设值。
  • 通过List query(Statement statement, ResultHandler resultHandler)方法来完成执行Statement,将Statement对象返回的resultSet封装成List集合;

d) StatementHandler的parameterize(statement) 方法的实现:

public void parameterize(Statement statement) throws SQLException {
	//使用ParameterHandler对象来完成对Statement的设值  
    parameterHandler.setParameters((PreparedStatement) statement);
}

ParameterHandler的setParameters(Statement)方法负责 根据我们输入的参数,对statement对象的 ? 占位符处进行赋值。

f) List query(Statement statement, ResultHandler resultHandler)方法的实现:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
	// 1.调用preparedStatemnt.execute()方法,然后将resultSet交给ResultSetHandler处理  
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();   //原生JDBC执行
    //2. 使用ResultHandler来处理ResultSet
    return resultSetHandler.<E> handleResultSets(ps);
}

ResultSetHandler的handleResultSets(Statement) 方法会将Statement语句执行后生成的resultSet 结果集转换成List 结果集。

四、接口方式:动态代理

mybatis basemapper自带方法实现原理_mybatis_05


先介绍一下MyBatis初始化时对接口的处理:MapperRegistry是Configuration中的一个属性,它内部维护一个HashMap用于存放mapper接口的工厂类,每个接口对应一个工厂类。

解析mappers标签时,当解析到接口时,会创建此接口对应的MapperProxyFactory对象,存入HashMap中,key = 接口的字节码对象,value = 此接口对应的MapperProxyFactory对象。

进入sqlSession.getMapper(UserMapper.class)中

//MapperRegistry中的getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	//1. 从MapperRegistry中的HashMap中拿MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //2. 通过动态代理工厂生成示例。
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

//MapperProxyFactory类中的newInstance方法
 public T newInstance(SqlSession sqlSession) {
 	// 3. 创建了JDK动态代理的Handler类
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 4. 调用了重载方法
    return newInstance(mapperProxy);
  }

//MapperProxy类,实现了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable { 

  //省略部分源码	
  
  // 构造,传入了SqlSession,说明每个session中的代理对象的不同的!
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //判断调用是是不是Object中定义的方法,toString,hashCode这类非。是的话直接放行。
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    } 
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 重点在这:MapperMethod最终调用了执行的方法
    return mapperMethod.execute(sqlSession, args);
  }
}

//5. 重载的方法,由动态代理创建新示例返回。
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
 }