MyBatis源码分析(一)会话连接与调用流程分析
1、分析案例搭建
- 新建一个maven项目
- 引入依赖
<!-- Mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- MySQL依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
<scope>runtime</scope>
</dependency>
- 创建实体类
public class User implements Serializable {
private Integer age;
private String name;
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 创建mapper
public interface UserMapper {
/**
* 根据id查询
*
* @param id
* @return
*/
User selectById(@Param("id") String id);
}
- 创建mapper.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.xiao7.mybatis.mapper.UserMapper">
<resultMap id="User" type="com.xiao7.mybatis.entity.User">
<id column="AGE" property="age"/>
<!-- <result column="NAME" property="name"/>-->
</resultMap>
<cache/>
<select id="selectById" resultMap="User">
select * from user
<where>
<if test="id != null">
id = #{id}
</if>
</where>
</select>
</mapper>
- 新建mybatis-config.xml
<?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>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="Asd4840840"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/xiao7/mybatis/mapper/UserMapper.xml"/>
</mappers>
</configuration>
- 测试类
public static void main(String[] args) {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
//读取mybatis-config.xml
inputStream = Resources.getResourceAsStream(resource);
//解析mybatis-config.xml配置文件,创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSessionFactory.getConfiguration().addInterceptor(new ExecutorPlugin());
sqlSessionFactory.getConfiguration().addInterceptor(new ParameterHandlerPlugin());
sqlSessionFactory.getConfiguration().addInterceptor(new ResultSetHandlerPlugin());
sqlSessionFactory.getConfiguration().addInterceptor(new StatementHandlerPlugin());
//创建sqlSession
sqlSession = sqlSessionFactory.openSession(true);
//创建userMapper对象(UserMapper并没有实现类)
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用userMapper对象的方法
User user = userMapper.selectById("1");
System.out.println(user);
// 提交
sqlSession.commit();
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.selectById("1");
System.out.println(user1);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭资源
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
sqlSession.close();
}
}
2、流程源码分析
2.1、配置文件解析
首先的话是读取的config配置文件流,然后使用SqlSessionFactoryBuilder去解析构建sqlSessionFactory,直接进入方法查看它是如何解析并构建的。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactory没有构造方法,那么这里使用的就是默认无参构造方法,所以我们直接进去build方法。
//这个方法啥也没干
public SqlSessionFactory build(InputStream inputStream) {
//调用的是另外一个build方法
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//创建一个XMLConfigBuilder对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 主要的话是使用XMLConfigBuilder去解析出Configuration对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
主要的话是使用XMLConfigBuilder去解析出Configuration对象,而XMLConfigBuilder里面主要是使用 Java XPath 解析器XPathParser去解析MyBatis中的mybatis-config.xml、mapper.xml等 XML 配置文件
先解析一级节点
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//mybatis-config.xml的一级标签
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
然后针对不同节点分别解析
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(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);
}
}
只看一下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中
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//使用java类名
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//根据文件存放目录,读取XxxMapper.xml
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
//使用绝对url路径
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
//映射器比较复杂,调用XMLMapperBuilder
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.");
}
}
}
}
}
把每一个节点的配置解析到configuration中,然后构建factory,大致流程如下
2.2、开启sqlSession会话
解析完配置文件之后,就需要开启一个与数据库连接的会话,然后在执行后续的操作。也就是下面这一段代码:
sqlSession = sqlSessionFactory.openSession(true);
直接查看会进入到:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//对应xml标签<environments> ,这个在配置文件解析的时候就已经存放到configuration中了。
final Environment environment = configuration.getEnvironment();
//构建事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//构建一个事务对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建一个executor来执行SQL
final Executor executor = configuration.newExecutor(tx, execType);
//创建一个DefaultSqlSession对象并返回
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();
}
}
1、创建事务Transaction
事务工厂类型可以配置为JDBC类型或者MANAGED类型。
- JdbcTransactionFactory生产JdbcTransaction。
- ManagedTransactionFactory生产ManagedTransaction。
如果配置的JDBC,则会使用Connection对象的commit()、rollback()、close()方法来管理事务。但是,如果是Spring+Mybatis,则没有必要配置,因为我们会直接在applicationContext.xml里配置数据源和事务管理器,从而覆盖Mybatis的配置。
把事务传给newExecutor()方法创建执行器Executor对象。
configuration.newExecutor(tx, execType)
2、创建Executor
调用configuration的newExecutor方法创建Executor。
final Executor executor = configuration.newExecutor(tx, execType);
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 1
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);
}
// 2
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 3
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
主要有三步骤
- 根据不同的类型创建Executor,默认的话是SimpleExecutor
- 如果设置的启用缓存的话则用CachingExecutor包装一层
- 使用拦截器链包装Executor,如果有拦截器的话,Executor会在这一步被包装成代理对象返回
Executor创建完毕后,就该创建DefaultSqlSession了,请看代码:
//创建一个DefaultSqlSession对象并返回
return new DefaultSqlSession(configuration, executor, autoCommit);
进入DefaultSqlSession的构造方法中:
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
大致流程如下:
2.3、mapper的代理对象
下一步则是获取mapper的对象了,因为mapper只是一个接口,所以毫无疑问,获取的就是一个代理的对象。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
debug进去会到DefaultSqlSession
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
然后又由configuration去获取,又在交给mapperRegistry去获取
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取到每个class的代理工厂
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);
}
}
具体的代理源码:
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<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
就是新建一个MapperProxy代理的拦截器对象,然后使用jdk代理,所以mapper的调用最终都会经过MapperProxy的invoke方法,先提前看一下,源码如下:
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);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
整体流程大致如下:
2.4、执行流程
上文也提到了,mapper是一个代理对象了,debug也可以看到:
所以,我们直接进入,代理的invoke方法中开始:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//首先判断是否为Object本身的方法,是则不需要去执行SQL,
//比如:toString()、hashCode()等方法。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
//判断是否JDK8以后的接口默认实现方法。
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//<3>
final MapperMethod mapperMethod = cachedMapperMethod(method);
//<4>
return mapperMethod.execute(sqlSession, args);
}
然后是由mapperMethod去执行接口逻辑的,先了解一下它吧。MapperMethod这个类,定义了两个属性command和method,以及两个静态内部类。
- SqlCommand封装了statement ID,比如说:
com.tian.mybatis.mapper.UserMapper.selectById
- 和SQL类型。
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
具体源码:
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
//获得 MappedStatement 对象
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
// <2> 找不到 MappedStatement
if (ms == null) {
// 如果有 @Flush 注解,则标记为 FLUSH 类型
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
// 抛出 BindingException 异常,如果找不到 MappedStatement
//(开发中容易见到的错误)说明该方法上,没有对应的 SQL 声明。
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
//找到 MappedStatement
} else {
// 获得 name
//id=com.tian.mybatis.mapper.UserMapper.selectById
name = ms.getId();
// 获得 type=SELECT
type = ms.getSqlCommandType();
//如果type=UNKNOWN
if (type == SqlCommandType.UNKNOWN) { // 抛出 BindingException 异常,如果是 UNKNOWN 类型
throw new BindingException("Unknown execution method for: " + name);
}
}
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// 获得编号
//com.tian.mybatis.mapper.UserMapper.selectById
String statementId = mapperInterface.getName() + "." + methodName;
//如果有,获得 MappedStatement 对象,并返回
if (configuration.hasStatement(statementId)) {
//mappedStatements.get(statementId);
//解析配置文件时候创建并保存Map<String, MappedStatement> mappedStatements中
return configuration.getMappedStatement(statementId);
// 如果没有,并且当前方法就是 declaringClass 声明的,则说明真的找不到
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// 遍历父接口,继续获得 MappedStatement 对象
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
// 真的找不到,返回 null
return null;
}
//....
}
public static class MethodSignature {
private final boolean returnsMap;
private final Class<?> returnType;
private final Integer rowBoundsIndex;
//....
}
接着看MapperMethod中execute方法。
先来看看这个方法的整体逻辑:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case SELECT:
//部分代码省略
Object param = method.convertArgsToSqlCommandParam(args);
//本次是QUERY类型,所以这里是重点
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
return result;
}
这个方法中,根据我们上面获得的不同的type(INSERT、UPDATE、DELETE、SELECT)和返回类型:
- 调用convertArgsToSqlCommandParam()将方法参数转换为SQL的参数。
- 调用sqlSession的insert()、update()、dalete()、selectOne()方法。我们这个案例是查询,这里回到了DefaultSqlSession中selectOne方法中。
继续DefaultSqlSession中的selectOne()方法:
//DefaultSqlSession中
@Override
public <T> T selectOne(String statement, Object parameter) {
//这是一种好的设计方法
//不管是执行多条查询还是单条查询,都走selectList方法(重点)
List<T> list = this.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 {
//数据库中没有数据就返回null
return null;
}
}
这里调用的是selectList方法。
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//从configuration获取MappedStatement
//此时的statement=com.tian.mybatis.mapper.UserMapper.selectById
MappedStatement ms = configuration.getMappedStatement(statement);
//调用执行器中的query方法
return executor.query(...);
} catch (Exception e) {
//.....
} finally {
ErrorContext.instance().reset();
}
}
在这个方法里是根据statement从configuration对象中获取MappedStatement。
MappedStatement ms = configuration.getMappedStatement(statement);
在configuration中getMappedStatement方法:
//存放在一个map中的
//key是statement=com.tian.mybatis.mapper.UserMapper.selectById,value是MappedStatement
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>();
public MappedStatement getMappedStatement(String id) {
return this.getMappedStatement(id, true);
}
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
return mappedStatements.get(id);
}
而MappedStatement里面有xml中增删改查标签配置的所有属性,包括id、statementType、sqlSource、入参、返回值等。
最后sql的执行都是交给executor来执行的,接着看,如果executor有拦截器的话则会先走Plugin的invoke方法,如果没有则会先走executor的query方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取sql对象
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
因为开启的缓存,所以调用的cacheExecutor的具体执行是由delegate这个simpleExecutor去执行
@Override
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) {
// 没有缓存则调用原Executor去执行sql
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
具体执行代码在BaseExecutor中:
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++;
// 从一级缓存中获取数据
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;
}
接着看它是如何从数据库中查询并组装数据的:
private <E> List<E> queryFromDatabase(...) 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);
return list;
}
@Override
public <E> List<E> doQuery(....) throws SQLException {
Statement stmt = null;
try {
//获取配置文件信息
Configuration configuration = ms.getConfiguration();
//获取handler
StatementHandler handler = configuration.newStatementHandler(....);
//获取Statement
stmt = prepareStatement(handler, ms.getStatementLog());
//执行RoutingStatementHandler的query方法
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
最后到StatementHandler执行:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//JDBC的流程了
ps.execute();
//处理结果集,如果有插件代理ResultHandler,会先走到被拦截的业务逻辑中
return resultSetHandler.handleResultSets(ps);
}
看到了ps.execute();表示已经到JDBC层面了,这时候SQL就已经执行了。后面就是调用DefaultResultSetHandler类进行处理。结果集的映射是调用resultType的类构建对象,根据mapper填充数据返回,默认是根据字段赋值的,不设置也是可以的。
到这里,SQL语句就执行完毕,并将结果集赋值并返回了。
大致流程如下: