当我们使用sqlSession.getMapper(xx.class)方法时,Mybatis其实是使用了jdk的动态代理技术,在MapperProxyFactory中生成对应的Mapper对象。
这段是MappedProxyFactory中的一段代码
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
其中mapperProxy对应的是MapperProxy,该类实现了InvocationHanlder接口。
正如我们所知,JDK的动态代理是根据接口来生成代理对象的。在Mybatis找那个可以根据Mapper.xml中的namespace属性,确定类的全限定类名。并且根据节点(insert | select | update | delete)所对应的ID,找到所要执行的方法。也因此,Mybatis中的方法是不能重载的。因为他根据的是类名+方法名进行唯一确定MapperStatement(节点)的。
这个就是MapperStatement所封装的一些信息了
再接着,当我们调用Mapper对应的方法时,此时,会交给代理对象进行处理。
MapperProxy#的invoke
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 var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
//执行到这一步,如果方法是第一次调用,那么会创建对象,如果不是则使用缓存
//需要注意的是调用的cacheMapperMethod方法,其实就是用一个Map<Method,MapperMethod>进行缓存
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
紧接着,调用了MapperMethod#execute(this,sqlSession,args);
这个方法比较简单,就是根据节点的类型,进行相应的处理。比如节点是insert 那就走到insert的逻辑,其他类似了。。。
本人的节点类型是select,方法返回值是list,所以代码执行了这个方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if(this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}
return !this.method.getReturnType().isAssignableFrom(result.getClass())?(this.method.getReturnType().isArray()?this.convertToArray(result):this.convertToDeclaredCollection(sqlSession.getConfiguration(), result)):result;
}
这里看到方法还执行了convertArgsToSqlCommandParam(args)方法,这个方法返回的对象值如下。其实说白了,就是对我们传进去参数的封装。需要注意到,这边其实是个Map对象,因为我的Mapper接口的方法是使用@Param注解的形式的。如果你传进去的是个POJO或者Map,那么这边就是POJO或者Map,如果是基本数据类型(单个值),那么会被转换成包装数据类型
中间的有些步骤就省略了。接下里的话,会将于需要执行的对象的方法,sql,sql参数创建一个缓存key
.
然后调用CacheExecutor#query,根据key查询有没有缓存,如果有缓存,直接从缓存中拿,如果没有,则继续执行。
期间会调用一个比较重要的方法 BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER); //key其实就是之前提到过的CacheKey,value只是充当一个占位。。
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql); //代码执行到这里。。。
} finally {
this.localCache.removeObject(key);
}
this.localCache.putObject(key, list);
if(ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
下面就是比较关键的地方了
代码执行了simpleExecutor#doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
从这边可以看出在Executor内创建了StatementHandler,并对参数进行了预处理,而调用的handler.query()方法后,var9的值,就是我们所要查询的结果了。
值得一提的是StatementHandler有4个默认的实现类:
RoutingStatementHandler:这是一个封装类,不提供具体的实现,根据Executor的类型,创建不同的类型的StatementHandler
SimpleStatementHandler:这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行
PreparedStatementHandler:用于预编译参数SQL的运行
CallableStatementHandler:用于存储过程的调度
在newStatementHandler方法中,我们也可以看到是创建了RoutingStatementHandler对象,会根据具体的Executor类型,创建不同的StatementHandler。而这个具体的StatementHandler被存储在了RoutingStatementHandler的delegate属性中
先看configuration#newStatementHandler。注意到这边有个interceptorChain.pluginAll 这边就是用来执行插件的。
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 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
这个是所创建的StatementHandler所包含的信息,可以看到包含了很多东西
再来看simpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog); // 在此处获取了数据的连接,此对象是个包装对象,包装了JDBC的Connection
Statement stmt = handler.prepare(connection); // 这边是进行一些预处理
handler.parameterize(stmt);
return stmt;
}
这边的调用的parameterize的方法就是对参数进行预处理了。其实就是遍历parameterMappings集合,然后从里面取出参数的属性,对参数进行处理,这个就是这个方法的逻辑。(注:parameterMappings 集合中存放的是传递进来的参数的属性)
当查询到结果到时候,会调用DefaultResultSetHandler对结果进行包装。并且查询完结果后,会将结果放到缓存中去
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。