当我们使用sqlSession.getMapper(xx.class)方法时,Mybatis其实是使用了jdk的动态代理技术,在MapperProxyFactory中生成对应的Mapper对象。
这段是MappedProxyFactory中的一段代码
protected T newInstance(MapperProxymapperProxy) { 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#的invokepublic 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进行缓存 MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); } }
紧接着,调用了MapperMethod#execute(this,sqlSession,args);
这个方法比较简单,就是根据节点的类型,进行相应的处理。比如节点是insert 那就走到insert的逻辑,其他类似了。。。本人的节点类型是select,方法返回值是list,所以代码执行了这个方法
privateObject 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
privateList 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#doQuerypublicList 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的类型,创建不同的类型的StatementHandlerSimpleStatementHandler:这个类对应于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对结果进行包装。并且查询完结果后,会将结果放到缓存中去