33-MyBatis-3(源码流程)
HelloWorld 源码全流程
1. SqlSessionFactory 的初始化#
1.1 SqlSessionFactoryBuilder#
public SqlSessionFactory build(InputStream inputStream
, String environment, Properties properties) {
try {
// 创建解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// ===== ↓↓↓ Step Into ↓↓↓ =====
// 使用 XPATH 解析 XML 配置文件,将配置文件封装为 Configuration 对象
// 返回 DefaultSqlSessionFactory 对象,该对象拥有 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 对象,这个类是 BaseBuilder 的子类,BaseBuilder 类图如下:
看到这些子类基本上都是以 Builder 结尾,这里使用的是建造者设计模式。
1.2 XMLConfigBuilder#
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
// XPathParser 基于 Java XPath 解析器,用于解析 MyBatis 中的配置文件
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// ===== ↓↓↓ Step Into ↓↓↓ =====
parseConfiguration(parser.evalNode("/configuration")); // 根节点
return configuration;
}
private void parseConfiguration(XNode root) {
// 挨个解析<configuration>下的每一个子节点,将详细信息保存在 configuration 中
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// ===== ↓↓↓ Step Into ↓↓↓ =====
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// ===== ↓↓↓ Step Into ↓↓↓ =====
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
// props ←→ <settings>
private void settingsElement(Properties props) throws Exception {
configuration.setAutoMappingBehavior(AutoMappingBehavior
.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior
.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(
booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory(
(ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(
props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(
booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(
booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(
booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(
booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(
ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(
integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(
integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(
booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(
booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(
LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(
JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(
props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(
booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(
resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(
booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(
booleanValueOf(props.getProperty("useActualParamName"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
@SuppressWarnings("unchecked")
Class<? extends Log> logImpl =
(Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
configuration.setConfigurationFactory(
resolveClass(props.getProperty("configurationFactory")));
}
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());
// ===== ↓↓↓ Step Into ↓↓↓ =====> #1.3
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.");
}
}
}
}
}
1.3 XMLMapperBuilder#
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 可重用 SQL
sqlElement(context.evalNodes("/mapper/sql"));
// ===== ↓↓↓ Step Into ↓↓↓ =====
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
// ===== ↓↓↓ Step Into ↓↓↓ =====
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// 解析 CRUD 标签的解析器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(
configuration, builderAssistant, context, requiredDatabaseId);
try {
// ===== ↓↓↓ Step Into ↓↓↓ =====> #1.4
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
1.4 XMLStatementBuilder#
public void parseStatementNode() {
// 将 CRUD 标签中的一个个属性都解析出来 ...
// ===== ↓↓↓ Step Into ↓↓↓ =====
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType
, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap
, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered
, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
1.5 MapperBuilderAssistant#
public MappedStatement addMappedStatement(对应上一步的那一堆配置...) {
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(
configuration, id, sqlSource, sqlCommandType).配置全部加进来;
ParameterMap statementParameterMap =
getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 根据解析出来的配置,构建一个 MappedStatement 对象(封装一个 CRUD 标签的详细信息)
MappedStatement statement = statementBuilder.build();
// 把这个 MappedStatement 加入到 configuration 中
configuration.addMappedStatement(statement);
return statement;
}
1.6 Configuration#
Configuration 对象保存了所有配置文件(全局、映射) 的详细信息。
2. 获取 SqlSession#
Mybatis 四大组件之一的 Executor 在这一步会被创建。
2.1 DefaultSqlSessionFactory#
@Override
public SqlSession openSession(boolean autoCommit) {
// ===== ↓↓↓ Step Into ↓↓↓ =====
return openSessionFromDataSource(
configuration.getDefaultExecutorType(), null, autoCommit);
}
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);
// ===== ↓↓↓ Step Into ↓↓↓ =====> 得先创建 Executor → #2.2
final Executor executor = configuration.newExecutor(tx, execType);
// Executor 作为 SqlSession 构造器形参之一,只有先有它才能创建 SqlSession → #2.5
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#
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据 Executor 在全局配置文件中配置的类型,创建出相应的 XxxExecutor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else { // SIMPLE
executor = new SimpleExecutor(this, transaction); // √
}
if (cacheEnabled) {
// ===== ↓↓↓ Step Into ↓↓↓ =====> 若配置了二级缓存 → #2.3
executor = new CachingExecutor(executor); // 包装 Executor
}
// ===== ↓↓↓ Step Into ↓↓↓ =====> 返回经拦截器们包装后的 Executor → #2.4
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
2.3 CachingExecutor#
public class CachingExecutor implements Executor {
// 原始 Executor
private Executor delegate;
// 二级缓存
private TransactionalCacheManager tcm = new TransactionalCacheManager();
// 对传进来的 Executor 做了包装,并返回包装后的 Executor
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
// ...
}
2.4 InterceptorChain#
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
// 每个拦截器都来重新包装 Executor
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
2.5 DefaultSqlSession#
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
3. 返回 Mapper 代理对象#
3.1 DefaultSqlSession#
@Override
public <T> T getMapper(Class<T> type) {
// ===== ↓↓↓ Step Into ↓↓↓ =====
return configuration.<T>getMapper(type, this);
}
3.2 Configuration#
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// ===== ↓↓↓ Step Into ↓↓↓ =====
return mapperRegistry.getMapper(type, sqlSession);
}
3.3 MapperRegistry#
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据 Mapper<I> 类型从这个 Map 中拿到对应的 MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory
= (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// ===== ↓↓↓ Step Into ↓↓↓ =====> mapperProxyFactory 根据 sqlSession 创建 MapperProxy
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
3.4 MapperProxyFactory#
public T newInstance(SqlSession sqlSession) {
// ===== ↓↓↓ Step Into ↓↓↓ =====> #3.5 → 创建 MapperProxy 对象
final MapperProxy<T> mapperProxy = new MapperProxy<T>(
sqlSession, mapperInterface, methodCache);
// ===== ↓↓↓ Step Into ↓↓↓ =====> 对上一步创建的 MapperProxy 对象做动态代理,并返回
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader()
, new Class[] { mapperInterface }, mapperProxy);
}
3.5 MapperProxy#
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession
, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
}
4. 调用查询方法#
4.1 MapperProxy#
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// ===== ↓↓↓ Step Into ↓↓↓ =====> 传入 sqlSession 和方法参数
return mapperMethod.execute(sqlSession, args);
}
4.2 MapperMethod#
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// ===== ↓↓↓ Step Into ↓↓↓ =====> 封装参数 → 下面的方法
Object param = method.convertArgsToSqlCommandParam(args);
// ===== ↓↓↓ Step Into ↓↓↓ =====> #4.4
result = sqlSession.selectOne(command.getName(), param);
}
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;
}
public static class MethodSignature {
private final ParamNameResolver paramNameResolver;
public Object convertArgsToSqlCommandParam(Object[] args) {
// ===== ↓↓↓ Step Into ↓↓↓ =====> 调用参数解析器 → #4.3
return paramNameResolver.getNamedParams(args);
}
}
4.3 ParamNameResolver#
private static final String GENERIC_NAME_PREFIX = "param";
public ParamNameResolver(Configuration config, Method method) {
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
4.4 DefaultSqlSession#
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
// ===== ↓↓↓ Step Into ↓↓↓ =====
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;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// ===== ↓↓↓ Step Into ↓↓↓ =====
// statement: cn.edu.nuist.mapper.TeacherMapper.getTeacherByTid
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// Configuration 成员:Map<String, MappedStatement> mappedStatements
// 根据该 statement(select标签的唯一标识),拿到了对应的 MappedStatement
// 该 MappedStatement 对象中封装了对应的 <select> 的详细信息
MappedStatement ms = configuration.getMappedStatement(statement);
// ===== ↓↓↓ Step Into ↓↓↓ =====> 先钻入下面的方法 → 然后是 #4.5
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();
}
}
// 嘿!
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;
}
- BaseExecutor:基础执行器,封装了子类的公共方法,包括一级缓存、延迟加载、回滚、关闭等功能;
- SimpleExecutor:简单执行器,每执行一条 sql 都会打开一个 Statement,执行完成后关闭;
- ReuseExecutor:重用执行器,相较于 SimpleExecutor 多了 Statement 的缓存功能,其内部维护一个 Map<String, Statement>,每次编译完成的 Statement 都会进行缓存,不会关闭;
- BatchExecutor:批量执行器,基于 JDBC 的 addBatch、executeBatch 功能,并且在当前 sql 和上一条 sql 完全一样的时候,重用 Statement,在调用 doFlushStatements 的时候,将数据刷新到数据库;
- CachingExecutor:缓存执行器,装饰器模式,在开启缓存的时候。会在上面三种执行器的外面再包上 CachingExecutor;
4.5 CachingExecutor#
@Override
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);
// ===== ↓↓↓ Step Into ↓↓↓ =====
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@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, parameterObject, boundSql);
@SuppressWarnings("unchecked")
// 先看二级缓存(TransactionalCacheManager) 中有没有
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// ===== ↓↓↓ Step Into ↓↓↓ =====> #4.6
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);
}
CachingExecutor 是对 Executor 的包装,所以最终还是调用原始 Executor 的 query 方法。
4.6 BaseExecutor#
@SuppressWarnings("unchecked")
@Override
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++;
// 拿 cacheKey 在一级缓存(localCache) 中查找!
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else { // 没找到就去 DB 中找
// ===== ↓↓↓ Step Into ↓↓↓ =====
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(MappedStatement ms
, Object parameter, RowBounds rowBounds, ResultHandler resultHandler
, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// ===== ↓↓↓ Step Into ↓↓↓ =====> 4.7
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;
}
4.7 SimpleExecutor*#
在此方法内创建了 StatementHandler (ParameterHandler、ResultSetHandler)
@Override
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();
// ===== ↓↓↓ Step Into ↓↓↓ =====> StatementHandler(可用来创建 Statement 对象) → #4.8
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// ===== ↓↓↓ Step Into ↓↓↓ =====> 创建 Statement 对象 → 就在下面 ~
stmt = prepareStatement(handler, ms.getStatementLog());
// ===== ↓↓↓ Step Into ↓↓↓ =====> #4.9[3]
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(
StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取连接
Connection connection = getConnection(statementLog);
// 创建 Statement(PreparedStatement、Statement、CallableStatement)
stmt = handler.prepare(connection, transaction.getTimeout());
// ===== ↓↓↓ Step Into ↓↓↓ =====> #4.11
// SQL 参数设置。面上是 StatementHandler 调用的,实际是 ParameterHandler 来做的参数预编译
// ParameterHandler 创建时机:创建 StatementHandler 时一并创建的 → #4.10
handler.parameterize(stmt);
return stmt;
}
4.8 Configuration#
public StatementHandler newStatementHandler(Executor executor
, MappedStatement mappedStatement, Object parameterObject
, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// ===== ↓↓↓ Step Into ↓↓↓ =====> #4.9
StatementHandler statementHandler = new RoutingStatementHandler(executor
, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 使用拦截器包装 StatementHandler
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public ParameterHandler newParameterHandler(
MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang()
.createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor
, MappedStatement mappedStatement, RowBounds rowBounds
, ParameterHandler parameterHandler, ResultHandler
resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor,
mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
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;
}
4.9 RoutingStatementHandler#
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter
, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor
, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
// ===== ↓↓↓ Step Into ↓↓↓ =====> { super(...); } → #4.10
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());
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
@Override
public <E> List<E> query(Statement statement
, ResultHandler resultHandler) throws SQLException {
// ===== ↓↓↓ Step Into ↓↓↓ =====> #4.11[2]
return delegate.<E>query(statement, resultHandler);
}
- BaseStatementHandler:基础语句处理器(抽象类),它基本把语句处理器接口的核心部分都实现了,包括配置绑定、执行器绑定、映射器绑定、参数处理器构建、结果集处理器构建、语句超时设置、语句关闭等,并另外定义了新的方法 instantiateStatement 供不同子类实现以便获取不同类型的语句连接,子类可以普通执行 SQL 语句,也可以做预编译执行,还可以执行存储过程等。
- SimpleStatementHandler:普通语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.Statement 对象的处理,处理普通的不带动态参数运行的 SQL,即执行简单拼接的字符串语句,同时由于 Statement 的特性,SimpleStatementHandler 每次执行都需要编译 SQL (注意:我们知道 SQL 的执行是需要编译和解析的)。
- PreparedStatementHandler:预编译语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.PrepareStatement 对象的处理,相比上面的普通语句处理器,它支持可变参数 SQL 执行,由于 PrepareStatement 的特性,它会进行预编译,在缓存中一旦发现有预编译的命令,会直接解析执行,所以减少了再次编译环节,能够有效提高系统性能,并预防 SQL 注入攻击(所以是系统默认也是我们推荐的语句处理器)。
- CallableStatementHandler:存储过程处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.CallableStatement 对象的处理,很明了,它是用来调用存储过程的,增加了存储过程的函数调用以及输出/输入参数的处理支持。
- RoutingStatementHandler:路由语句处理器,直接实现了 StatementHandler 接口,作用如其名称,确确实实只是起到了路由功能,并把上面介绍到的三个语句处理器实例作为自身的委托对象而已,所以执行器在构建语句处理器时,都是直接 new 了 RoutingStatementHandler 实例。
4.10 BaseStatementHandler#
protected BaseStatementHandler(Executor executor, MappedStatement
mappedStatement, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// ===== ↓↓↓ Step Into ↓↓↓ =====> 还会同时创建另外两个组件!!! → 组件创建: #4.8
this.parameterHandler = configuration.newParameterHandler(
mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor
, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
如果看到这了,现在再回到 #4.7!看第二个要 Step Into 的地方!
4.11 PreparedStatementHandler#
@Override
public void parameterize(Statement statement) throws SQLException {
// ===== ↓↓↓ Step Into ↓↓↓ =====> #4.12
parameterHandler.setParameters((PreparedStatement) statement);
}
@Override
public <E> List<E> query(Statement statement
, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// ===== ↓↓↓ Step Into ↓↓↓ =====> #4.13
return resultSetHandler.<E> handleResultSets(ps);
}
4.12 DefaultParameterHandler#
@Override
public void setParameters(PreparedStatement ps) {
// ...
// TypeHandler 给 SQL 预编译设置参数
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
typeHandler.setParameter(ps, i + 1, value, jdbcType);
// ...
}
回到 #4.7,继续走第 3 个断点。
4.13 DefaultResultSetHandler#
JavaBean 和表记录的映射(设置参数、结果映射) 由 TypeHandler 完成。
(1)DefaultResultSetHandler#handleResultSets
从 rsw 结果集参数中获取查询结果,再根据 resultMap 映射信息,将查询结果映射到 multipleResults 中。
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// <select>标签的resultMap属性,可以指定多个值,多个值之间用逗号(,)分割
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 这里是获取第一个结果集,将传统JDBC的ResultSet包装成一个包含结果列元信息的ResultSetWrapper对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 这里是获取所有要映射的ResultMap(按照逗号分割出来的)
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
// 要映射的ResultMap的数量
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 循环处理每个ResultMap,从第一个开始处理
while (rsw != null && resultMapCount > resultSetCount) {
// 得到结果映射信息
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理结果集。从rsw结果集参数中获取查询结果,再根据resultMap映射信息,将查询结果映射到multipleResults中
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 对应<select>标签的resultSets属性,一般不使用该属性
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 如果只有一个结果集合,则直接从多结果集中取出第一个
return collapseSingleResultList(multipleResults);
}
(2)DefaultResultSetHandler#handleRowValues
处理行数据,其实就是完成结果映射。
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 是否有内置嵌套的结果映射
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
// 嵌套结果映射
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 简单结果映射
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
(3)DefaultResultSetHandler#handleRowValuesForSimpleResultMap
简单结果映射
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
// 获取结果集信息
ResultSet resultSet = rsw.getResultSet();
// 使用rowBounds的分页信息,进行逻辑分页(也就是在内存中分页)
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 通过<resultMap>标签的子标签<discriminator>对结果映射进行鉴别
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 将查询结果封装到POJO中
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 处理对象嵌套的映射关系
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
(4)DefaultResultSetHandler#getRowValue
将查询结果封装到 POJO 中
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
// 延迟加载的映射信息
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建要映射的 PO 类对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 是否应用自动映射,也就是通过 resultType 进行映射
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 根据 columnName 和 type 属性名映射赋值
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 根据我们配置 ResultMap 的 column 和 property 映射赋值
// 如果映射存在 nestedQueryId,会调用 getNestedQueryMappingValue 方法获取返回值
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
(4.1)DefaultResultSetHandler#createResultObject
创建映射结果对象
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
// 创建结果映射的PO类对象
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 获取要映射的PO类的属性信息
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// 延迟加载处理
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
// 通过动态代理工厂,创建延迟加载的代理对象
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
List<Object> constructorArgs, String columnPrefix) throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
if (hasTypeHandlerForResultObject(rsw, resultType)) {
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} else if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes,
constructorArgs, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 对象工厂创建对象
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
(4.2)DefaultResultSetHandler#applyAutomaticMappings
根据 columnName 和 type 属性名映射赋值
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
(4.3)DefaultResultSetHandler#applyPropertyMappings
根据我们配置 ResultMap 的 column 和 property 映射赋值,如果映射存在 nestedQueryId,会调用 getNestedQueryMappingValue 方法获取返回值。
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader,
columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls()
&& !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
(4.4)DefaultResultSetHandler#getPropertyMappingValue
private Object getPropertyMappingValue(ResultSet rs
, MetaObject metaResultObject, ResultMapping propertyMapping
, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping);
return DEFERED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
4.14 小结#
- 根据配置文件(全局、映射) 初始化出 Configuration 对象
- 创建一个 DefaultSqlSession 对象,其中包含 Configuration、Executor (根据全局配置文件中的 defaultExecutorType 创建出对应的 Executor) 对象的引用。
DefaultSqlSession.getMapper(class)
,首先拿到Mapper<I>
对应的 MapperProxyFactory,然后通过newInstance()
创建 MapperProxy 对象,最终返回的是该对象的动态代理。- 执行 CRUD 方法
- 底层是代理对象调用了 DefaultSqlSession 对象的 CRUD 方法
- 会创建一个 Statement 对象 (会首先创建 StatementHandler,而在 StatementHandler 的构造器中又会创建 ParameterHandler、ResultSetHandler 对象)
- 调用 StatementHandler 预编译参数以及设置参数值(底层实际是调用 ParameterHandler 来做的)
- 调用 StatementHandler 的 CRUD 方法
- 使用 ResultSetHandler 封装结果
5. 总结#
组件介绍:
- SqlSession:是 Mybatis 对外暴露的核心 API,提供了对数据库的 CRUD 操作接口;
- Executor:执行器,由 SqlSession 调用,负责数据库操作以及 Mybatis 两级缓存的维护;
- StatementHandler:封装了 JDBC Statement 操作,负责对 Statement 的操作,例如 PrepareStatement 参数的设置以及结果集的处理;
- ParameterHandler:是 StatementHandler 内部一个组件,主要负责对 ParameterStatement 参数的设置;
- ResultSetHandler:是 StatementHandler 内部一个组件,主要负责对 ResultSet 结果集的处理,封装成目标对象返回;
- TypeHandler:用于 Java 类型与 JDBC 类型之间的数据转换,ParameterHandler 和 ResultSetHandler 会分别使用到它的类型转换功能;
- MappedStatement:是对 Mapper 配置文件或 Mapper 接口方法上通过注解声明 SQL 的封装;
- Configuration:Mybatis 所有配置都统一由 Configuration 进行管理,内部由具体对象分别管理各自的小功能模块。
四层架构:
- API 接口层:提供增加、删除、修改、查询等接口,通过 API 接口对数据库进行操作;
- 数据处理层:主要负责 SQL 的查询、解析、执行以及结果映射的处理,解析 SQL 根据调用请求完成一次数据库操作;
- 框架支撑层:负责通用基础服务支撑,包含事务管理、连接池管理、缓存管理等共用组件的封装,为上层提供基础服务支撑;
- 引导层:引导层是配置和启动 MyBatis 配置信息的方式。
【注意】MyBatis 的四大组件创建过程中,都有插件进行介入:interceptorChain.pluginAll(...)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?