MyBatis核心 - SqlSession如何通过Mapper接口生成Mapper对象

简介: 从 SqlSessionFactoryBuilder - SqlSessionFactory - SqlSession - Mapeper实例对象 的过程

书接上文 MyBatis -- 执行流程

我们通过SqlSession获取到了UserMapper对象,代码如下:

// 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();

// 执行查询操作
try {
   
   
    // 获取映射器接口
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 调用映射器接口的方法执行查询操作
    List<User> users = userMapper.getAllUsers();

    // 处理查询结果
    for (User user : users) {
   
   
        System.out.println(user.getId() + " - " + user.getName());
    }
} finally {
   
   
    // 关闭SqlSession
    sqlSession.close();
}

我们看到,往 sqlSession.getMapper 传入UserMapper接口后,得到的是一个 userMapper 对象,这是怎么做到的呢?

查看SqlSession源码发现,SqlSession有两个实现类,在正常情况下使用的当然就是默认的 DefaultSqlSession

DefaultSqlSession中有以下属性,其中最重要的两个属性是 Configuration 和 Executor

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;

什么是Configuration

查看SqlSessionFactory的默认实现类 DefaultSqlSessionFactory 发现,其内部只有一个属性,就是Configuration

那DefaultSqlSessionFactory的Configuration从哪里获取到的值呢?那就得追踪到SqlSessionFactoryBuilder,在上文中,我们通过如下方式创建SqlSessionFactory对象

// 创建 SqlSessionFactoryBuilder 对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

// 加载配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

// 构建 SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = builder.build(inputStream);

// 关闭配置文件流
inputStream.close();

查看SqlSessionFactoryBuilder的源码(实际上SqlSessionFactoryBuilder 的所有方法就是多个重载的build,以下仅展示我们使用到的),如下:

  public SqlSessionFactory build(Reader reader) {
   
   
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
   
   
    try {
   
   
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
   
   
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
   
   
      ErrorContext.instance().reset();
      try {
   
   
        reader.close();
      } catch (IOException e) {
   
   
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

我们调用了 build(Reader reader) 方法,而该方法内部调用了 build(Reader reader, String environment, Properties properties) 方法,其中只传入了一个reader参数,另外两个参数是null,reader参数保存的是核心配置文件 mybatis-config.xml 的信息

通过reader获取到XMLConfigBuilder对象,我们不知道这个对象到底是什么,但是我们知道它的作用仍然是保存 mybatis-config.xml 的信息

接着return 了 build(parser.parse()) 方法,该方法源码如下

public SqlSessionFactory build(Configuration config) {
   
   
    return new DefaultSqlSessionFactory(config);
  }

发现调用了DefaultSqlSessionFactory的构造方法,并传入携带 mybatis-config.xml 信息的config

该构造方法源码如下:

  public DefaultSqlSessionFactory(Configuration configuration) {
   
   
    this.configuration = configuration;
  }

将config赋值给了 DefaultSqlSessionFactory对像 的 configuration 属性

然后我们调用如下代码获取到SqlSession 对象(即 DefaultSqlSession对象),其中sqlSessionFactory引用指向的就是DefaultSqlSessionFactory对像

// 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();

查看 DefaultSqlSessionFactory 中的 openSession() 方法源码,如下:

  @Override
  public SqlSession openSession() {
   
   
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

发现调用了openSessionFromDataSource方法,继续跟踪

  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);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
   
   
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
   
   
      ErrorContext.instance().reset();
    }
  }

我们终于看到了真正创建SqlSession的方法,该方法调用了 DefaultSqlSession 的构造方法,并直接传入DefaultSqlSessionFactory 的 configuration,到此我们就知道了,DefaultSqlSession 中的 Configuration 属性就是记录着 mybatis-config.xml 的信息,其中包含着 数据源信息 和 Mapper 映射文件地址等

executor

从上面我们知道,创建 DefaultSqlSession 的方法是 DefaultSqlSessionFactory 对象中的 openSessionFromDataSource方法,源码如下:

  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);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
   
   
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
   
   
      ErrorContext.instance().reset();
    }
  }

我们发现在调用 DefaultSqlSession 构造方法时,不仅传入了 configuration 对象,还传入了executor对象,并且是通过 configuration 的 newExecutor 方法获得,查看 newExecutor 方法源码

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
   
   
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
   
   
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
   
   
      executor = new ReuseExecutor(this, transaction);
    } else {
   
   
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
   
   
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

大概上可以看出,该方法是通过 executorType 参数来构造不同类型的构造器,查看ExecutorType源码,发现是个枚举类

public enum ExecutorType {
   
   
  SIMPLE, REUSE, BATCH
}

其中的枚举值的含义如下:

  • SIMPLE:简单执行器,用于执行简单的查询操作,不支持事务的提交和回滚。
  • REUSE:重用执行器,用于执行重复的查询操作,可以重用缓存的查询结果,提高性能。
  • BATCH:批处理执行器,用于执行批量的数据操作,如插入、更新和删除操作。它可以一次执行多个SQL语句,并支持事务的提交和回滚。

当我们调用 DefaultSqlSessionFactory 的无参 openSession 方法时,而openSession 方法又调用openSessionFromDataSource方法,并传入的参数configuration.getDefaultExecutorType(),我们不断跟踪.getDefaultExecutorType方法,发现最后返回的是ExecutorType. SIMPLE ,而 openSessionFromDataSource 又把这个参数传给了newExecutor 方法,因此此时的executor是简单执行器对象,创建的是 SimpleExecutor 对象

我们查看 SimpleExecutor 类的源码发现,其继承了 BaseExecutor,如下 ,实际上每个Executor都继承了BaseExecutor

public class SimpleExecutor extends BaseExecutor

查看 BaseExecutor 源码(源码太多不加入文章),发现其实现了Executor接口,Executor接口中的方法几乎覆盖了对数据库的所有操作,如下,包括事务的操作和增删改查的操作

public interface Executor {
   
   

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

因此SqlSession依靠Executor属性就能完成所有的SQL操作

最后看 SqlSession 是如何生成 Mapper

查看 DefaultSqlSession 中的 getMapper方法 源码

  @Override
  public <T> T getMapper(Class<T> type) {
   
   
    return configuration.getMapper(type, this);
  }

传入了Mapper接口的类对象,以及 DefaultSqlSession 本身

追踪 getMapper 方法,最后来到了 MapperRegistry 类的 getMapper 方法

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   
   
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
   
   
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
   
   
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
   
   
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

该方法的大致过程就是 通过动态代理生成了 Mapper 实例的代理对象,并且这个代理对象还“整合”进了sqlSession对象

当调用代理对象的Mapper接口方法时,代理对象将拦截这个方法调用,并获取对应的方法信息(参数返回值等),然后交由 sqlSession对象 对象中的 Executor对象,执行真正的SQL操作,而Executor对象 在执行SQL时需要用到的信息就来之DefaultSqlSession对象中的configuration属性(包括数据源信息和Mapper映射文件信息等)

目录
相关文章
|
7月前
|
SQL Java 数据库连接
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
|
3月前
|
SQL XML Java
MyBatis Mapper中使用limit参数的查询问题
总结而言,MyBatis中使用 `limit`参数的查询可以高度定制并且灵活,基于方法签名和XML映射文件的组合来达成多样化的查询需求。通过参数化查询和动态SQL,MyBatis可以有效地处理各种复杂情境下的数据库操作,并且将SQL语句的维护与业务代码的编写相分离,提升代码的可维护性和可阅读性。
337 13
|
7月前
|
SQL Java 数据库连接
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
|
7月前
|
人工智能 Java 数据库连接
MyBatis Plus 使用 Service 接口进行增删改查
本文介绍了基于 MyBatis-Plus 的数据库操作流程,包括配置、实体类、Service 层及 Mapper 层的创建。通过在 `application.yml` 中配置 SQL 日志打印,确保调试便利。示例中新建了 `UserTableEntity` 实体类映射 `sys_user` 表,并构建了 `UserService` 和 `UserServiceImpl` 处理业务逻辑,同时定义了 `UserTableMapper` 进行数据交互。测试部分展示了查询、插入、删除和更新的操作方法及输出结果,帮助开发者快速上手 MyBatis-Plus 数据持久化框架。
488 0
|
9月前
|
SQL Java 数据库连接
【潜意识Java】深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
1114 1
|
11月前
|
SQL Java 数据库连接
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
|
12月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
325 10
|
4月前
|
Java 数据库连接 数据库
Spring boot 使用mybatis generator 自动生成代码插件
本文介绍了在Spring Boot项目中使用MyBatis Generator插件自动生成代码的详细步骤。首先创建一个新的Spring Boot项目,接着引入MyBatis Generator插件并配置`pom.xml`文件。然后删除默认的`application.properties`文件,创建`application.yml`进行相关配置,如设置Mapper路径和实体类包名。重点在于配置`generatorConfig.xml`文件,包括数据库驱动、连接信息、生成模型、映射文件及DAO的包名和位置。最后通过IDE配置运行插件生成代码,并在主类添加`@MapperScan`注解完成整合
637 1
Spring boot 使用mybatis generator 自动生成代码插件
|
7月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
552 0
|
9月前
|
前端开发 Java 数据库连接
Java后端开发-使用springboot进行Mybatis连接数据库步骤
本文介绍了使用Java和IDEA进行数据库操作的详细步骤,涵盖从数据库准备到测试类编写及运行的全过程。主要内容包括: 1. **数据库准备**:创建数据库和表。 2. **查询数据库**:验证数据库是否可用。 3. **IDEA代码配置**:构建实体类并配置数据库连接。 4. **测试类编写**:编写并运行测试类以确保一切正常。
325 2