Mybais之MapperProxy

本文解析了MyBatis中的动态代理机制,介绍了MapperProxyFactory如何为Mapper接口创建代理类,以及SqlSessionTemplate如何实现线程安全的SqlSession管理。
部署运行你感兴趣的模型镜像

Mybatis中实现了为数据访问层接口动态创建代理,简化了代码书写方式。我们可以不必手动实现接口了,因为Mybatis在运行时帮我们实现了。

MapperProxyFactory为创建动态代理类实例代码:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

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

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

MapperProxy是实现了InvocationHandler接口的handler,在创建代理实例时Mapper接口类和MapperProxy作为参数传入,也就是创建了Mapper接口的代理甙类,当调用Mapper接口的方法时,实际上执行了invoke方法。

class MapperProxy implements InvocationHandler, Serializable {
    ...
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  ...
}

在MapperRegistry中注册动态代理类的工厂实例,mapper接口作为参数新建一个MapperProxyFactory实例

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

获取代理类实例,mapper接口和sqlSession做参数,在Spring中实际上sqlSession是SqlSessionTemplate实例,SqlSessionTemplate一个singleton

  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);
    }
  }
  ```
此处可能有个疑惑,为什么SqlSessionTemplate作为一个singleton传入的?
Mybatis中的SqlSession是非线程安全的,每个线程应该有独立的SqlSession实例。所以SqlSession应该是限制在请求范围。使用完毕需要执行关闭动作。
```java
SqlSession session = sqlSessionFactory.openSession();
try {
  // do work
} finally {
  session.close();
}




<div class="se-preview-section-delimiter"></div>

解开SqlSessionTemplate的秘密

通过查看源码,SqlSessionTemplate实现了SqlSession,并创建了SqlSession的动态代理类。

public class SqlSessionTemplate implements SqlSession, DisposableBean {
  ...
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
  ...




<div class="se-preview-section-delimiter"></div>

SqlSessionTemplate实现SqlSession接口方法,并委托给动态代理类执行,即通过回调SqlSessionInterceptor的invoke方法,

   private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

而在invoke方法中使用了SqlSession的另一个实现DefaultSqlSession来执行真正的数据库查询操作,此处也证明了JDK的动态代理是接口的代理而非类的代理。
SqlSession使用ThreadLocal<Map<Object, Object>> resources保存,最终SqlSession是每线程一个实例。也消除了为什么SqlSessionTemplate是一个Singleton的疑惑。

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

<think>嗯,用户想优化MyBatis的查询时间。这个问题需要一步步来分析。首先,我得想想MyBatis查询慢的可能原因。可能涉及SQL语句本身、数据库配置、MyBatis的使用方式,或者连接池设置等。 首先,SQL优化是关键。用户可能需要检查生成的SQL是否有不必要的JOIN或者SELECT *,这样会导致查询数据量大。然后,索引有没有合理使用?比如,经常查询的字段是否加了索引,联合索引是否匹配查询条件。还有,分页查询如果数据量大,LIMIT OFFSET效率低,可以考虑用游标分页,或者优化分页语句,比如用id范围查询。 接下来是MyBatis的配置和用法。二级缓存如果开启的话,可能会提升性能,但要注意数据一致性。是否启用了懒加载,避免一次加载过多关联数据。还有,动态SQL如果太复杂,可能会影响解析速度,需要简化。 然后是数据库连接池的配置。比如最大连接数是否合理,太小会导致等待,太大会占用资源。连接超时时间和空闲连接的回收策略也需要调整,避免资源浪费。 应用层面,批量操作是否用到了,比如BatchExecutor来批量插入或更新。还有,结果集处理,如果返回大量数据,可能会导致内存问题,可以用分页或者流式查询。 数据库本身的优化也不能忽视。表结构设计是否合理,有没有冗余字段。执行计划分析,看是否有全表扫描,可以通过EXPLAIN来查看。服务器硬件资源,比如CPU、内存、磁盘IO是否足够,或者有瓶颈。 监控和诊断工具也很重要。比如用Druid监控SQL执行时间,慢查询日志定位问题,或者用VisualVM分析应用性能。 最后,框架和版本的问题。MyBatis的版本是否过旧,有性能改进的新版本吗?插件或拦截器是否有性能问题,需要检查。 总结下来,优化需要从SQL、配置、连接池、应用代码、数据库结构、监控等多个方面入手,逐步排查和调整。可能还需要具体场景具体分析,比如数据量大小、查询频率等因素都会影响优化策略的选择。</think>好的,我将从多个层面分步骤介绍如何优化MyBatis查询时间,以下是具体建议: --- ### 一、SQL语句优化(核心方向) ```sql /* 低效查询示例 */ SELECT * FROM user WHERE create_time BETWEEN '2020-01-01' AND '2023-12-31' /* 优化后示例 */ SELECT id, name FROM user WHERE create_time >= '2023-01-01' AND create_time < '2024-01-01' AND status = 1 ``` 1. **避免全表扫描** - 使用`EXPLAIN`分析执行计划 - 对`WHERE`条件字段添加索引,特别是范围查询字段 2. **分页优化** 传统分页: ```sql SELECT * FROM table LIMIT 1000000, 20 -- 效率低下 ``` 优化方案: ```sql SELECT * FROM table WHERE id > 1000000 LIMIT 20 -- 基于有序主键 ``` --- ### 二、MyBatis配置优化 ```xml <!-- mybatis-config.xml --> <settings> <!-- 关闭延迟加载的侵入性检查 --> <setting name="lazyLoadTriggerMethods" value=""/> <!-- 启用二级缓存 --> <setting name="cacheEnabled" value="true"/> <!-- 优化默认执行器类型 --> <setting name="defaultExecutorType" value="BATCH"/> </settings> ``` --- ### 三、连接池调参(以HikariCP为例) ```properties # application.properties spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.idle-timeout=30000 spring.datasource.hikari.connection-timeout=3000 ``` --- ### 四、结果集处理优化 ```java // 使用ResultHandler流式处理 @Select("SELECT * FROM large_table") void fetchLargeData(ResultHandler<User> handler); // 配合MyBatis的@Options注解 @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 100) ``` --- ### 五、缓存策略选择 ```xml <!-- Mapper XML --> <cache eviction="LRU" flushInterval="60000" size="1024"/> <!-- 关联查询指定useCache --> <select id="selectWithAssoc" resultMap="detailedResult" useCache="true"> ``` --- ### 六、监控分析工具 1. **慢查询日志** MySQL配置: ```ini [mysqld] slow_query_log = 1 long_query_time = 1 ``` 2. **Arthas诊断** ```bash trace com.example.mapper.* select* ``` --- ### 七、特殊场景优化 **批量插入优化**: ```xml <insert id="batchInsert"> INSERT INTO table (col1, col2) VALUES <foreach collection="list" item="item" separator=","> (#{item.val1}, #{item.val2}) </foreach> </insert> ``` --- ### 优化效果验证 1. 使用`System.nanoTime()`记录执行时间 2. 对比优化前后的JVM内存变化(VisualVM) 3. 监控数据库QPS和TPS指标 建议通过`EXPLAIN ANALYZE`获取实际执行计划,优先处理`type=ALL`和`rows`值过大的查询。优化需要结合具体业务场景,建议采用渐进式调优策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值