mybatis查询过程的源码分析

参考:咕泡学院的mybatis源码分析及手写实现教程

1. 运行hello world,提出问题

先自己运行一个demo,以mybatis为例,需要sqlsession,需要mapper,为什么需要呢?

2. 看设计文档,看模块划分,理解大局

需要看其design设计文档或先从模块上来看,大局上是什么样的,之后再跟进去看

看其架构设计图,看其是如何设计其系统的

如果没有架构设计图,那么就看其包组织的结构,

通过上面的包组织结构及基本的hello world demo,基本上就能猜测出来下面这张图

Configuration其实就是我们的全局配置xml文件

通过读取全局配置文件来给configuration来赋值

3. 边看代码,边画UML图

看代码的时候,要学会画UML图,包括类图(对象)和时序图(顺序)

mybatis是一个主架构比较清晰的框架,比较适合来进行阅读

 

SqlSession可以看做是mybatis的核心

看源码不是要把每一行看清,重点是把流程先看明白,细节可以在后面学习

看源码看得多的,可以看到一个接口设计的基本结构是

先声明一个interface,来定义好规范/模板

之后有一个abstract class,实现共性的功能

再下面有一个Default Class,即默认的实现

支持定制

上面这点也是应该学的,我们一般人就直接写interface--定制实现

如下面的Executor接口,就是这样设计的

源码分析

业务代码1

获取sqlSessionFactory对象

解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSessionFactory

Configuration中两个重要的属性:

1. MappedStatement:代表一个增删改查的详细信息

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

2. mapperRegistry:是一个Map,为每个Mapper绑定了对应的MapperProxyFactory对象,最后就是通过MapperProxyFactory来创建MapperProxy

public MapperRegistry getMapperRegistry() {

  return mapperRegistry;

}

代码:

String resource = "mybatis-config.xml";

InputStream inputStream = Resources.getResourceAsStream(resource);

return new SqlSessionFactoryBuilder().build(inputStream);

 

源码:

通过XMLConfigBuilder

 

具体的执行:

private void parseConfiguration(XNode root) {

  try {

    Properties settings = settingsAsPropertiess(root.evalNode("settings"));

    //issue #117 read properties first

    propertiesElement(root.evalNode("properties"));

    loadCustomVfs(settings);

    typeAliasesElement(root.evalNode("typeAliases"));

    pluginElement(root.evalNode("plugins"));

    objectFactoryElement(root.evalNode("objectFactory"));

    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

    reflectorFactoryElement(root.evalNode("reflectorFactory"));

    settingsElement(settings);

    // read it after objectFactory and objectWrapperFactory issue #631

    environmentsElement(root.evalNode("environments"));

    databaseIdProviderElement(root.evalNode("databaseIdProvider"));

    typeHandlerElement(root.evalNode("typeHandlers"));

    mapperElement(root.evalNode("mappers"));

  } catch (Exception e) {

    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);

  }

}

一层一层来解析,

ConfigurationParser

MapperParser

StatementParser,将sql语句封装为MappedStatement,一个MappedStatement对应我们mapper.xml中的一个增删改查标签

 

这样就通过XML文件获得了Configuration类,就是向Configuration类中填充内容

把配置文件的信息解析并保存在Configuration对象中

最后返回DefaultSqlSessionFactory

流程图

业务代码2:

获取sqlSession对象

返回一个DefaultSqlSession对象,包含Executor和Configuration;

在这一步会创建Executor对象

SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();

 

源码:

org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()

return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
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);
}

可以看到configuration.newExecutor  四大对象中的Executor

CachingExecutor中包含了一个Executor,其将这个executor进行了包装了wrapper

private Executor delegate;

public CachingExecutor(Executor delegate) {

  this.delegate = delegate;

  delegate.setExecutorWrapper(this);

}

delegate(executor)执行所有真正的增删改查,相当于CacheExecutor的门面

这样的好处就是:比如说我们开启了二级缓存,那么你在查询之前,就会有缓存操作

 

下面这句就比较重要了,与plugin有关

executor = (Executor) interceptorChain.pluginAll(executor);

拿到所有的拦截器,调用每个interceptor的plugin方法

每一个executor创建出来,都要执行这一步,使用每一个拦截器重新包装executor,并返回

 

接下来就是从全局配置文件中读取值,建立与db的会话session

return new DefaultSqlSession(configuration, executor, autoCommit);

流程图

边读源码变画图,整个流程通了,那么我们就可以自己山寨一个了

SqlSession持有configuration和executor

业务代码3:

获取接口的代理对象(MapperProxy)

getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象

代理对象里面包含了DefaultSqlSession(Executor),一层一层的包含

EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

我在没有看源码的情况下,猜测一下,其如果是简单的实现,那应该类似于从一个hashmap中把EmployeeMapper.class取出来,这个hashmap的填充应该是在业务代码1的build中进行的

源码:

@Override

public <T> T getMapper(Class<T> type) {

  return configuration.<T>getMapper(type, this);

}

 

xml配置或annotation配置或其他方式,spring框架、mybatis框架等都是这样一个套路,都是映射成一个configuration类

 

configuration.getMapper,我们可以大胆猜测之前的build里面,由xml生成Configuration的时候,把xml与mapper的interface已经组装好了。由于这里是EmployeeManager.class,那么就猜测其是由一个map进行关联的,map的key就是interface.class,

 

org.apache.ibatis.session.Configuration#mapperRegistry

return mapperRegistry.getMapper(type, sqlSession);

既然其也是用getMapper方法,那mapperRegistry中应该有一个hashmap,

 

org.apache.ibatis.binding.MapperRegistry中

我们看到了这个hashmap

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

这里看到HashMap的value是MapperProxyFactory<?>

 

我们知道,我们定义的interface EmployeeMapper,其是没有实现类的,那么没有实现类,是怎么运行方法的呢?

注意:这里是一个非常有意思的点

final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);

拿到这个MapperProxyFactory之后,

在MapperRegistry.java中 newInstance

return mapperProxyFactory.newInstance(sqlSession);

 

MapperProxyFactory.java

public T newInstance(SqlSession sqlSession) {

  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);

  return newInstance(mapperProxy);

}

mapperInterface是com.hfi.dao.EmployeeMapper

下面这段代码,就是我们熟悉的动态代理的代码

protected T newInstance(MapperProxy<T> mapperProxy) {

  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

}

但是你确定这个就是我们熟悉的动态代理的代码吗?

反正到这里这段业务代码就执行完成了,关于动态代理的事情后面再说

流程图

业务代码4:

1. 调用DefaultSqlSession的增删改查(Executor);

2. 会创建一个StatementHandler对象。

(同时也会创建出ParameterHandler和ResultSetHandler)

3. 调用StatementHandler预编译参数以及设置参数值;使用ParameterHandler来给sql设置参数

4. 调用StatementHandler的增删改查方法;

5. ResultSetHandler封装结果

注意:

        四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler),将我们创建的对象进行包装wrap

Employee employee = mapper.getEmployeeById(1);

源码

我们熟悉的动态代理

 

首先有一个TargetInterface,如IUserDao,其中提供了save方法。及其一个实现类TargetImpl,如UserDao,我想在其实现类UserDao中方法save的执行的前后,添加逻辑功能,使用动态代理怎么办呢?

MyProxy就是UserSaveHandler(其也可以使用匿名内部类的方式)

IUserDao target = new UserDao();

UserSaveHandler userSaveHandler = new UserSaveHandler(target);

IUserDao proxyInstance = (IUserDao) Proxy.newProxyInstance(userSaveHandler.getClass().getClassLoader(),

        target.getClass().getInterfaces(), userSaveHandler);

proxyInstance.save();

但是,我们EmployeeMapper没有实现类,用上面的例子来说,就是并没有UserDao这个实现类,那么其是如何运行的呢?

其会执行到MapperProxy的invoke方法中

我们看MapperProxy

class MapperProxy<T> implements InvocationHandler

其invoke方法

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  if (Object.class.equals(method.getDeclaringClass())) {

    try {

      return method.invoke(this, args);

    } catch (Throwable t) {

      throw ExceptionUtil.unwrapThrowable(t);

    }

  }

  final MapperMethod mapperMethod = cachedMapperMethod(method);

  return mapperMethod.execute(sqlSession, args);

}

第一个参数proxy是真实对象的真实代理对象。正常情况下应该是

com.hfi.mapper.EmployeeMapper@xxxxxx

但我们debug的时候,发现其为org.apache.ibatis.binding.MapperProxy@xxxxxx

 

这就是trick的点,这样设计就不需要实现类了

因为其使用MapperProxy去做了,其实压根就没有代理实现类,只需要接口的方法的定义,我拿到这个方法的定义,去映射文件xml中找sql就可以了,这整个的过程并不需要实现类。

也就是说,EmployeeMapper只是一个摆设,真正执行功能的是MapperProxy,在这个MapperProxy中是持有映射文件xml的所有的sql的(MapperProxy中持有DefaultSqlSession,而DefaultSqlSession中持有Configuration和Executor),我只要根据EmployeeMapper找到sql,执行后返回就可以了

后面我们想既然用了动态代理,那么应该调用method.invoke方法了吧,并不是

final MapperMethod mapperMethod = cachedMapperMethod(method);

 

其并没有调用method.invoke,而是直接执行下面的语句

MapperMethod中

就是要从configuration中拿sql去

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {

  this.command = new SqlCommand(config, mapperInterface, method);

  this.method = new MethodSignature(config, mapperInterface, method);

}

this.command

拿到了hashmap中配置的key,

接下来拿到一个methodSignature

其实就是读xml,拿到返回值、sql语句等等

 

this.method

其返回值,其参数都读进来了

之后才是运行方法

return mapperMethod.execute(sqlSession, args);

先进行了一个转换

Map<String, Object> param

 

Object param = method.convertArgsToSqlCommandParam(args);

result = sqlSession.selectOne(command.getName(), param);

然后执行selectOne方法

最后调用到selectList方法

MappedStatement ms = configuration.getMappedStatement(statement);

MappedStatement结果:

 

return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

用executor.query来真正执行

先还是要对我们传入的parameter进行wrap包装

private Object wrapCollection(final Object object) {

  if (object instanceof Collection) {

    StrictMap<Object> map = new StrictMap<Object>();

    map.put("collection", object);

    if (object instanceof List) {

      map.put("list", object);

    }

    return map;

  } else if (object != null && object.getClass().isArray()) {

    StrictMap<Object> map = new StrictMap<Object>();

    map.put("array", object);

    return map;

  }

  return object;

}

这就是我们所说的,如果我们的参数是collection list array,那么要取出来,就要使用#{list}

之后继续看query方法

 

在CachingExecutor.java

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {

  BoundSql boundSql = ms.getBoundSql(parameterObject);

  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);

  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

}

 

先把sql和参数进行绑定

先获取boundSql对象,包括sql,参数,以及sql和参数的映射关系

cacheKey是很长的一个字符串

-1566216868:1207450854:com.hfi.dao.EmployeeMapper.getEmployeeById:0:2147483647:select id,last_name lastName,email,gender from tbl_employee where id = ?:1:development

这就是我们的二级缓存及一级缓存中保存的key

Cache cache = ms.getCache();

if (cache != null) {

如果二级缓存中没有,那么继续向下执行,如果有,那么就直接执行

return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

delegate是SimpleExecutor,即CachingExecutor最终还是调用SimpleExecutor来执行

 

BaseExecutor

list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;

if (list != null) {

  handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

} else {

  list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

}

 

先向localCache中put一个占位符(类似于防止缓存击穿的效果),然后执行doQuery方法

之后再把占位符remove掉,再put进去

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 {

    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;

}

 

在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();

    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

    stmt = prepareStatement(handler, ms.getStatementLog());

    return handler.<E>query(stmt, resultHandler);

  } finally {

    closeStatement(stmt);

  }

}

Statement就是我们熟悉的java.sql包下的Statement

注意:这里涉及到4大对象的StatementHandler

StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

在Configuration类中

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);

  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

  return statementHandler;

}

注意:这里又出现了我们熟悉的interceptorChain

statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

 

在org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandler中

默认StatementType是Prepared,会创建一个PreparedStatementHandler对象,通过构造器创建。另外要注意的是,在创建StatementHandler的时候,同时还创建了ParameterHandler和ResultSetHandler

 

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

  this.configuration = mappedStatement.getConfiguration();

  this.executor = executor;

  this.mappedStatement = mappedStatement;

  this.rowBounds = rowBounds;



  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);

  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

}

newParameterHandler以及newResultSetHandler也是属于4大对象,同样有interceptorChain

至此,4大对象都创建完毕了

 

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {

  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);

  parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);

  return parameterHandler;

}



public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,

    ResultHandler resultHandler, BoundSql boundSql) {

  ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);

  resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);

  return resultSetHandler;

}

 

 

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {



  switch (ms.getStatementType()) {

    case STATEMENT:

      delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);

      break;

    case PREPARED:

      delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);

      break;

    case CALLABLE:

      delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);

      break;

    default:

      throw new ExecutorException("Unknown statement type: " + ms.getStatementType());

  }



}

SimpleExecutor中doQuery方法

stmt = prepareStatement(handler, ms.getStatementLog());

 

这个创建是很经典的jdbc创建statement的方式,拿到connection,prepare就是进行 参数预编译

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {

  Statement stmt;

  Connection connection = getConnection(statementLog);

  stmt = handler.prepare(connection, transaction.getTimeout());

  handler.parameterize(stmt);

  return stmt;

}

 

BaseStatementHandler中

@Override

public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {

  ErrorContext.instance().sql(boundSql.getSql());

  Statement statement = null;

  try {

    statement = instantiateStatement(connection);

    setStatementTimeout(statement, transactionTimeout);

    setFetchSize(statement);

    return statement;

  } catch (SQLException e) {

    closeStatement(statement);

    throw e;

  } catch (Exception e) {

    closeStatement(statement);

    throw new ExecutorException("Error preparing statement.  Cause: " + e, e);

  }

}

在org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement中,就是在这里进行参数预编译的

@Override

protected Statement instantiateStatement(Connection connection) throws SQLException {

  String sql = boundSql.getSql();

  if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {

    String[] keyColumnNames = mappedStatement.getKeyColumns();

    if (keyColumnNames == null) {

      return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);

    } else {

      return connection.prepareStatement(sql, keyColumnNames);

    }

  } else if (mappedStatement.getResultSetType() != null) {

    return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);

  } else {

    return connection.prepareStatement(sql);

  }

}

 

在BaseStatementHandler中,存在

protected final ParameterHandler parameterHandler;

这个又是4大对象之一。

在DefaultParameterHandler中,这里就看到jdbcType,也就是我们在resultMap中指定的jdbcType

TypeHandler typeHandler = parameterMapping.getTypeHandler();

JdbcType jdbcType = parameterMapping.getJdbcType();
typeHandler.setParameter(ps, i + 1, value, jdbcType);

通过调用typeHandler来设置参数

最后到PrepareStatementHandler中

@Override

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {

  PreparedStatement ps = (PreparedStatement) statement;

  ps.execute();

  return resultSetHandler.<E> handleResultSets(ps);

}

执行ps.execute

查询出来的结果,用resultSetHandler做mapping,mapping到我们的pojo里面去

可以看到

进入到DefaultResultSetHandler.java中

//

// HANDLE RESULT SETS

//

@Override

public List<Object> handleResultSets(Statement stmt) throws SQLException {

  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());



  final List<Object> multipleResults = new ArrayList<Object>();



  int resultSetCount = 0;

  ResultSetWrapper rsw = getFirstResultSet(stmt);



  List<ResultMap> resultMaps = mappedStatement.getResultMaps();

  int resultMapCount = resultMaps.size();

  validateResultMapsCount(rsw, resultMapCount);

  while (rsw != null && resultMapCount > resultSetCount) {

    ResultMap resultMap = resultMaps.get(resultSetCount);

    handleResultSet(rsw, resultMap, multipleResults, null);

    rsw = getNextResultSet(stmt);

    cleanUpAfterHandlingResultSet();

    resultSetCount++;

  }

获取到resultMap

然后拿到我们每个resultMap的每个元素

这就是为什么我们在写xml文件的时候,要指定jdbcType,就是这里映射的时候用的,我只是知道你数据库的类型,但我并不知道你java的数据类型,要把数据库的数据类型和java的数据类型做映射

在org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getPropertyMappingValue方法中,也是通过typeHandler来做的

typeHandler.getResult(rs, column);

 

各种TypeHandler

完成之后,

在SimpleExecutor的doQuery中

closeStatement(stmt);

在BaseExecutor中,

} finally {

  localCache.removeObject(key);

}

localCache.putObject(key, list);

放到缓存中

查询流程总结

至此,我们也就看到了完整的一个mybatis执行的过程

 

 

这样一个完整的执行过程,我们边读边画uml,最后得到这样一张图

根据这张图,我们就可以来自己手写我们的mybatis了

对于mybatis源码,我们还缺少一部分关于事务的内容

 

关于手写实现上面的基本功能,参考:手写实现乞丐版mybatis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值