2024年MyBatis-底层源码解析-(详细),阿里技术专家深入讲解

架构学习资料

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

/**

  • 解析 <mapper url
    /
    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) {
    /
    *
  • 解析 <mapper class
    /
    Class<?> mapperInterface = Resources.classForName(mapperClass);
    configuration.addMapper(mapperInterface);
    } else {
    /
    *
  • 以下代码可以看出 url resource class 三个属性只能选择一个,否则就会报错
    */
    throw new BuilderException(“A mapper element may only specify a url, resource or class, but not more than one.”);
    }
    }
    }
    }
    }
    }

1.4.2:mapperParser.parse();解析

我的UserMapper.xml文件内容为:

<?xml version="1.0" encoding="UTF-8"?> select * from `user` where userId = #{userId}

UserMapper接口内容为:

package com.mapper;

import com.entity.User;
import org.apache.ibatis.annotations.Select;

public interface UserMapper {
@Select(“select * from user where userId = 2”)
User findById(int userId);
}

疑问?UserMapper.xml有<select id=“findById”,而在接口中的findById方法我又加了一个@Select注解;那么执行会选择哪一条Sql执行还是报错呢?

以UserMapper.xml为例子解析,可以看到 resource = mapper/UserMapper.xml
在这里插入图片描述

public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
/**

  • 解析 mapper.xml文件内容
    /
    configurationElement(parser.evalNode(“/mapper”));
    configuration.addLoadedResource(resource);
    /
    *
  • 解析 mapper.xml的
  • namespace指定的{UserMapper}接口的注解信息
    */
    bindMapperForNamespace();
    }

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}

1.4.3:configurationElement(parser.evalNode(“/mapper”));

解析 mapper.xml文件内容

public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
try {
/**

  • namespace属性
    /
    String namespace = context.getStringAttribute(“namespace”);
    if (namespace == null || namespace.equals(“”)) {
    /
    *
  • 不指定 namespace会报错哦 由此得知 namespace属性是必须指定的
    /
    throw new BuilderException(“Mapper’s namespace cannot be empty”);
    }
    builderAssistant.setCurrentNamespace(namespace);
    /
    *
  • 解析 cache-ref
    /
    cacheRefElement(context.evalNode(“cache-ref”));
    /
    *
  • 解析 cache
    /
    cacheElement(context.evalNode(“cache”));
    /
    *
  • 解析 parameterMap
    /
    parameterMapElement(context.evalNodes(“/mapper/parameterMap”));
    /
    *
  • 解析 resultMap
    /
    resultMapElements(context.evalNodes(“/mapper/resultMap”));
    /
    *
  • 解析 sql
    /
    sqlElement(context.evalNodes(“/mapper/sql”));
    /
    *
  • 解析 sql语句 select|insert|update|delete
  • 重点分析这里,这里的解析会关联到 mapper接口的执行方法 sql语句映射
    */
    buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));
    } catch (Exception e) {
    throw new BuilderException(“Error parsing Mapper XML. The XML location is '” + resource + "'. Cause: " + e, e);
    }
    }
    }

1.4.4:buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));

解析 sql语句 select|insert|update|delete;list参数内容是select|insert|update|delete的Sql语句
XMLStatementBuilder Sql语句的解析器
在这里插入图片描述

1.4.5:statementParser.parseStatementNode();解析Sql语句

builderAssistant.addMappedStatement,并不是添加一个mapper.xml文件隐射的实例,而是为每一个Sql语句创建一个实例

public class XMLStatementBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
public void parseStatementNode() {
/**

  • 此处省略一大推代码…
    /
    /
    *
  • select * from user where userId = #{userId}
  • 参数解析
  • id:标签指定的id = findById
  • sqlSource:Sql语句,Sql参数占位
  • statementType:sql执行类型 参考{@link StatementType}
  •              STATEMENT: 直接操作sql,不进行预编译  ${}
    
  •              PREPARED: 预处理,参数,进行预编译    #{}
    
  •              CALLABLE: 执行存储过程
    
  • sqlCommandType:sql语句类型 参考{@link SqlCommandType}
  •              UNKNOWN:未知,INSERT:新增,UPDATE:修改,DELETE:删除,SELECT:查询,FLUSH:刷新
    
  • 其他参数可查看官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
    */
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
    fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
    resultSetTypeEnum, flushCache, useCache, resultOrdered,
    keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
    }

1.4.6:builderAssistant.addMappedStatement();

创建一个MappedStatement实例添加到 Configuration.mappedStatements的Map集合中

public class XMLStatementBuilder extends BaseBuilder {
public MappedStatement addMappedStatement() {
/**

  • 此处省略一大推代码…
    /
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
    /
    *
  • 建造者模式
  • 用于设置 MappedStatement的属性
  • 此处省略一大推代码…
    */

/**

  • 设置参数入参类型 parameterType属性
  • select * from user where userId = #{userId}

*/
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}

/**

  • 创建一个 {@link MappedStatement} 实例
    */
    MappedStatement statement = statementBuilder.build();

/**

  • MappedStatement实例添加到 {@link #configuration.mappedStatements} Map集合中
  • MappedStatement 是对应一个Sql语句的实例对象
  • configuration.mappedStatements 存放所有的MappedStatement实例,后面会详细介绍
    */
    configuration.addMappedStatement(statement);
    return statement;
    }
    }

以上流程执行完后,又回到1.4.2:mapperParser.parse();解析,现在开始解析 namespace指定的接口的注解信息,并创建该接口的代理工厂对象,UserMapper接口。

在这里插入图片描述

1.4.7:bindMapperForNamespace();

开始解析接口注解,并添加一个MapperProxyFactory代理工厂的对象到configuration.mapperRegistry.knownMappers;key是Mapper接口

public class XMLMapperBuilder extends BaseBuilder {
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
/**

  • java 反射 Class.classForName
    /
    boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
    /
    *
  • 这里并没有抛出异常,说明 namespace 可以指定一个不存在的接口
    /
    //ignore, bound type is not required 忽略,绑定类型不是必需的
    }
    if (boundType != null) {
    if (!configuration.hasMapper(boundType)) {
    // Spring may not know the real resource name so we set a flag
    // to prevent loading again this resource from the mapper interface
    // look at MapperAnnotationBuilder#loadXmlResource
    configuration.addLoadedResource(“namespace:” + namespace);
    /
    *
  • 添加一个Mapper接口的代理工厂对象到configuration.mapperRegistry.knownMappers集合中
  • 参考 {@link Configuration#mapperRegistry},
  • {@link MapperRegistry#knownMappers}
    

*/
configuration.addMapper(boundType);
}
}
}
}
}

1.4.8:configuration.addMapper(boundType);

public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public void addMapper(Class type) {
/**

  • mapperRegistry = {@link MapperRegistry} mapper接口注册器,存放所有的mapper接口信息
    */
    mapperRegistry.addMapper(type);
    }
    }

mapperRegistry.addMapper(type);
为Mapper接口创建一个代理工厂,方便后期使用Mapper接口时创建代理类

解析mapper接口的注解信息

public class MapperRegistry {
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException(“Type " + type + " is already known to the MapperRegistry.”);
}
boolean loadCompleted = false;
try {
/**

  • {@link MapperProxyFactory} 代理接口工厂
    /
    knownMappers.put(type, new MapperProxyFactory<>(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.
    /
    *
  • {@link MapperAnnotationBuilder} mapper接口注解解析器
    /
    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    /
    开始解析 */
    parser.parse();
    loadCompleted = true;
    } finally {
    if (!loadCompleted) {
    knownMappers.remove(type);
    }
    }
    }
    }
    }

1.4.9 parser.parse(); MapperAnnotationBuilder.parse()

解析mapper接口的注解信息,parseStatement(method)主要在这个方法中完成

public class MapperAnnotationBuilder {
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
/**

  • 解析 @CacheNamespace 注解
    /
    parseCache();
    /
    *
  • 解析 CacheNamespaceRef 注解
    /
    parseCacheRef();
    Method[] methods = type.getMethods();
    for (Method method : methods) {
    try {
    // issue #237
    if (!method.isBridge()) {
    /
    *
  • 解析Sql相关注解 列如 @Select|@Update 之类的注解
  • 重点关注
    /
    parseStatement(method);
    }
    } catch (IncompleteElementException e) {
    configuration.addIncompleteMethod(new MethodResolver(this, method));
    }
    }
    }
    /
    *
  • 解析待定方法
    */
    parsePendingMethods();
    }
    }

1.5.0:parseStatement(method);

创建一个MappedStatement实例添加到 Configuration.mappedStatements的Map集合中

public class MapperAnnotationBuilder {
private final MapperBuilderAssistant assistant;
void parseStatement(Method method) {
/**

  • 此处省略一大推代码…
    */
    assistant.addMappedStatement(
    mappedStatementId,
    sqlSource,
    statementType,
    sqlCommandType,
    fetchSize,
    timeout,
    // ParameterMapID
    null,
    parameterTypeClass,
    resultMapId,
    getReturnType(method),
    resultSetType,
    flushCache,
    useCache,
    // TODO gcode issue #577
    false,
    keyGenerator,
    keyProperty,
    keyColumn,
    // DatabaseID
    null,
    languageDriver,
    // ResultSets
    options != null ? nullOrEmpty(options.resultSets()) : null);
    }
    }

1.5.1:assistant.addMappedStatement();

这里可以参考1.4.6:builderAssistant.addMappedStatement();一模一样的操作
都调用了configuration.addMappedStatement(statement);
在这里插入图片描述

这里重点分析Configuration.addMappedStatement(statement);在做什么操作,并且解决 1.4.2留下的疑点;UserMapper.xml和UserMapper接口都有findById的Sql语句定义

public class Configuration {
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
}

mappedStatements.put(ms.getId(), ms); 实际调用 Configuration.StrictMap.put()方法
Configuration.StrictMap是一个重写的HashMap,put方法会先校验key是否存在

public class Configuration {
/**

  • mappedStatements Sql语句的对象
  • Configuration.StrictMap 实现了HashMap
    */
    protected final Map<String, MappedStatement> mappedStatements = new Configuration.StrictMap(“Mapped Statements collection”)
    .conflictMessageProducer((savedValue, targetValue) ->
    ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

protected static class StrictMap extends HashMap<String, V> {
@Override
@SuppressWarnings(“unchecked”)
public V put(String key, V value) {
/**

  • key 是否存在 存在就抛出异常
  • 由此得知,方法映射Sql语句只能定义一个,要么在 mapper.xml中定义,要么就注解定义
    */
    if (containsKey(key)) {
    throw new IllegalArgumentException(name + " already contains value for " + key
  • (conflictMessageProducer == null ? “” : conflictMessageProducer.apply(super.get(key), value)));
    }
    if (key.contains(“.”)) {
    final String shortKey = getShortName(key);
    if (super.get(shortKey) == null) {
    super.put(shortKey, value);
    } else {
    super.put(shortKey, (V) new org.apache.ibatis.session.Configuration.StrictMap.Ambiguity(shortKey));
    }
    }
    return super.put(key, value);
    }
    }
    }

debug调试,key已经存在,就会报错。由此得知,方法映射Sql语句只能定义一个,要么在 mapper.xml中定义,要么就注解定义
在这里插入图片描述

到此,MyBatis的启动流程就走完了。

2:接下来就来看下是如何执行Sql查询的。

public class Main {
public static void main(String[] args) throws IOException {
String configName = “mybatis_config.xml”;
Reader reader = Resources.getResourceAsReader(configName);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

/**

  • 获取一个会话连接
    */
    SqlSession sqlSession = sqlSessionFactory.openSession();

/**

  • 拿到 UserMapper 的代理类
    */
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

/**

  • 执行Sql查询
    */
    User user = userMapper.findById(1);
    System.out.println(user);
    }
    }

输出结果:User{userId=1, username=‘张三’, sex=‘男’, age=12}

一行代码的查询,底层既然走了那么多流程;

流程图:

在这里插入图片描述

2.1:sqlSessionFactory.openSession();打开会话连接

调用DefaultSqlSessionFactory.openSessionFromDataSource();

public class DefaultSqlSessionFactory implements SqlSessionFactory {
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
/**

  • mybatis_config.xml配置的
  • 数据源
    */
    final Environment environment = configuration.getEnvironment();

/**

  • transactionManager 配置的事务管理器工厂 type=“JDBC” {@link JdbcTransactionFactory}
    *
  •    <transactionManager type="JDBC"></transactionManager>
    

*/
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

/**

  • 创建事务管理器,由于上面指定的事务管理器工厂是 {@link JdbcTransactionFactory}
  • 所以创建的事务管理器是 {@link JdbcTransaction}
  • @param level 事务隔离级别
  • @param autoCommit 是否自动提交事务
    */
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

/**

  • 创建Sql执行器
  • @param execType 创建执行器类型 defaultExecutorType如果不指定 默认就是 SIMPLE
  •             <settings>
    
  •                 <setting name="defaultExecutorType" value="SIMPLE"/>
    
  •             </settings>
    

*/
final Executor executor = configuration.newExecutor(tx, execType);

/**

  • 创建一个默认的 SqlSession实例
    */
    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();
    }
    }
    }

2.2:configuration.newExecutor(tx, execType);创建执行器

public class Configuration {
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);
    /
    *
  • 执行器会重用预处理语句(PreparedStatement)
    /
    } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
    } else {
    /
    *
  • 普通的执行器 也是默认的执行器
    /
    executor = new SimpleExecutor(this, transaction);
    }
    /
    *
  • 如果开启了二级缓存 cacheEnabled,创建一个CachingExecutor缓存执行器
  • cacheEnabled 默认为true
  • <setting name="cacheEnabled" value="true"/>
    

*/
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}

2.3:sqlSession.getMapper(UserMapper.class);

获取Mapper接口代理类实例

public class DefaultSqlSession implements SqlSession {
@Override
public T getMapper(Class type) {
/**

  • 用 Configuration 类的 getMapper方法
    */
    return configuration.getMapper(type, this);
    }
    }

public class Configuration {
public T getMapper(Class type, SqlSession sqlSession) {
/**

  • 调用 MapperRegistry Mapper接口注册器
    */
    return mapperRegistry.getMapper(type, sqlSession);
    }
    }

public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

public T getMapper(Class type, SqlSession sqlSession) {
/**

  • knownMappers 从缓存中获取 MapperProxyFactory Mapper接口代理工厂
  • 如果没有找到就会抛出异常,
  • 说明获取Mapper接口代理实例时,需要事先定义好 --> 相当于Spring的扫包
    /
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
    }
    }
    }

2.3.1:使用JDK代理创建Mapper接口代理

InvocationHandler是MapperProxy

public class MapperProxyFactory {

private final Class mapperInterface;
/**

  • MapperMethod 缓存
    */
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

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

@SuppressWarnings(“unchecked”)
protected T newInstance(MapperProxy mapperProxy) {
/**

  • JDK 生产代理类
    */
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }

public T newInstance(SqlSession sqlSession) {
/**

  • 代理类回调接口
    */
    final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
    }
    }

2.4:userMapper.findById(1); 调用Mapper接口方法Sql查询

会走代理类 MapperProxy.invoke

public class MapperProxy implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map<Method, MapperMethod> methodCache;

public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

@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 (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
/**

  • 根据方法全限名 获取一个MapperMethod实例,并且缓存
  • 注意:这里的 methodCache 只是一个引用,缓存的所有对象都在 {@link MapperProxyFactory#methodCache}中
    /
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    /
    *
  • 开始执行
    */
    return mapperMethod.execute(sqlSession, args);
    }

/**

  • 添加到缓存 methodCache {@link MapperProxyFactory#methodCache}中
  • computeIfAbsent HashMap 存在就获取,不存在就新增
  • @param method
  • @return
    */
    private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
    }
    }

2.5:mapperMethod.execute(sqlSession, args);

执行Sql语句查询,由于我的返回结果是一个 User对象,所以会走到
result = sqlSession.selectOne(command.getName(), param);这一行,查询一条记录

实际走到 DefaultSqlSession.selectOne()

public class MapperMethod {

private final org.apache.ibatis.binding.MapperMethod.SqlCommand command;
private final org.apache.ibatis.binding.MapperMethod.MethodSignature method;

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new org.apache.ibatis.binding.MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new org.apache.ibatis.binding.MapperMethod.MethodSignature(config, mapperInterface, method);
}
/**

  • 开始执行Sql查询
    /
    public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
    case INSERT: {
    // 省略代码… 执行 insert语句 /@Insert
    break;
    }
    case UPDATE: {
    // 省略代码… 执行 insert语句 /@Update
    break;
    }
    case DELETE: {
    // 省略代码… 执行 delete语句 /@Delete
    break;
    }
    case SELECT: // 执行 select语句 /@Select
    if (method.returnsVoid() && method.hasResultHandler()) {
    // 返回类型是否为空 一般情况做Sql操作都要有返回结果的
    executeWithResultHandler(sqlSession, args);
    result = null;
    } else if (method.returnsMany()) {
    /
    *
  • 是否返回多个结果集 {@link Collection}集合/数组
    /
    result = executeForMany(sqlSession, args);
    } else if (method.returnsMap()) {
    /
    *
  • 返回类型是否为 Map 集合
    /
    result = executeForMap(sqlSession, args);
    } else if (method.returnsCursor()) {
    /
    *
  • 返回类型是否是 游标 {@link org.apache.ibatis.cursor.Cursor}
    /
    result = executeForCursor(sqlSession, args);
    } else {
    /
    *
  • 将参数转换为Sql命令参数
    /
    Object param = method.convertArgsToSqlCommandParam(args);
    /
    *
  • 发起查询 调用的是 sqlSession中的方法
    */
    result = sqlSession.selectOne(command.getName(), param);
    if (method.returnsOptional()
    && (result == null || !method.getReturnType().equals(result.getClass()))) {
    result = Optional.ofNullable(result);
    }
    }
    break;
    case FLUSH: // 刷新
    result = sqlSession.flushStatements();
    break;
    default:
    throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException(“Mapper method '” + command.getName()
  • " attempted to return null from a method with a primitive return type (" + method.getReturnType() + “).”);
    }
    return result;
    }
    }

DefaultSqlSession.selectOne()可以看出实际是调用selectList(),而且如果返回了多个结果集就会报错。错误信息如下
Expected one result (or null) to be returned by selectOne(), but found: 2
在这里插入图片描述

2.6:DefaultSqlSession.selectList()

查询多结果集

public class DefaultSqlSession implements SqlSession {
/**
*

  • @param statement 方法全限名 比如:com.mapper.UserMapper.findById
  • @param parameter 参数
  • @param rowBounds 分页
  • @param
  • @return
    /
    @Override
    public List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
    /
    *
  • 根据方法全限名在 configuration.mappedStatements缓存集合中拿到方法对应的Sql Statement对象实例
    /
    MappedStatement ms = configuration.getMappedStatement(statement);
    /
    *
  • 使用执行器执行 由当前设置的执行器执行
  • 注意:cacheEnabled由于开启二级缓存默认为true,会先使用 {@link CachingExecutor} 缓存执行器查询
    */
    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();
    }
    }
    }

最后

码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到

又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考

以下是部分内容截图
架构面试专题及架构学习笔记导图.png

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

dStatement(statement);
/**

  • 使用执行器执行 由当前设置的执行器执行
  • 注意:cacheEnabled由于开启二级缓存默认为true,会先使用 {@link CachingExecutor} 缓存执行器查询
    */
    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();
    }
    }
    }

最后

码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到

又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考

以下是部分内容截图
[外链图片转存中…(img-ODVSBlH2-1715002820323)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值