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

本文详细解释了如何通过SqlSession获取UserMapper对象,追踪了SqlSession、Configuration、Executor和Mapper的生成过程,重点介绍了Configuration如何从配置文件中获取信息并构建SqlSession,以及Executor在执行SQL操作中的关键作用。

书接上文 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映射文件信息等)

<think>我们之前讨论过MyBatis-Plus的代码生成不是通过Byte Buddy,而是通过模板引擎生成源代码。现在的问题是:Byte Buddy能否用于增强MyBatis-Plus生成Mapper接口? 首先,我们需要明确MyBatis-Plus生成Mapper接口是什么。通常,这些接口是普通的Java接口,例如: public interface UserMapper extends BaseMapper<User> { ... } 在MyBatis中,这些接口的实现是由MyBatis框架在运行时通过动态代理生成的(默认使用JDK动态代理或CGLIB)。而Byte Buddy是一个字节码操作库,可以在运行时动态生成或修改类。 因此,我们可以使用Byte Buddy来增强这些Mapper接口吗?答案是肯定的,但需要注意以下几点: 1. **增强时机**:由于Mapper接口的实现是由MyBatis在运行时生成的代理对象,我们可以在MyBatis创建代理对象之后,使用Byte Buddy对这些代理对象进行增强。或者,我们也可以尝试在MyBatis生成代理之前,对接口本身进行增强(但接口增强通常比较少见,因为接口不能包含实现)。 2. **增强方式**: - **增强接口实现(代理对象)**:由于MyBatis已经为Mapper接口生成了代理对象,我们可以使用Byte Buddy创建一个新的代理,包装MyBatis的代理对象,从而添加额外的逻辑(例如日志、性能监控等)。 - **修改MyBatis的代理生成过程**:我们可以通过自定义MyBatis的代理生成机制,使用Byte Buddy来生成代理类,而不是使用默认的JDK代理或CGLIB。这需要深入MyBatis的扩展点。 3. **具体示例**:假设我们想在调用Mapper方法前后添加日志: ```java // 使用Byte Buddy创建一个包装器 Mapper originalMapper = sqlSession.getMapper(Mapper.class); // 获取MyBatis生成的代理 Mapper enhancedMapper = new ByteBuddy() .subclass(Mapper.class) // 这里注意:由于原始的是接口,我们需要代理实现 .method(ElementMatchers.any()) .intercept(InvocationHandlerAdapter.of((proxy, method, args) -> { System.out.println("Before method: " + method.getName()); Object result = method.invoke(originalMapper, args); System.out.println("After method: " + method.getName()); return result; })) .make() .load(Mapper.class.getClassLoader()) .getLoaded() .newInstance(); ``` 4. **注意事项**: - 性能:Byte Buddy生成代理通常很快,但额外的代理层会增加调用栈深度,可能略微影响性能。 - 兼容性:需要确保Byte Buddy生成的代理与MyBatis的代理机制兼容,避免冲突。 结论:Byte Buddy可以用于增强MyBatis-Plus生成Mapper接口,主要通过创建额外的代理层来实现。但需注意增强的层次和性能影响。 现在,根据规则,我们需要在回答后提出3-5个相关问题,并以“§§相关问题§§”开头,问题之间用数字和点分隔。 注意:问题应该围绕当前问题和回答内容展开,具有连贯性和深度。 </think> 是的,Byte Buddy**可以**用于增强MyBatis-Plus生成Mapper接口,但需要特定技术手段实现。具体原理和方式如下: ### 实现原理 1. **动态代理机制**: MyBatisMapper接口在运行时通过JDK动态代理或CGLIB生成实现类。Byte Buddy可介入此过程,创建更高阶的代理对象。 2. **字节码注入**: 通过Byte Buddy的`AgentBuilder`或`ClassFileTransformer`,在类加载时修改Mapper实现类的字节码,注入自定义逻辑(如SQL监控、缓存切面等)。 ### 操作示例(添加SQL执行日志) ```java // 使用Byte Buddy创建Mapper代理增强 new ByteBuddy() .subclass(DefaultSqlSessionFactory.class) // 目标类 .method(named("openSession")) .intercept(MethodDelegation.to(SqlInterceptor.class)) // 委托给拦截器 .make() .load(getClass().getClassLoader()) .getLoaded(); // 拦截器实现 public class SqlInterceptor { @RuntimeType public static Object intercept(@SuperCall Callable<?> zuper) throws Exception { long start = System.currentTimeMillis(); Object result = zuper.call(); // 执行原方法 System.out.println("SQL执行耗时: " + (System.currentTimeMillis()-start) + "ms"); return result; } } ``` ### 适用场景 1. **性能监控**:注入SQL执行时间统计 2. **动态过滤**:根据条件自动追加WHERE子句 3. **安全审计**:敏感操作(如批量删除)拦截 4. **故障注入**:测试环境模拟数据库异常 ### 注意事项 - **代理层级**:Byte Buddy代理需在MyBatis代理之上,避免破坏原生逻辑 - **类加载顺序**:需通过Java Agent或Spring LoadTimeWeaver确保优先加载 - **事务兼容性**:增强逻辑需与`@Transactional`等注解协同工作 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值