从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

    • Redis

    • Mybatis

      • Mybatis介绍
      • Mybatis入门案例
      • Mybatis入门案例-注解
      • Mybatis入门案例-实现类
      • Mybatis内部执行原理概述
      • 实现一个微型的Mybatis-配置文件版
      • 实现一个微型的Mybatis-注解版
      • Mybatis实现CRUD
      • Mybatis中传递对象参数
      • Mybatis中的列名和属性名的映射
      • Mybatis实现DAO层的开发
      • Mybatis实现类的执行过程-查询方法
        • 打断点
        • SqlSession
        • Executor
        • Handler​
        • 梳理
        • 代理对象
      • properties标签的使用及细节
      • typeAliases标签和package标签
      • Mybatis连接池和事务
      • Mybatis与JNDI
      • Mybatis中的动态SQL
      • Mybatis多表查询
      • Mybatis中的多对多查询
      • Mybatis的延迟加载
      • Mybatis的缓存
      • Mybatis的注解开发-CRUD
      • Mybatis的注解开发-多表查询
      • Mybatis
    • Lucene

    • Elasticsearch

    • MQ

    • MyCat

    • Lombok

    • 主流框架
  • SpringMVC

  • SpringBoot

  • Java并发

  • Java源码

  • JVM

  • 韩顺平

  • Java
  • Java
  • 主流框架
  • Mybatis
2023-04-25
目录

Mybatis实现类的执行过程-查询方法

# 110.Mybatis实现类的执行过程-查询方法

我们从上一节的DAO的源码,一步步分析代码执行的过程   ‍

‍

# 打断点

我们在MybatisTestImpl​的testFindAll​方法上打一个断点:

​​

‍

‍

‍

在UserDaoImpl​类的findAll​上也打一个:

​​

‍

然后我们debug运行:可以在test方法上右键--调试 xxx :

​​

‍

‍

‍

# SqlSession

调试的过程中,首先我们可以看到实现类分别是DefaultSqlSessionFactory和 DefaultSqlSession

​​

知道这个后,我们先停止调试。

‍

我们点进SqlSession​的 源码,然后寻找它的实现类:在类名上右键--diagram--show diagram,例如:

​​

‍

可以看到这样的图:这两个接口都是java提供的,用来关闭资源的接口

​​​​

‍

我们回到SqlSession,可以查看其实现类:

​​

‍

‍

根据之前的debug,我们选择DefaultSqlSession:

​​​​

‍

然后我们就可以看到其源码了:

​​

‍

‍

我们观察selectList方法:可以看到有4个重载

public <E> List<E> selectList(String statement) {
    return this.selectList(statement, (Object)null);
}

public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    List var6;
    try {
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        this.dirty |= ms.isDirtySelect();
        var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
    } catch (Exception var10) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
    } finally {
        ErrorContext.instance().reset();
    }

    return var6;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

‍

可以看到,前3个selectList方法,最终调用的都是第4个selectList方法;而该方法通过调用executor.query​方法返回数据;我们点进query方法,可以看到这又是一个接口:

public interface Executor {
}
1
2

‍

# Executor

为了看到具体是哪个实现类,我们打个断点:

​​

‍

通过断点调试,我们看到其调用的实现类是CachingExecutor​

​​

‍

我们找到有4个方法重载的query方法:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
1
2
3
4
5

‍

注:我们可以在这里加个断点,然后继续调试,可以看到确实是执行了这个query方法:

​​​​

‍

可以看到query最后又调用了一个6个参数的query方法,而该query方法最后调用的是delegate.query​方法

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //..........
    return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
1
2
3
4

​

‍

可以看到最后delegate,实现类是SimpleExecutor

​

‍

SimpleExecutor类中,没有query方法,这是因为BaseExecutor里有query方法,该方法最终调用的就是doQuery方法,该方法是抽象方法,最终被SimpleExecutor实现:

public class SimpleExecutor extends BaseExecutor {

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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

‍

该doquery方法最终调用的是

handler.query(stmt, resultHandler)
1

‍

而这个Handler是RoutingStatementHandler​:

​​

‍

# Handler​

我们通过查找StatementHandler​的实现类 RoutingStatementHandler​,可以看到其还是有query方法:

​​

‍

‍

而最后调用的就是PreparedStatementHandler​的query方法

​​

‍

‍

该query方法的内容如下:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
    ps.execute();
    return this.resultSetHandler.handleResultSets(ps);
}
1
2
3
4
5

‍

这个ps是一个代理对象:execute能执行任意的SQL,我们在讲解JDBC时讲过了:JDBC常用类介绍 (opens new window)

​​

‍

‍

执行完后做了结果集的封装,ResultSetHandler​接口只有一个实现类: DefaultResultSetHandler​。handleResultSets方法部分源码如下:

while(rsw != null && resultMapCount > resultSetCount) {
    ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
    this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
    rsw = this.getNextResultSet(stmt);
    this.cleanUpAfterHandlingResultSet();
    ++resultSetCount;
}
1
2
3
4
5
6
7

也就是获取每一行的数据,然后第3行那里调用handleResultSet处理每一行的数据,例如根据列名和Java类名进行映射;

‍

‍

# 梳理

我们在MybatisTestImpl里调用了Dao实现类的session.selectList方法:

public List<User> findAll() {
    SqlSession session = factory.openSession();
    List<User> users = session.selectList("com.peterjxl.dao.IUserDao.findAll");
    session.close();
    return users;
}
1
2
3
4
5
6

该Session是DefaultSqlSession;由DefaultSqlSessionFactory工厂创建。

DefaultSqlSession调用的selectList方法, 有很多个重载,但最终调用的是Executor的query方法

Executor是个接口,实现类有CachingExecutor,该类里也有多个重载的query方法,最终调用的是delegate.query方法;

delegate是SimpleExecutor的实例,其query方法最后调用的是RoutingStatementHandler的方法;

RoutingStatementHandler类里,就有一个PreparedStatement对象,并且调用JDBC的execute方法执行任何的SQL,最后通过resultSetHandler封装返回的结果。

除了查询,删除和修改的执行过程我们就不一一演示了,也是类似的执行过程,并且其底层都是调用JDBC的接口

此外,断点调试和跟踪的方法,是我们在实际开发中经常用到的 ,希望读者们能好好掌握

‍

‍

‍

# 代理对象

之前我们是自己实现的DAO,调用了Session的select方法;而如果是代理对象呢?其内部是调用selectList方法的原理是什么呢?

我们根据之前的模式来,首先代理对象是通过getMapper方法获取的;

userDao = session.getMapper(IUserDao.class);
1

‍

‍

getMapper是session接口的方法,我们得找其实现类DefaultSqlSession的源码,其getMapper方法如下:

public <T> T getMapper(Class<T> type) {
    return this.configuration.getMapper(type, this);
}
1
2
3

‍

‍

​Configuration​里方法如下:

public MapperRegistry getMapperRegistry() {
    return this.mapperRegistry;
}
1
2
3

‍

‍

​MapperRegistry​中,则创建了代理对象(第7行,熟悉的newInstance)

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    } else {
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception var5) {
            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

‍

‍

​MapperProxyFactory​的源码:

protected T newInstance(MapperProxy<T> mapperProxy) {
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    return this.newInstance(mapperProxy);
}
1
2
3
4
5
6
7
8

‍

也就是最后就是调用Proxy的newProxyInstance方法。因此我们的关注点,就是mapperProxy,其内部应该是实现了增强方法的逻辑。

‍

‍

MapperProxy部分源码:可以看到其实现了InvocationHandler,并且有invoke方法:

public class MapperProxy<T> implements InvocationHandler, Serializable {

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
}
1
2
3
4
5
6
7
8
9
10

‍

观察cachedInvoker,可以看到其用到了一个类MapperMethod​:

​​

‍

‍

​MapperMethod​部分源码如下:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    Object param;
    switch (this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:

    //..... 省略其他
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

‍

看到这里,大家应该都知道了,其最后就是根据switch判断执行什么SQL,然后调用对应的方法,例如sqlSession.insert,sqlSession.update。

而insert,update方法,最后的调用过程,跟我们本文前半部分讲的一样,也是Executor和Handler。

‍

在GitHub上编辑此页 (opens new window)
上次更新: 2023/5/16 10:04:12
Mybatis实现DAO层的开发
properties标签的使用及细节

← Mybatis实现DAO层的开发 properties标签的使用及细节→

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式