MyBatis源码分析(四):SQL执行过程分析
一、获取Mapper接口的代理
根据上一节,Mybatis初始化之后,利用sqlSession(defaultSqlSession)的getMapper方法获取Mapper接口
1 @Override
2 public <T> T getMapper(Class<T> type) {
3 return configuration.<T>getMapper(type, this);
4 }
而调用configuration对象的getMapper方法
1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
2 return mapperRegistry.getMapper(type, sqlSession);
3 }
再次调用mapperRegister,注册mapper的类
1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
2 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
3 if (mapperProxyFactory == null) {
4 throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
5 }
6 try {
7 return mapperProxyFactory.newInstance(sqlSession);
8 } catch (Exception e) {
9 throw new BindingException("Error getting mapper instance. Cause: " + e, e);
10 }
11 }
而mapperRegister根据传进来的mapper接口来创建MapperProxyFactory代理工厂对象,再用sqlSession参数创建Mapper的代理对象,这里运用的是JDK的动态代理,Proxy.newProxyInstance方法绑定mapper接口,第一个参数是类加载器,第二个参数是需要实现的接口数组,第三个是InvocationHandler接口,也就是交由InvocationHandler接口实现类MapperProxy里的invoke()方法去处理
1 @SuppressWarnings("unchecked")
2 protected T newInstance(MapperProxy<T> mapperProxy) {
3 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
4 }
5
6 public T newInstance(SqlSession sqlSession) {
7 final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
8 return newInstance(mapperProxy);
9 }
然后就这样给UserMapper赋予了一个代理对象
1 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
二、使用Mapper代理对象进行查询操作
主代码调用代理对象查询,方法里面的参数为数据库字段的长整型id
1 user = userMapper.getUser(30L);
对应的mapper映射文件:
1 <select id="getUser" parameterType="long" resultMap="userMap">
2 SELECT user_id as id, user_name as username, sex, user_password as password, email from tb_user WHERE user_id = #{id}
3 </select>
使用Mapper代理对象,首先调用的是MapperProxy里面的invoke方法,传进三个主要的参数,分别是:代理对象、被调用的方法、方法的参数
1 @Override
2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
3 try {
4 if (Object.class.equals(method.getDeclaringClass())) {
5 return method.invoke(this, args);
6 } else if (isDefaultMethod(method)) {
7 return invokeDefaultMethod(proxy, method, args);
8 }
9 } catch (Throwable t) {
10 throw ExceptionUtil.unwrapThrowable(t);
11 }
12 final MapperMethod mapperMethod = cachedMapperMethod(method);
13 return mapperMethod.execute(sqlSession, args);
14 }
上面这段代码首先检查当前这个method是哪个类的方法,然后再判断有无默认方法,如果都没有则对方法进行缓存,最后对 SqlSession 进行的包装调用。
MapperMethod对SqlSession的操作进行了封装,来看其中的一段execute方法源码
1 public Object execute(SqlSession sqlSession, Object[] args) {
2 Object result;
3 switch (command.getType()) {
4 case INSERT: {
5 Object param = method.convertArgsToSqlCommandParam(args);
6 result = rowCountResult(sqlSession.insert(command.getName(), param));
7 break;
8 }
9 case UPDATE: {
10 Object param = method.convertArgsToSqlCommandParam(args);
11 result = rowCountResult(sqlSession.update(command.getName(), param));
12 break;
13 }
14 case DELETE: {
15 Object param = method.convertArgsToSqlCommandParam(args);
16 result = rowCountResult(sqlSession.delete(command.getName(), param));
17 break;
18 }
19 case SELECT:
20 if (method.returnsVoid() && method.hasResultHandler()) {
21 executeWithResultHandler(sqlSession, args);
22 result = null;
23 } else if (method.returnsMany()) {
24 result = executeForMany(sqlSession, args);
25 } else if (method.returnsMap()) {
26 result = executeForMap(sqlSession, args);
27 } else if (method.returnsCursor()) {
28 result = executeForCursor(sqlSession, args);
29 } else {
30 Object param = method.convertArgsToSqlCommandParam(args);
31 result = sqlSession.selectOne(command.getName(), param);
32 if (method.returnsOptional() &&
33 (result == null || !method.getReturnType().equals(result.getClass()))) {
34 result = Optional.ofNullable(result);
35 }
36 }
37 break;
38 case FLUSH:
39 result = sqlSession.flushStatements();
40 break;
41 default:
42 throw new BindingException("Unknown execution method for: " + command.getName());
43 }
44 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
45 throw new BindingException("Mapper method '" + command.getName()
46 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
47 }
48 return result;
49 }
调用的mapper的查询操作,先看看上面这段的SELECT这一段代码。首先是看方法的返回值类型是否为空并且结果处理器resultHandler,有的话则执行实现的ResultHandler的方法;之后也是检查方法的参数和返回类型,有的话执行各种情况下的方法;都没有的话,把参数传进SQL命令中
1 public Object convertArgsToSqlCommandParam(Object[] args) {
2 return paramNameResolver.getNamedParams(args);
3 }
可以看到参数传递利用了ParamNameResolver,处理接口形式的参数,最后会把参数处放在一个map中,
1 public Object getNamedParams(Object[] args) {
2 final int paramCount = names.size();
3 if (args == null || paramCount == 0) {
4 return null;
5 } else if (!hasParamAnnotation && paramCount == 1) {
6 return args[names.firstKey()];
7 } else {
8 final Map<String, Object> param = new ParamMap<>();
9 int i = 0;
10 for (Map.Entry<Integer, String> entry : names.entrySet()) {
11 param.put(entry.getValue(), args[entry.getKey()]);
12 // add generic param names (param1, param2, ...)
13 final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
14 // ensure not to overwrite parameter named with @Param
15 if (!names.containsValue(genericParamName)) {
16 param.put(genericParamName, args[entry.getKey()]);
17 }
18 i++;
19 }
20 return param;
21 }
22 }
参数解析完后,MapperMethod使用sqlSession,执行一条操作:
1 result = sqlSession.selectOne(command.getName(), param);
1 @Override
2 public <T> T selectOne(String statement, Object parameter) {
3 // Popular vote was to return null on 0 results and throw exception on too many.
4 List<T> list = this.selectList(statement, parameter);
5 if (list.size() == 1) {
6 return list.get(0);
7 } else if (list.size() > 1) {
8 throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
9 } else {
10 return null;
11 }
12 }
1 @Override
2 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
3 try {
4 MappedStatement ms = configuration.getMappedStatement(statement);
5 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
6 } catch (Exception e) {
7 throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
8 } finally {
9 ErrorContext.instance().reset();
10 }
11 }
sqlSession的selectList最后使用到MappedStatement,这个MappedStatement是保存Mapper中一个SQL语句的结点。利用执行器进行查询,第二个参数是为了检查参数是不是一个集合;
1 private Object wrapCollection(final Object object) {
2 if (object instanceof Collection) {
3 StrictMap<Object> map = new StrictMap<>();
4 map.put("collection", object);
5 if (object instanceof List) {
6 map.put("list", object);
7 }
8 return map;
9 } else if (object != null && object.getClass().isArray()) {
10 StrictMap<Object> map = new StrictMap<>();
11 map.put("array", object);
12 return map;
13 }
14 return object;
15 }
第三个参数是rowBounds逻辑分页方式,这里使用的是默认的;第四个是执行器的参数,这里是null。
然后跳到了CacheExecutor的query方法,它根据传进的MappedStatement参数获取BoundSql对象,ms中有mapper中的sql语句,放在SqlSource,然后根据传进来的参数组装成boundSql;之后生成一个对应二级缓存的key,
1 @Override
2 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
3 BoundSql boundSql = ms.getBoundSql(parameterObject);
4 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
5 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
6 }
但是Mybatis默认只开启了一级缓存,本例中并没有开启二级缓存,所以直接执行最后一个父类delegate.query方法,
1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
2 throws SQLException {
3 Cache cache = ms.getCache();
4 if (cache != null) {
5 flushCacheIfRequired(ms);
6 if (ms.isUseCache() && resultHandler == null) {
7 ensureNoOutParams(ms, boundSql);
8 @SuppressWarnings("unchecked")
9 List<E> list = (List<E>) tcm.getObject(cache, key);
10 if (list == null) {
11 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
12 tcm.putObject(cache, key, list); // issue #578 and #116
13 }
14 return list;
15 }
16 }
17 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
18 }
上面调用的是BaseExecutor中的query方法,此方法中的最重要的一段代码
1 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
2 if (list != null) {
3 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
4 } else {
5 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key,
6 boundSql);
7 }
因为我没有自己写的resultHandler类,所以直接执行
1 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
其方法源码为BaseExecutor抽象类中的queryFromDataBase
1 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
2 List<E> list;
3 localCache.putObject(key, EXECUTION_PLACEHOLDER);
4 try {
5 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
6 } finally {
7 localCache.removeObject(key);
8 }
9 localCache.putObject(key, list);
10 if (ms.getStatementType() == StatementType.CALLABLE) {
11 localOutputParameterCache.putObject(key, parameter);
12 }
13 return list;
14 }
queryFromDataBase中在深入,主要是第5行的doQuery方法:
1 @Override
2 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
3 Statement stmt = null;
4 try {
5 Configuration configuration = ms.getConfiguration();
6 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
7 stmt = prepareStatement(handler, ms.getStatementLog());
8 return handler.query(stmt, resultHandler);
9 } finally {
10 closeStatement(stmt);
11 }
12 }
StatementHadler是四大核心对象之一,它的任务就是和数据库对话。上面这段代码configuration.newStatementHandler方法使用了RoutingStatementHandler(采用的适配器模式)创建StatementHandler:
1 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
2
3 switch (ms.getStatementType()) {
4 case STATEMENT:
5 delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
6 break;
7 case PREPARED:
8 delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
9 break;
10 case CALLABLE:
11 delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
12 break;
13 default:
14 throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
15 }
16
17 }
RoutingStatementHandler执行query方法
1 @Override
2 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
3 return delegate.<E>query(statement, resultHandler);
4 }
PreparedStatementHandler执行query方法
1 @Override
2 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
3 PreparedStatement ps = (PreparedStatement) statement;
4 ps.execute();
5 return resultSetHandler.handleResultSets(ps);
6 }
DefaultResultSetHandler执行handleResultSets方法,getFirstResultSet获取第一个结果集在于知道sql语句要操作到哪些元素数据(表的列),会获取到元数据名称、Java数据类型、JDBC数据类型,之后getResultMaps获取执行的sql配置的resultMap,之后一个resultMap对应一个结果集,依次遍历resultMap并处理结果集
1 @Override
2 public List<Object> handleResultSets(Statement stmt) throws SQLException {
3 ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
4
5 final List<Object> multipleResults = new ArrayList<>();
6
7 int resultSetCount = 0;
8 ResultSetWrapper rsw = getFirstResultSet(stmt);
9
10 List<ResultMap> resultMaps = mappedStatement.getResultMaps();
11 int resultMapCount = resultMaps.size();
12 validateResultMapsCount(rsw, resultMapCount);
13 while (rsw != null && resultMapCount > resultSetCount) { //一个resultMap对应一个结果集,依次遍历resultMap并处理结果集
14 ResultMap resultMap = resultMaps.get(resultSetCount);
15 handleResultSet(rsw, resultMap, multipleResults, null); // 处理结果集
16 rsw = getNextResultSet(stmt);// 获取下一个结果集
17 cleanUpAfterHandlingResultSet();
18 resultSetCount++;
19 }
20
21 String[] resultSets = mappedStatement.getResultSets();
22 if (resultSets != null) {
23 while (rsw != null && resultSetCount < resultSets.length) {
24 ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
25 if (parentMapping != null) {
26 String nestedResultMapId = parentMapping.getNestedResultMapId();
27 ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
28 handleResultSet(rsw, resultMap, null, parentMapping);
29 }
30 rsw = getNextResultSet(stmt);
31 cleanUpAfterHandlingResultSet();
32 resultSetCount++;
33 }
34 }
35
36 return collapseSingleResultList(multipleResults); //把结果集转化为List
37 }
DefaultResultSetHandler的getFirstResultSet方法
1 private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
2 ResultSet rs = stmt.getResultSet();
3 while (rs == null) { // 没有结果集,也许是数据库驱动还没有返回第一个结果集
4 // move forward to get the first resultset in case the driver
5 // doesn't return the resultset as the first result (HSQLDB 2.1)
6 if (stmt.getMoreResults()) { // 尝试再一次获取结果集
7 rs = stmt.getResultSet();
8 } else {
9 if (stmt.getUpdateCount() == -1) { //表示驱动已经返回,没有更多结果,没有结果集
10 // no more results. Must be no resultset
11 break;
12 }
13 }
14 }
15 return rs != null ? new ResultSetWrapper(rs, configuration) : null; //不为空则返回结果集的包装
16 }
ResultSetWrapper构造函数(包装结果集)
1 public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
2 super();
3 this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
4 this.resultSet = rs;
5 final ResultSetMetaData metaData = rs.getMetaData();
6 final int columnCount = metaData.getColumnCount();
7 for (int i = 1; i <= columnCount; i++) { // 设置结果集的元数据
8 columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
9 jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
10 classNames.add(metaData.getColumnClassName(i));
11 }
12 }
把结果集转化为List
1 @SuppressWarnings("unchecked")
2 private List<Object> collapseSingleResultList(List<Object> multipleResults) {
3 return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
4 }
然后一层层传递回去,最后获得查询结果。(待续)
MyBatis源码分析(四):SQL执行过程分析的更多相关文章
- MyBatis 源码分析——动态SQL语句
有几年开发经验的程序员应该都有暗骂过原生的SQL语句吧.因为他们不能一句就搞定一个业务,往往还要通过代码来拼接相关的SQL语句.相信大家会理解SQL里面的永真(1=1),永假(1=2)的意义吧.所以m ...
- Mybatis源码分析之Mapper执行SQL过程(三)
上两篇已经讲解了SqlSessionFactory的创建和SqlSession创建过程.今天我们来分析myabtis的sql是如何一步一步走到Excutor. 还是之前的demo public ...
- 精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- 精尽 MyBatis 源码分析 - SqlSession 会话与 SQL 执行入口
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
随机推荐
- js-监听网络状态
<script> // 监听网络状态 window.addEventListener("online", function(){ alert("网络连接了&q ...
- CodeForce-734C Anton and Making Potions(贪心+二分)
CodeForce-734C Anton and Making Potions C. Anton and Making Potions time limit per test 4 seconds m ...
- 3.15学习总结(Python爬取网站数据并存入数据库)
在官网上下载了Python和PyCharm,并在网上简单的学习了爬虫的相关知识. 结对开发的第一阶段要求: 网上爬取最新疫情数据,并存入到MySql数据库中 在可视化显示数据详细信息 项目代码: im ...
- 1.24学习总结——HTML常见标签
HTML 标签简写及全称 下表列出了 HTML 标签简写及全称: 标签 英文全称 中文说明 a Anchor 锚 abbr Abbreviation 缩写词 acronym Acronym 取首字母的 ...
- ssh 执行 shell脚本执行jps时:-bash: jps: command not found
转至: https://www.codeleading.com/article/67592908468/ 我构建了hadoop集群.我们一定会写一个shell脚本去每一个节点上去jps,查看每个节点的 ...
- Go学习【02】:理解Gin,搭一个web demo
Go Gin 框架 说Gin是一个框架,不如说Gin是一个类库或者工具库,其包含了可以组成框架的组件.这样会更好理解一点. 举个 下面的示例代码在这:github 利用Gin组成最基本的框架.说到框架 ...
- javascript traverse object attributes 遍历对象属性
* for in for (var prop in o) { if (o.hasOwnProperty(prop)) { console.log(o[prop]); } } * Object keys ...
- MySQL修改root密码的多种方法, mysql 导出数据库(包含视图)
方法1: 用SET PASSWORD命令 mysql -u root mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass ...
- excel模板数据填充 :tablefill
背景(问题) 在Web后台系统中或多或少都存在导入数据的功能,其中操作流程基本是 1.下载模板 2.填充模板数据 3.上传模板 但通常比较耗费时间的是填充模板数据这一步骤, 已自己为例之前的数据要么是 ...
- Unity——对象池管理
Unity对象池管理 一.Demo展示 二.逻辑 在游戏中会出现大量重复的物体需要频繁的创建和销毁:比如子弹,敌人,成就列表的格子等: 频繁的创建删除物体会造成很大的开销,像这种大量创建重复且非持续性 ...