Mybatis 3.x 源码分析--查询操作

MyBatis流式查询实战:解决大数据查询OOM问题 最近在做一个数据报表导出的功能,业务方要求能导出近一年的订单明细。开发时图省事,直接写了个 SELECT * FROM orders WHERE create_time BETWEEN ... ,然后在 Service 层用 List<Order> 接收。本地测试时数据量小,一切正常。结果上线后,运营在后台一点“导出”,服务内存直接飙到 90%+,紧接着就是一连串的 OutOfMemoryError: Java heap space 告警,服务差点挂掉。 事后复盘,一张大表几百万条记录,用 Li 阅读详情

环境准备

  1. mybatis-configuration.xml

最新的完整mybatis每个配置属性含义可参考http://www.mybatis.org/mybatis-3/zh/configuration.html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
        // mybatis内置提供JDBC和MANAGED两种事务管理方式,
        // 前者主要用于简单JDBC模式,后者主要用于容器管理事务,
        //一般使用JDBC事务管理方式
        // 使用了别名
            <transactionManager type="JDBC"/>
            // mybatis内置提供JNDI、POOLED、UNPOOLED三种数据源工厂,一般情况下使用POOLED数据源。
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true&amp;characterEncoding=UTF-8&amp;allowMultiQueries=true&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="1234"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="ConfigMapper.xml"/>
    </mappers>
</configuration>
  1. ConfigMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxx.springboot.demo.mapper.ConfigMapper">
  <resultMap id="BaseResultMap" type="com.xxx.springboot.demo.entity.ConfigBO">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="ckey" jdbcType="VARCHAR" property="ckey" />
    <result column="cvalue" jdbcType="VARCHAR" property="cvalue" />
    <result column="application" jdbcType="VARCHAR" property="application" />
    <result column="profile" jdbcType="VARCHAR" property="profile" />
    <result column="label" jdbcType="VARCHAR" property="label" />
    <result column="type" jdbcType="INTEGER" property="type" />
  </resultMap>

  <sql id="Base_Column_List">
    id, ckey, cvalue, `application`, profile, `label`, `type`
  </sql>

  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from config
    where id = #{id,jdbcType=INTEGER}
  </select>
</mapper>

3.java 代码

public interface ConfigMapper {
    ConfigBO selectByPrimaryKey(Integer id);
}
    public static void main(String[] args) {
        String resource = "Configuration.xml";
        Reader reader;
        try {
            reader = Resources.getResourceAsReader(resource);
            SqlSessionFactory sqlSession = new SqlSessionFactoryBuilder().build(reader);
           // SqlSession就是jdbc连接的代表,openSession()就是获取jdbc连接
            SqlSession session = sqlSession.openSession();
            try {
                // 调试入口
                // 在session上可执行各种CRUD操作
                ConfigBO config = (ConfigBO) session.selectOne("com.xxx.springboot.demo.mapper.ConfigMapper.selectByPrimaryKey", 1);
            } finally {
                session.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

开始

1.session.selectOne

此处session的实际类型为:org.apache.ibatis.session.defaults.DefaultSqlSession

  public <T> T selectOne(String statement, Object parameter) {
    // selectOne方法的底层是通过selectList实现的
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

2. DefaultSqlSession#selectList

  public <E> List<E> selectList(String statement, Object parameter) {
    // 初始化分页参数
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

 // selectList的重载方法
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 从Mybatis的configuration中获取MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

(1)selectList引入RowBounds类,作用:分页功能的封装
(2)什么是MappedStatement?
它在ConfigMapper.xml长这样:

  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from config
    where id = #{id,jdbcType=INTEGER}
  </select>

也就是它代表了sql语句的一个映射关系,从sql配置文件到java实体类的映射。它保存在一个Map中,那么什么是它的key呢?跟踪源码我们可以看到定义:

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

它的key 就是参数statement的值,即:(key = 全类名+方法名)
com.xxx.springboot.demo.mapper.ConfigMapper.selectByPrimaryKey

思考下列问题:
2.1 MappedStatement对象是怎么实例化的?
2.2 上述selectByPrimaryKey方法可以有重载方法吗?

(3)wrapCollection(parameter) 作用是什么?如果参数是集合类,就包装成Map对象。

  private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("array", object);
      return map;
    }
    return object;
  }

很自然,我们要思考为什么这么做?如果大家使用过<foreach>标签,应该知道:
<foreach collection=“list” item="" open="" close="" separator="" >
上面的list就是这里map的key,当然也可以指定为“collection”

StrictMap对HashMap的装饰在于增加了put时防重复的处理,get时取不到值时候的异常处理,这样核心应用层就不需要额外关心各种对象异常处理,简化应用层逻辑。

(4)引入了参数Executor.NO_RESULT_HANDLER
Executor类中定义了一个空的结果处理器:
ResultHandler NO_RESULT_HANDLER = null;

(5)executor.query方法中executor具体类型是什么?
它是在DefaultSqlSession实例化时赋值的,这里实际类型为:CachingExecutor

3.CachingExecutor#query

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

  // 上面query的一个重载方法,增加了MappedStatement级别的缓存功能
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

前面我们知道MappedStatement 是一个sql语句的映射,它包括了sql语句、参数类型、结果集类型等,那么sql语句在MappedStatement 是怎样保存的呢?答案就是SqlSource类,它有2个实现类DynamicSqlSource和RawSqlSource,在解析mapper文件时会根据当前sql语句是动态与否实例化成相应的类。在本例中它被实例成为RawSqlSource。
(1)BoundSql类:封装真实sql字符串和相关sql参数
从sqlSource获得的实际SQL字符串,SQL可能有SQL占位符“?”和一个参数映射的列表(有序的),其中包含每个参数的附加信息。
(2)实例化CacheKey对象,用于Mybatis提供的缓存机制
(3)CachingExecutor#query在MappedStatement级别的实现了缓存功能
Cache cache = ms.getCache();
思考下列问题:
3.1 怎么设置MappedStatement级别的缓存?
在mapper.xml中使用<cache>标签开启
(4)CachingExecutor将执行sql查询的操作委托给SimpleExecutor,就是这里的delegate对象,而CachingExecutor只实现缓存功能。

4.BaseExecutor#query

SimpleExecutor的父类BaseExecutor的query实现了sqlSession级别的缓存,具体实现类为PerpetualCache

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // sqlSession级别的缓存
      // resultHandler赋值,前面有提到
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 存储过程相关
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

  // 设置sqlSession级别的缓存
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

5. SimpleExecutor#doQuery

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

(1) 实例化语句处理器StatementHandler
相关代码如下:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // StatementType 可在mapper.xml中指定,默认为PREPARED
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

RoutingStatementHandler 是对上面3种语句处理器的一个封装。这里真正实例化的语句处理器是PreparedStatementHandler

(2)分析SimpleExecutor#prepareStatement方法

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 这里从transaction中获取connection,然后返回代理过的connection
    // connection的具体类型为 org.apache.ibatis.logging.jdbc.ConnectionLogger
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

流程1 BaseStatementHandler#prepare

主要工作就是把我们在mapper.xml中定义的属性转换为JDBC标准的调用

这里handler.prepare方法的具体逻辑委托给BaseStatementHandler#prepare实现:

  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // instantiateStatement由子类实现
      statement = instantiateStatement(connection);
      // 设置查询超时时间
      setStatementTimeout(statement, transactionTimeout);
      // 设置每次查询抓取的最大size
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

PreparedStatementHandler#instantiateStatement

  // 返回值类型为java.sql.PreparedStatement
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    // 处理Jdbc3KeyGenerator,因为它代表的是自增,另外一个是SelectKeyGenerator用于不支持自增的情况
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

connection的类型为ConnectionLogger,它代理了JDBC4Connection对象
ConnectionLogger#invoke代码:

  public Object invoke(Object proxy, Method method, Object[] params)
      throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }    
      if ("prepareStatement".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
        }
        // stmt 返回类型为 JDBC42PreparedStatement         
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        // PreparedStatementLogger代理了JDBC42PreparedStatement并返回
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
        return stmt;
      } else if ("prepareCall".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
        }        
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
        return stmt;
      } else if ("createStatement".equals(method.getName())) {
        Statement stmt = (Statement) method.invoke(connection, params);
        stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
        return stmt;
      } else {
        return method.invoke(connection, params);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

实例化PreparedStatementLogger#newInstance对象时,给它的属性
private final PreparedStatement statement;
赋值为 statement = JDBC42PreparedStatement对象。

  public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
    InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
    ClassLoader cl = PreparedStatement.class.getClassLoader();
    return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
  }

  private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {
    super(statementLog, queryStack);
    this.statement = stmt;
  }

流程2 PreparedStatementHandler#parameterize

主要功能:将mapper.xml中定义的参数转换为JDBC参数

语句处理器持有参数处理器ResultSetHandler和结果集处理器ParameterHandler
这里parameterHandler的实现类为DefaultParameterHandler,PreparedStatementHandler.parameterize将具体实现委托给了DefaultParameterHandler.setParameters()方法

  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
DefaultParameterHandler#setParameters

我们在mapper中定义的所有ParameterType、ParameterMap、内嵌参数映射等在最后都在这里被作为ParameterMapping转换为JDBC参数

  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 只处理入参类型的
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          // 1.先判断是不是属于语句的AdditionalParameter
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          // 2.参数是不是null
          } else if (parameterObject == null) {
            value = null;
          // 3.判断是不是属于注册类型
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
          // 4. 估计参数是object或者map,借助于MetaObject获取属性值
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
          // jdbc下标从1开始,由具体的类型处理器进行参数的设置, 对于每个jdbcType, mybatis都提供了一个对应的Handler,
          //具体可参考TypeHandlerRegistry详解, 其内部调用的是PrepareStatement.setXXX进行设置。
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

(1)ParameterMapping 对象:
ParameterMapping{property=‘id’, mode=IN, javaType=class java.lang.Integer, jdbcType=INTEGER, numericScale=null, resultMapId=‘null’, jdbcTypeName=‘null’, expression=‘null’}
(2)TypeHandlerRegistry对象
提供了常见的Jdbc对象的java处理器类型,参考源码

6 PreparedStatementHandler#query

上面第5步中SimpleExecutor#doQuery,将查询操作委托给RoutingStatementHandler#query执行。而RoutingStatementHandler又委托给PreparedStatementHandler#query执行

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 查询语句的真正执行动作
    ps.execute();
    // 查询结果集的封装
    return resultSetHandler.<E> handleResultSets(ps);
  }

7 configuration文件解析核心代码

org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

(1)入参XNode root对象信息如下:

<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="ConfigMapper.xml"/>
</mappers>
</configuration>

(2) <mappers>标签的解析代码

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

8 资料

参考资料:
[1] https://www.cnblogs.com/zhjh256/p/8512392.html
[2] https://blog.youkuaiyun.com/isea533/article/details/44002219
[3] https://blog.youkuaiyun.com/qq_35525955/article/details/80890926

MyBatis 核心组件源码分析 MyBatis是Java中广泛使用的持久层框架,以其灵活的SQL映射和强大的扩展性受到开发者青睐。本文通过源码分析,深入探讨了MyBatis的四个核心组件:SqlSessionFactory、SqlSession、Executor和MappedStatement。SqlSessionFactory作为入口,负责创建SqlSession,其构建过程涉及配置文件的解析和Configuration对象的填充。SqlSession是数据库操作的入口,通过Executor执行具体的SQL操作。Executor作为SQ 阅读详情

相关推荐

mybatis-3.4.4(包含源码,中文帮助文档PDF,JAR包)[超清晰版]

文档是Mybatis在github上的官方中文教程文档,很详细的讲解的mybatis的用法。pdf文档是文字版可复制,是我通过工具将官方的中文文档转化为pdf的,如果好用的话,希望大家给个好的评论(好资源就是要分享)

selectByExample,selectByPrimaryKey和select的区别

通用mapper中selectByExample,selectByPrimaryKey和select的区别 1  selectByExample几乎可以解决所有的查询,select和selectByPrimary是简化的针对特定情况的解决方法 2  以主键为条件进行查询, selectByExample的代码如下: Example example = new Example(Sku.class); Example.Criteria criteria = example.createCriteria(); c

weixin_48720405的博客 1874

一本小小的MyBatis源码分析书.rar

一本小小的MyBatis源码分析书,内容详细介绍MyBatis源码!对于初中级java开发工程师是必备的!

通用mapper的查询,selectByPrimaryKey、select、selectByExample等

最近有在用到mybatis的通用mapper的查询,既然接触了索性记录总结一下,以便帮到后来人。 一. 首先,放上mybatis 通用mapper的接口代码Mapper.class: package tk.mybatis.mapper.common; import tk.mybatis.mapper.annotation.RegisterMapper; @RegisterMapper public interface Mapper<T> extends BaseMapper<T>

初心不改 1万+

Mybatis源码解析

mybatis源码解析视频的讲解是由某知名老师讲解。包你学完可以完全理解mybatis框架实现原理等。

mybatis 3.x源码深度解析与最佳实践.html

mybatis 3.x源码深度解析与最佳实践.html

Springboot 整合mybatis可能出现的小问题之selectByPrimaryKey(id)查询不出数据

实体类 @Data public class AppGroupUser extends AppBaseEntity { private static final long serialVersionUID = 1L; /** * 群组用户关系ID ...

chenwan8029的博客 948

Mybatis源码分析.pdf

Mybatis源码分析主要分三部分: 1.使用案例讲解 2.Mybatis中用的所有设计模式讲解 3.源码分析

Mybatis源码分析总结

1111

qq_45111347的博客 630

JavaFx之集成FontAwesomeFX与JFoenix(十五)

本文详细介绍了如何在JavaFx项目中同时集成JFoenix和FontAwesomeFX,以创建现代化、美观的桌面应用界面。通过Maven依赖配置、动态代码创建和FXML静态声明两种方式,演示了如何将JFoenix的Material Design控件与FontAwesomeFX的丰富矢量图标完美结合,并提供了实战案例展示如何构建一个功能完善的现代化工具栏。

pepsi的博客 438

02.1 知识图谱工具 Protege的下载安装与使用

Protege的下载安装与使用 本文是王昊奋老师《知识图谱》系列笔记第2.1篇。 文章目录Protege的下载安装与使用0 相关介绍0.0 Protege相关介绍0.1 Protégé的特点0.2 Protégé的用途1 Protege的下载1.1 准备工作:安装配置JAVA环境1.2 官网下载2 Protege的安装2.1 直接解压缩2.2 双击运行3 Protege的使用实例-创建类...

和而不流 7万+

JavaEE框架

针对企业开发的主流框架进行讲解,注重理论和实践的结合,课程内容深入源码,讲解深入浅出,每个技术点都配备具有典型性和实用性的案例及代码。使学生具备开发企业级项目的能力,同时针对框架新特性也有讲授,通过这些新功能实用性强、易用性高,可大幅降低 JavaEE 开发的难度,同时有效提升应用开发的优雅性。

DDPM代码详细解读(2):Unet结构、正向和逆向过程、IS和FID测试、EMA优化

Diffusion Models专栏文章汇总:入门与实战 前言:大部分DDPM相关的论文代码都是基于《Denoising Diffusion Probabilistic Models》和《Diffusion Models Beat GANs on Image Synthesis》贡献代码基础上小改动的。官方的DDPM是tensorflow TPU版本,暂时没有GPU的版本。上一篇文章介绍了数据集加载,超参数的含义、关键参数的计算方法等,这一篇重点解读一下网络结构。 目录 EMA优化 训练目标.

沉迷单车的追风少年 1万+

Mybatis 3.X框架-Mybatis源码分析

Mybatis源码分析核心对象的生命周期工作流程及源码解读核心流程调用链 核心对象的生命周期 核心对象 生命周期 描述 SqlSessionFactoryBuilder 方法局部 只需要使用一次,用于构建SqlSessionFactory SqlSessionFactory 应用级别 全局单例,SqlSession工厂 SqlSession 会话级别 每次访问DB都需要使用...

ieen_csdn的博客 314

Dify 第 3 篇 Dify 入门示例 - 聊天助手

本文介绍了如何使用Dify平台快速搭建一个聊天助手。首先通过创建空白应用并选择"聊天助手"模板完成基础设置,然后安装配置通义千问等大语言模型(LLM)并设置API密钥。进阶部分演示了如何创建支持编程语言选择的代码解释器,包括添加提示词约束、插入变量、设置开场白等功能。最后展示了运行效果:用户可选择编程语言后输入代码,系统会给出相应解释。文章还提到应用支持发布共享,并附有Dify官方文档参考链接。

甘蓝的专栏 865

DBC中如何实现报文传输类型属性的配置

AUTOSAR中CAN报文的传输类型主要有三种,周期型,事件型和混合型。周期型:按固定时间间隔自动发送,无需额外的触发条件。事件型:当信号值变化或满足内部条件时立即发送。混合型:默认按周期发送,但事件触发时可提前发送。

sunchengl的博客 307

mybatis3源码分析

1.原生JDBC查询 编写sql–》预编译sql–》参数设置–》执行sql–》结果封装 2.半自动框架 与Hibernate不同,Hibernate是全自动映射框架,目的是消除sql,但是消除sql的同时,Hibernate又失去了灵活性和sql优化空间,尽管提出了一个HQL概念,但是又增加了学习成本。所以,mybatis在这种背景下发展起来的,mybatis选择不封装sql编写操作(mapper.xml文件),封装其他JDBC操作,追求的是封装性,又不失sql灵活性。 3.mybatis前言 与所有框架

weixin_39296289的博客 411

蓝牙攻防实战指南:低功耗蓝牙

Bluetooth Low Energy(也称Bluetooth Smart)是经典蓝牙(Bluetooth Classic)的演进版本,设计目标是实现极低功耗,适用于无需频繁更换电池的设备,如智能手环、健康监测器、蓝牙标签等。Bluetooth Low Energy 是一项极具前景的无线通信技术,它以极低的功耗实现了短距离高效通信,广泛应用于智能设备和物联网领域。然而,其简化的协议结构和配对机制也带来了不小的安全挑战。

idlecloud0105的博客 683

SpringBoot——MyBatis-Plus源码分析及开发实践详解

  在上一家公司一直使用,它是对基于JPA的数据访问层的增强支持,底层使用框架,支持使用原生L或查询语言。是基于的,它把代码和分离,即在代码和之间增加了新的一层,从而间接操作,大家可能觉得这个学习成本比较高,但其实也还好,都用不到文件等。  而在新的公司,使用的是,面向基于的模式,复杂的需要通过写文件来操作可能更直观友好,当结合就原地起飞了。本文主要总结使用的一些实践经验及源码分析。  是的增强工具,可以简化开发,提高我们的开发效率。

Andya_net的博客 1864

月薪30k+的工业视觉,必备Java+YOLO技能(工业级落地全拆解)

30k+工业视觉岗的核心不是算法调参,而是用Java把YOLO落地到工业场景的能力——稳运行、能对接、可管理是高薪的核心壁垒;必备技能分五层:模型工业级适配、Java推理核心、SpringBoot后端、工业部署运维、场景适配,每层都要练到“能落地、能讲清、能避坑”;学习路径的核心是“实战驱动”:不要只学理论,从一个完整的工业项目(如锂电缺陷检测)入手,练完就能对标高薪岗。

2601_94871597的博客 526

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

MyBatis3.X源码分析(一二级缓存机制等)

1、MyBatis运行机制(原理) 2、MyBatis SqlSession是全局的吗? 3、一级缓存和二级缓存(缓存的装饰模式)源码原理分析 4、MyBatis 执行器原理分析? 5、服务集群后MyBatis存在怎样的问题?

邪恶八进制 17万+

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

MyBatis BLOB查询性能优化与原理剖析

在数据库操作中,BLOB字段处理是常见的性能瓶颈点。MyBatis作为流行的ORM框架,对BLOB字段有特殊处理机制,这会导致显著的性能差异。其原理在于结果集映射时,BLOB查询会启用EnhancedResultSetHandler进行逐行缓冲处理,增加了类型检查和内存开销。通过字段投影、分页优化和延迟加载等技术,可以显著提升查询效率。本文以MyBatis为例,深入分析BLOB处理的底层机制,并提供多种优化方案,帮助开发者规避常见的性能陷阱。

weixin_33721427的博客 461

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Mybatis查询过程源码分析--转载整理

文章目录一.SqlSession的创建(OpenSession)二.从Configration中获取接口对应代理类MapperProxy三.MapperProxy的执行1.先调用MapperProxy的invoke方法2.通过MapperMethod的execute方法判断执行sqlSession的哪个CRUD方法四.简述selectList1.获取MappedStatement2.Executo...

了凡的博客 240

MyBatis源码分析之核心处理层

我还为大家准备了一套体系化的架构师学习资料包以及BAT面试资料,供大家参考及学习已经将知识体系整理好(源码,笔记,PPT,学习视频)a工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**因此收集整理了一份《Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

2401_83641533的博客 883

3.MyBatis源码解析-CRUD执行流程--阿呆中二

CRUD执行流程MyBatisCRUD执行流程与我联系 MyBatis 本文是对mybatis 3.x源码深度解析与最佳实践学习的总结,包括XML文件解析流程、SqlSession构建流程、CRUD执行流程、MyBatis扩展点,以及针对面试题进行源码分析。 mybatis 3.x源码深度解析与最佳实践下载:https://download.csdn.net/download/u013425841/12816685 CRUD执行流程 MyBatis执行的基本流程大致可以分四个步骤: 使用Resources

拔起最后一根救命稻草 243
上一篇: springboot + camel + jdbc Component 入门
ThreadSnow
博客等级 码龄7年 1粉丝 4原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值