当前位置: 首页 > wzjs >正文

网站建设款计入什么科目网站的基本布局

网站建设款计入什么科目,网站的基本布局,做一个公司网站,做违法网站会怎么样大家好,我是 方圆。SQL 查询是 Mybatis 中的核心流程,本节我们来介绍简单 SQL 的执行流程,过程会比较长,期间会认识很多重要的组件,比如 SqlSession、四大处理器(Executor、StatementHandler、ParameterHan…

大家好,我是 方圆。SQL 查询是 Mybatis 中的核心流程,本节我们来介绍简单 SQL 的执行流程,过程会比较长,期间会认识很多重要的组件,比如 SqlSession、四大处理器(ExecutorStatementHandlerParameterHandlerResultSetHandler)等等,大家先有个脸熟,到具体环节时需要重点关注。在这个过程中会遇到很多设计模式,比如 SqlSession 使用的 门面模式,需要考虑为什么它会使用该模式呢?模板方法模式策略模式 在这个过程中被使用的尤其的多。此外,在这里能够很好的理解和区分 代理模式装饰器模式 等等。在设计原则上,多用组合,少用继承 的设计原则有很多体现,单一职责 更是随处可见,还有关于方法命名的小细节等等都特别值得关注。不过,一定要记得一点:应用再多原则都是在为 降低复杂性,提高可读性和可扩展性努力。

验证该过程源码逻辑采用的单测为 org.apache.ibatis.session.SqlSessionTest.shouldExecuteSelectOneAuthorUsingMapperClass,如下:

    @Testvoid shouldExecuteSelectOneAuthorUsingMapperClass() {try (SqlSession session = sqlMapper.openSession()) {AuthorMapper mapper = session.getMapper(AuthorMapper.class);Author author = mapper.selectAuthor(101);assertEquals(101, author.getId());}}

开篇我们便能看到 SqlSession session = sqlMapper.openSession() 逻辑,那么就以介绍 SqlSession 开始吧:

SqlSession

org.apache.ibatis.session.SqlSession 采用了 门面模式,封装了对数据库的所有操作,包括查询、插入、更新和删除,也对事务进行管理,它是与数据库进行交互的对象,所有执行逻辑都经过该对象去执行:

public interface SqlSession extends Closeable {<E> List<E> selectList(String statement);int insert(String statement);int update(String statement);int delete(String statement);void commit();void rollback();// ...
}

SqlSessionorg.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource 方法中创建,有如下逻辑:

public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;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);// 创建 Executorfinal Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {// may have fetched a connection so lets call close()closeTransaction(tx);throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}// ...
}

根据上述源码可知,在 SqlSession 的构造方法中组合了 ConfigurationExecutor 对象,通过调用它组合的对象来完成 SQL 的执行,这遵循了 多用组合 的设计原则。这段逻辑中,需要重点关注的是 Executor 对象,接下来我们详细介绍一下它。

Executor

org.apache.ibatis.executor.Executor 执行器是 MyBatis 框架中的核心接口,它定义了执行 SQL 语句、管理事务和处理缓存的基本操作。Executor 负责管理 SQL 语句的执行(updatequery 方法等)、事务的处理(commitrollBack 方法)以及缓存的维护(一级缓存在 BaseExecutor 中,二级缓存由 CachingExecutor 负责)等,如下所示:

public interface Executor {ResultHandler NO_RESULT_HANDLER = null;// 该方法用于执行更新操作(包括插入、更新和删除),它接受一个 `MappedStatement` 对象和更新参数,并返回受影响的行数int update(MappedStatement ms, Object parameter) throws SQLException;// 该方法用于执行查询操作,接受 `MappedStatement` 对象(包含 SQL 语句的映射信息)、查询参数、分页信息、结果处理器等,并返回查询结果的列表<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,CacheKey cacheKey, BoundSql boundSql) throws SQLException;<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)throws SQLException;<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;// 该方法用于刷新批处理语句并返回批处理结果List<BatchResult> flushStatements() throws SQLException;// 该方法用于提交事务,参数 `required` 表示是否必须提交事务void commit(boolean required) throws SQLException;// 该方法用于回滚事务。参数 `required` 表示是否必须回滚事务void rollback(boolean required) throws SQLException;// 该方法用于创建缓存键,缓存键用于标识缓存中的唯一查询结果CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);// 该方法用于检查某个查询结果是否已经缓存在本地boolean isCached(MappedStatement ms, CacheKey key);// 该方法用于清空一级缓存void clearLocalCache();// 该方法用于延迟加载属性void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);// 该方法用于获取当前的事务对象Transaction getTransaction();// 该方法用于关闭执行器。参数 `forceRollback` 表示是否在关闭时强制回滚事务void close(boolean forceRollback);boolean isClosed();// 该方法用于设置执行器的包装器void setExecutorWrapper(Executor executor);}

Executororg.apache.ibatis.session.Configuration#newExecutor 方法中被创建:

public class Configuration {protected boolean cacheEnabled = true;public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;// 创建具体的 Executor 实现类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);}// 默认 cacheEnabled 为 true,所以实际创建类型为 CachingExecutorif (cacheEnabled) {executor = new CachingExecutor(executor);}// 插件相关逻辑return (Executor) interceptorChain.pluginAll(executor);}// ...
}

如上所示,MyBatis 提供了多个 Executor 的实现类,以支持不同的执行策略和性能优化,灵活地应对不同的性能和资源管理需求,它在定义这些实现类时,使用了 模板方法模式和策略模式,在 BaseExecutor 定义了方法的模板,子类负责实现其中的逻辑,类图关系如下:

在这里插入图片描述

BaseExecutor 是所有执行器的基类,它主要用来维护事务对象 Transaction、管理一级缓存 PerpetualCache、提供模板方法 queryupdate,具体的执行方法(doQuerydoUpdate)由各子类实现,并提供了缓存管理的方法,如 clearLocalCacheflushStatements

继续 Configuration#newExecutor 方法源码逻辑,ExecutorType executorType 类型默认配置为 ExecutorType.SIMPLE,所以创建的执行器类型为 SimpleExecutor。但需要注意 protected boolean cacheEnabled = true; 配置默认为 true,实际创建类型为 CachingExecutor,会在 SimpleExecutor 外包一层:

我们继续看一下 CachingExecutor 的构造方法实现,注意其中的注释信息:

public class CachingExecutor implements Executor {// 采用了静态代理模式,delegate 为被代理对象,在本次样例中,它为 SimpleExecutor 类型private final Executor delegate;public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}// ...
}

CachingExecutor 的实现使用了 静态代理模式,它是代理对象,负责处理 二级缓存 相关的逻辑,实际的查询逻辑由被代理对象 Executor delegate 执行(SimpleExecutor);逻辑 delegate.setExecutorWrapper(this); 会执行 BaseExecutor 中的 setExecutorWrapper 方法,并用 wrapper 字段引用最外层的执行器,Mybatis 将其命名为 wrapper,但是实际上在源码中并没有应用到 装饰器模式,不过这样设计提供了使用装饰器模式的可能。

public abstract class BaseExecutor implements Executor {// 装饰器模式protected Executor wrapper;@Overridepublic void setExecutorWrapper(Executor wrapper) {this.wrapper = wrapper;}// ...
}

现在 SqlSession session = sqlMapper.openSession() 逻辑已经被执行完了,准备进入获取 Mapper 的逻辑:

    @Testvoid shouldExecuteSelectOneAuthorUsingMapperClass() {try (SqlSession session = sqlMapper.openSession()) {// 在会话中获取 MapperAuthorMapper mapper = session.getMapper(AuthorMapper.class);Author author = mapper.selectAuthor(101);assertEquals(101, author.getId());}}

它会执行到 org.apache.ibatis.binding.MapperRegistry#getMapper 方法,注意其中的注释信息:

public class MapperRegistry {private final Configuration config;// 所有的 Mapper 对应的 MapperProxyFactory 已经在 Mybatis 配置加载时初始化好了(对应 mybatis.xml 配置文件中的 <mappers> 标签)private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();public MapperRegistry(Configuration config) {this.config = config;}@SuppressWarnings("unchecked")public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {// 通过 MapperProxyFactory 工厂创建 MapperProxyreturn mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}}

mapperProxyFactory.newInstance(sqlSession); 方法创建了 MapperProxy 对象,虽然它将类命名中包含 Factory,但是它并没有使用工厂模式,而是采用了 简单工厂的编程风格,将创建 MapperProxy 对象的逻辑封装起来:

public class MapperProxyFactory<T> {// ...public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}@SuppressWarnings("unchecked")protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);}}

MapperProxy 对象使用了 动态代理模式,它实现了 InvocationHandler 接口,主要代理的功能为创建并缓存 MapperMethodInvoker 对象,衔接了 Mapper 接口方法与 SQL 操作的绑定和执行过程:

public class MapperProxy<T> implements InvocationHandler, Serializable {// ...@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 是否为 Object 类的方法,如果是直接执行,不做代理相关逻辑if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);}// 否则缓存和获取 MapperMethodInvoker 实例,再执行return cachedInvoker(method).invoke(proxy, method, args, sqlSession);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}
}

其中 cachedInvoker 方法值得关注,它为每个 mapper 方法创建并缓存 MapperMethodInvoker

public class MapperProxy<T> implements InvocationHandler, Serializable {// ...// 缓存 MapperMethodInvokerprivate final Map<Method, MapperMethodInvoker> methodCache;private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {try {return MapUtil.computeIfAbsent(methodCache, method, m -> {// mapper 中声明的 SQL 执行方法均为非default方法if (!m.isDefault()) {return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}// default 方法:声明在 interface 中,使用 default 标记,并提供了默认实现// default 方法使用 DefaultMethodInvoker 执行,了解即可try {if (privateLookupInMethod == null) {return new DefaultMethodInvoker(getMethodHandleJava8(method));}return new DefaultMethodInvoker(getMethodHandleJava9(method));} catch (IllegalAccessException | InstantiationException | InvocationTargetException| NoSuchMethodException e) {throw new RuntimeException(e);}});} catch (RuntimeException re) {Throwable cause = re.getCause();throw cause == null ? re : cause;}}
}

PlainMethodInvokerDefaultMethodInvoker 对象中的逻辑非常简单,不过是在方法执行对象上套了一层壳,但是如此设计还是很有必要的,它使用到了 策略模式

public class MapperProxy<T> implements InvocationHandler, Serializable {// ...interface MapperMethodInvoker {Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;}private static class PlainMethodInvoker implements MapperMethodInvoker {private final MapperMethod mapperMethod;public PlainMethodInvoker(MapperMethod mapperMethod) {this.mapperMethod = mapperMethod;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args);}}private static class DefaultMethodInvoker implements MapperMethodInvoker {private final MethodHandle methodHandle;public DefaultMethodInvoker(MethodHandle methodHandle) {this.methodHandle = methodHandle;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return methodHandle.bindTo(proxy).invokeWithArguments(args);}}
}

这样设计的好处是:

  1. 单一职责PlainMethodInvoker 通过封装 MapperMethod 的调用逻辑,实现了职责分离。这样做可以将方法调用的具体实现与代理的其他逻辑(如缓存处理、事务管理等)分开,保持代码的清晰和可维护性

  2. 灵活性和扩展性:封装调用逻辑使得以后可以更容易地扩展或修改调用过程,而不需要直接修改 MapperProxy 的代码(如果需要在调用前后添加额外的逻辑,可以实现不同的 MethodInvoker

  3. 提供一致的接口MapperProxy 可以在调用方法时不关心具体的实现细节,只需调用 MethodInvoker#invoke 方法。MapperMethodInvoker 的另一个实现 DefaultMethodInvoker 内封装的是 MethodHandle,显然与 MapperMethod 对象执行方法的逻辑不一致,但是 MethodInvoker 只对外暴露 invoke 方法,外部调用逻辑便无需针对不同的类型做改动了

现在我们已经清楚了 PlainMethodInvokerMapperMethod 的执行器,这便需要我们重点了解下 MapperMethod 的逻辑:

public class MapperMethod {// 主要用于记录 SQL 类型: SELECT、INSERT、DELETE 和 UPDATE 等private final SqlCommand command;// 记录返回值等信息private final MethodSignature method;public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {this.command = new SqlCommand(config, mapperInterface, method);this.method = new MethodSignature(config, mapperInterface, method);}// ...
}

MapperMethod 被实例化时,会创建 SqlCommandMethodSignature 对象,这两个类均定义在 MapperMethod 类内部。在 Mybatis 源码中多处都遵循了这种设计原则:

  1. 单一职责:这个是经常提到的原则,定义两个不同的对象来分别做不同的功能体现了该原则
  2. 高内聚SqlCommandMethodSignatureMapperMethod 直接相关,封装为静态内部类体现该原则
  3. 信息隐藏SqlCommandMethodSignature 的实现细节都作为内部类,不对外公开,其他类无需关注具体实现,也没有对外公开使用
  4. 最少知识原则:其思想是“一个对象应当对其他对象有尽可能少的了解,只与直接的朋友通信,而不与陌生对象通信”,此处也遵循了该思想

SqlCommand 定义非常简单,其中字段 nametype 分别记录了方法全路径名和 SQL 类型:

public class MapperMethod {// ...public static class SqlCommand {// eg: org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthorprivate final String name;// eg: SELECTprivate final SqlCommandType type;// ...}
}

MethodSignature 中定义的字段内容稍多一些,请关注注释,具体的字段赋值逻辑并不复杂,便不在这里详细解释了:

public class MapperMethod {// ...public static class MethodSignature {// 结果是否返回一个集合private final boolean returnsMany;// 返回值是否使用了 org.apache.ibatis.annotations.MapKey 注解,标记了作为 Map 的 key 的值// 使用方法详见注解的注释内容private final String mapKey;// 结果是否返回 Mapprivate final boolean returnsMap;// 结果是否返回 voidprivate final boolean returnsVoid;// 结果是否返回 cursorprivate final boolean returnsCursor;// 结果是否返回 optionalprivate final boolean returnsOptional;// 返回值类型private final Class<?> returnType;// resultHandler 在参数中的索引值,无则为 nullprivate final Integer resultHandlerIndex;// rowBounds 用于分页的参数对象的索引值,无则为 nullprivate final Integer rowBoundsIndex;// 用于解析方法参数名称的工具,它用于处理方法参数的名称,以便在执行 SQL 语句时正确地将参数传递给 SQL 语句,常见的 @Param 注解便在这里生效// 该工具类中的注释描述很清楚,其中封装了 names 字段来表示参数名和索引值的对应关系,例子如下// aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}// aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}// aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}private final ParamNameResolver paramNameResolver;// ...}}

MethodSignature 中定义的 ParamNameResolver 中有一段比较有意思的代码,在这里稍稍提一下:

public class ParamNameResolver {// key: 索引 value: 参数值private final SortedMap<Integer, String> names;// ...public ParamNameResolver(Configuration config, Method method) {final SortedMap<Integer, String> map = new TreeMap<>();// ...names = Collections.unmodifiableSortedMap(map);}
}

其中使用到了 Collections.unmodifiableSortedMap(map) 方法,表示该 Map 初始化完成后是不能修改的,如果业务中也有不可修改的对象,可以参考使用该逻辑。此外,该 Map 类型使用的是 TreeMap 红黑树,想具体了解经典红黑树可以参考这篇文章 深入理解经典红黑树。

MapperMethod 中的组件已经介绍完了,下面来看一下 execute 方法,因为该方法相对复杂,我们先集中精力看一下 SELECT 相关的流程:

public class MapperMethod {// ...private final SqlCommand command;private final MethodSignature method;public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {// ...}case UPDATE: {// ...}case DELETE: {// ...}case SELECT:// 返回值为 void 并且在入参中指定了 ResultHandlerif (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;// 结果返回集合} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);// 结果返回 Map} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);// 结果返回 Cursor} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {// 结果返回单个对象Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;}// ...return result;}}

根据单测用例,我们先看一个返回单个对象的分支,MethodSignature#convertArgsToSqlCommandParam 方法见名知意,它会将入参转换成执行 SQL 命令的参数,由此也可以发现 Mybatis 中各个操作的命名不需要注释信息也能表达清楚。接下来就要执行到关键方法 org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne 了,现在又将 SqlSession 的内容接上了:

public class DefaultSqlSession implements SqlSession {// ...@Overridepublic <T> T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.List<T> list = this.selectList(statement, parameter);if (list.size() == 1) {return list.get(0);}if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}}}

可以发现 selectOne 方法复用的是 selectList 方法,这种代码风格也值得我们参考:封装通用的方法尽可能的复用,减少开发工作量。其中的注释也蛮有意思,这么写的原因也是经过大家讨论的结果:

Popular vote was to return null on 0 results and throw exception on too many.
受欢迎的设计是无结果时返回 null,多个结果时抛出异常

其中 DefaultSqlSession#selectList 方法的代码风格也值得参考,它使用了 方法的重载,定义了一个私有的(private)接受全量参数的方法,其他公开出的同名方法入参不同,但本质上调用的都是私有方法(Spring 框架中也有类似的代码)。在《软件设计哲学》中提到过相关的观点,它提倡寻找更 通用的设计,即使在不考虑复用的情况下,通用性的代码也更合理。带入到实际开发中,可以尝试思考如下问题来引导自己找出更通用的设计:

  • 满足当前需求最简单的接口是什么?
  • 这个方法会在多少种情况下被使用?
  • 目前通用的 API 使用起来是否简单?
public class DefaultSqlSession implements SqlSession {// ...@Overridepublic <E> List<E> selectList(String statement) {return this.selectList(statement, null);}@Overridepublic <E> List<E> selectList(String statement, Object parameter) {return this.selectList(statement, parameter, RowBounds.DEFAULT);}@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);}private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {try {MappedStatement ms = configuration.getMappedStatement(statement);dirty |= ms.isDirtySelect();return executor.query(ms, wrapCollection(parameter), rowBounds, handler);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}}

上文中我们提到过,Executor 实际创建类型为 CachingExecutor,接下来继续看下它的 org.apache.ibatis.executor.CachingExecutor#query 方法,分为 3 个主要步骤:

public class CachingExecutor implements Executor {// ...@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 1. 获取 BoundSqlBoundSql boundSql = ms.getBoundSql(parameterObject);// 2. 创建缓存 key 用于一级、二级缓存的获取CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);// 3. 执行查询逻辑return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}
}

第一步获取 BoundSql 对象本质上执行的是 org.apache.ibatis.mapping.SqlSource#getBoundSql 方法,上一节中我们提到过,不包含动态标签的 SQL 最终会被解析成 RawSqlSource 并在内部组合 StaticSqlSourceSqlSource#getBoundSql 做的事情是将 SQL 字符串,参数映射,参数和配置信息保存在 BoundSql 中:

public class StaticSqlSource implements SqlSource {// ...@Overridepublic BoundSql getBoundSql(Object parameterObject) {return new BoundSql(configuration, sql, parameterMappings, parameterObject);}}

借此我们也介绍下 BoundSql,它的主要功能是保存 SQL 语句及其参数的详细信息:

public class BoundSql {// 经过 SqlSource#getBoundSql 处理的 SQL,可能包含 ? 占位符private final String sql;// 参数映射private final List<ParameterMapping> parameterMappings;// 实际入参private final Object parameterObject;// 用于存储附加的参数,这些参数可能是在运行时动态添加的,通常用于处理动态 SQL 中的额外需求private final Map<String, Object> additionalParameters;// 用于方便地访问 additionalParameters 中的属性private final MetaObject metaParameters;// ...
}

第二步创建一级、二级缓存的 key 值,具体逻辑相对简单,它会根据 SQL 和参数等信息来创建,具体方法参见 org.apache.ibatis.executor.BaseExecutor#createCacheKey,无需特别关注。

第三步执行查询逻辑,具体逻辑如下,与 二级缓存 相关,如果想详细了解二级缓存的机制,请参考 从根上理解 Mybatis 二级缓存。

public class CachingExecutor implements Executor {// ...@Overridepublic <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.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);}
}

我们主要关注 delegate.query 方法的逻辑,如下所示,其中涉及了一级缓存相关的内容,详细了解请参考 从根上理解 Mybatis 一级缓存,在此就不再赘述了。

public abstract class BaseExecutor implements Executor {// ...@Overridepublic <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 #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}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 {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;}
}

这里我们重点关注 BaseExecutor#queryFromDatabase 方法设计,它使用到了 模板方法模式,定义了方法的模板,具体执行逻辑 doQuery 由具体子类去实现,而在我们测试的样例中,“具体的子类” 是 SimpleExecutor,执行的方法如下:

public class SimpleExecutor extends BaseExecutor {// ...@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// 创建 StatementHandlerStatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,boundSql);// 准备 Statementstmt = prepareStatement(handler, ms.getStatementLog());// 由 StatementHandler 执行 query 方法return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}}

首先便会创建 StatementHandler,其中逻辑蛮有意思,我们重点看一下:

StatementHandler

StatementHandlerConfiguration#newStatementHandler 方法中被创建,实际类型为 RoutingStatementHandler

public class Configuration {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);// 拦截器相关逻辑return (StatementHandler) interceptorChain.pluginAll(statementHandler);}
}

RoutingStatementHandler 使用了 静态代理模式,命名中 Routing 即表示它代理的作用:根据 statementType 创建不同的 StatementHandler,并去执行相关的逻辑,不配置 statementType 参数的话,默认为 PREPARED,如下所示:

public class RoutingStatementHandler implements StatementHandler {// 代理对象private final StatementHandler delegate;public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,ResultHandler resultHandler, BoundSql boundSql) {// 在调用构造方法时,根据 statementType 字段为代理对象 delegate 赋值,那么这样便实现了复杂度隐藏,只由代理对象去帮忙路由具体的实现即可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());}}@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {return delegate.query(statement, resultHandler);}// ...
}

在这里我们详细介绍下 org.apache.ibatis.executor.statement.StatementHandler SQL 处理器,它是 MyBatis 框架中的一个接口,定义了处理 SQL 语句的核心方法,提供统一的接口供框架调用。它的主要职责是 准备(prepare)、承接封装 SQL 执行参数的逻辑和承接处理结果集的逻辑,这里描述成“承接”的意思是这两部分职责并不是由它处理,而是分别由 ParameterHandlerResultSetHandler 完成。StatementHandler 是 MyBatis 执行 SQL 语句的关键组件之一,Executor 借助它与数据库进行交互。继承关系图如下(BaseStatementHandler 为抽象类,但并没有在命名中添加 Abstract):

在这里插入图片描述

继续回到 SimpleExecutor#doQuery 方法 准备 Statement prepareStatement 方法中:

public class SimpleExecutor extends BaseExecutor {// ...private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());// 封装SQL语句中的参数handler.parameterize(stmt);return stmt;}
}

它会调用 StatementHandler#prepare 方法,该方法使用了 模板方法模式 定义了算法骨架,具体的步骤 instantiateStatement 分别由具体实现类去实现:

public abstract class BaseStatementHandler implements StatementHandler {// ...@Overridepublic Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {// 实例化 Statement 方法statement = instantiateStatement(connection);// 赋值查询超时时间setStatementTimeout(statement, transactionTimeout);// 赋值结果集获取数量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);}}protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
}

在这里实例化 Statement 的方法被命名为 instantiateStatementinstantiate 表示实例化的意思,后续我们在为构造对象的方法命名时也可以采用 instantiateXxx 的形式,一眼便能知道该方法的作用。相应地,为对象的字段赋值的方法可以命名为 initialXxx 表示为已知实例封装了某些字段值。

继续回到源码逻辑中,StatementHandler 实际创建类型为 PreparedStatementHandlerinstantiateStatement 方法创建的 Statement 类型为 ClientPreparedStatement,它是 JDBC 相关的内容,就不再多赘述了。

回到 SimpleExecutor#prepareStatement 方法,创建完 Statement 会调用 StatementHandler#parameterize 方法封装参数,其中会使用到 DefaultParameterHandler 完成该操作,它是 ParameterHandler 接口的默认实现类,我们详细介绍下它:

ParameterHandler

ParameterHandler 的核心逻辑如下,它会完成占位符和指定参数值的对应关系:

public class DefaultParameterHandler implements ParameterHandler {// ...@Overridepublic void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());// 获取参数映射,以便处理用户传入的参数List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {MetaObject metaObject = 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();if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional paramsvalue = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {// 已定义类型处理器,这些类型能直接取对应值value = parameterObject;} else {// 使用反射,根据参数映射中定义的字段值获取对应的参数值if (metaObject == null) {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 {// 将对应的参数值设置到对应的占位符顺序上typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException | SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}}}}

它本身的逻辑并不复杂,其中重要的组件 TypeHandler 我们来介绍下:对于字符串、整数、布尔值等基本数据类型的转换,Mybatis 框架定义了默认 TypeHandler 实现(StringTypeHandler, BooleanTypeHandler…),它其中定义了四个方法:

public interface TypeHandler<T> {// 用于将 Java 类型的数据设置到 JDBC 的 PreparedStatement 中,以便执行 SQL 语句时正确传递参数,替换掉对应顺序的占位符void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;// 用于从 ResultSet 中根据列名获取数据,并将其转换为Java类型T getResult(ResultSet rs, String columnName) throws SQLException;// 用于从 ResultSet 中根据列索引获取数据,并将其转换为Java类型T getResult(ResultSet rs, int columnIndex) throws SQLException;// 用于从 CallableStatement 存储过程中获取存储过程的输出参数,并将其转换为 Java 类型T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

不难发现,它的作用是将 Java 类型的数据设置到正确的占位符索引位置上;根据 SQL 查询结果,将数据库中数据转换为 Java 类型。在 TypeHandlerRegistry 可以看到默认初始化的类型处理器,这些处理器的实现使用了 策略模式和模板方法模式

在这里插入图片描述

如上图所示,TypeHandler 根据不同的 JavaType 来实现不同的策略,由于其中部分逻辑是通用的,所以抽出了抽象层定义方法模板来实现代码的复用。

现在 SimpleExecutor#prepareStatement 方法已经执行完毕了,这时 SQL 已经准备好了,对应的参数已经映射到对应的占位符上了,现在便是执行对应 SQL 的逻辑,它的执行流程在 JDBC 的 PreparedStatement#execute 方法中已经封装好了,感兴趣的同学可以去了解下,在这里我们需要看下 resultSetHandler.handleResultSets(ps) 处理结果的逻辑:

public class PreparedStatementHandler extends BaseStatementHandler {// ...@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();// 处理从数据库中返回的结果return resultSetHandler.handleResultSets(ps);}
}

结果由 ResultSetHandler 处理,它也是非常重要的组件之一。

ResultSetHandler

它包含以下三个方法,我们关注 ResultSetHandler#handleResultSets 方法即可:

public interface ResultSetHandler {/*** 处理 Statement 对象并返回结果对象** @param stmt SQL 语句执行后返回的 Statement 对象* @return 映射后的结果对象列表*/<E> List<E> handleResultSets(Statement stmt) throws SQLException;/*** 处理 Statement 对象并返回一个 Cursor 对象* 它用于处理从数据库中获取的大量结果集,与传统的 List 或 Collection 不同,Cursor 提供了一种流式处理结果集的方式,* 这在处理大数据量时非常有用,因为它可以避免将所有数据加载到内存中** @param stmt SQL 语句执行后返回的 Statement 对象* @return 游标对象,用于迭代结果集*/<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;/*** 处理存储过程的输出参数** @param cs 存储过程调用的 CallableStatement 对象*/void handleOutputParameters(CallableStatement cs) throws SQLException;
}

它的默认实现类是 DefaultResultSetHandler,将 ResultSet 转换成对应 Java 对象的核心逻辑(根据声明的数据库列和 Java 对象字段的映射关系来赋值),总体上比较简单,关注注释信息即可:

public class DefaultResultSetHandler implements ResultSetHandler {// ... private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {DefaultResultContext<Object> resultContext = new DefaultResultContext<>();ResultSet resultSet = rsw.getResultSet();skipRows(resultSet, rowBounds);// 逐个按行解析成 Java 对象while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);Object rowValue = getRowValue(rsw, discriminatedResultMap, null);storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}}private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {final ResultLoaderMap lazyLoader = new ResultLoaderMap();// 根据返回值对象类型调用其构造方法,该结果中所有字段未生成值Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {// 元对象final MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;// 根据配置信息是否处理自动字段和列的映射,默认为 tureif (shouldApplyAutomaticMappings(resultMap, false)) {// 在这里处理 result map 中没用定义的字段和列关系的映射// 在 Mybatis 框架下默认情况下,只有字段值和数据库列相同才能完成映射,如果想将数据库列转换成驼峰式的 Java 字段定义,需要配置 mapUnderscoreToCamelCase 为 truefoundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}// 根据 result mapping 中配置的字段和数据库列的映射关系,从 resultSet 中取值后封装给 metaObjectfoundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}return rowValue;}private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue,ResultMapping parentMapping, ResultSet rs) throws SQLException {if (parentMapping != null) {linkToParents(rs, parentMapping, rowValue);} else {callResultHandler(resultHandler, resultContext, rowValue);}}
}

在这里便完成数据库中数据和 Java 类对象的转换,转换完成后便是不断的方法返回,最终由 Mapper 接口返回结果。这样,一条简单 SQL 的查询便结束了,想要了解该过程非常需要大家 Debug 根据代码代码流程。我们来整理下时序图:

总结

在这里插入图片描述

每个声明 SQL 查询语句的 Mapper 接口都会被 MapperProxy 代理,接口中每个方法都会被定义为 MapperMethod 对象,借助 PlainMethodInvoker 执行。当方法被执行时,会先调用 SqlSession 中的查询方法,由 执行器 Executor 去承接,接下来会调用 SQL 处理器 StatementHandler 的方法完成 SQL 准备,而封装参数则由 参数处理器 DefaultParameterHandlerTypeHandler 完成,ResultSet 结果的处理:将数据库中数据转换成所需要的 Java 对象由 结果处理器 DefaultResultSetHandler 完成。


文章转载自:

http://TF6NHqmr.xpLng.cn
http://QDbLvWOg.xpLng.cn
http://nJguK2Y4.xpLng.cn
http://PsmR2SE7.xpLng.cn
http://oTh5PbWY.xpLng.cn
http://wFbAEpwU.xpLng.cn
http://192b9HEy.xpLng.cn
http://pcfKe778.xpLng.cn
http://2qEWzTHp.xpLng.cn
http://7uDzldw3.xpLng.cn
http://IEdnFPTW.xpLng.cn
http://N5sxiP6Z.xpLng.cn
http://X5r77W0s.xpLng.cn
http://7zhUpcK3.xpLng.cn
http://ageXZ8I1.xpLng.cn
http://lY8iyE75.xpLng.cn
http://sfVxaMf8.xpLng.cn
http://8tTgYaKW.xpLng.cn
http://MTIOzSiY.xpLng.cn
http://o053mQoS.xpLng.cn
http://bGdvK4Jp.xpLng.cn
http://sK7TSVg5.xpLng.cn
http://KYrsyESu.xpLng.cn
http://b6IZ6fJF.xpLng.cn
http://EoNKVydo.xpLng.cn
http://HkYIiaCo.xpLng.cn
http://pOn8Bi7m.xpLng.cn
http://OsMa4c6o.xpLng.cn
http://cOXZKUWj.xpLng.cn
http://IVBOwTdM.xpLng.cn
http://www.dtcms.com/wzjs/662867.html

相关文章:

  • 海参企业网站怎么做设置网站的关键词
  • 企业网站用什么技术做网站开发 平面设计
  • 上海网站空间续费程序员接单网站
  • 做离线版申报表进入哪个网站seo的工作原理
  • 黄山网站设计公司秦皇岛市建设局
  • 网站开发的技术可行性怎么写ppt网站建设答案
  • 网站菜单栏代码网站建设外包费用
  • 企业网站托管费用云建站自动建站系统源码
  • 自己做网站卖东西可以企业购网站建设
  • 家庭清洁东莞网站建设技术支持app定制网站开发
  • 江苏省建设考试网站营销网站制作信ls15227
  • 企业网站建设的定位东莞凤岗网站建设
  • 海口智能建站价格低多边形网站
  • 北京网站建设公司代理做网站排名收益
  • 团购酒店的网站建设外协加工网最新订单
  • 网站建设域名注册亚马逊推广
  • 建行官方网站淘宝网页版登录入口官方
  • 重庆垫江网站建设专业的传媒行业网站开发
  • 公司概况-环保公司网站模板网站建设规范方案
  • 网站pv访问量统计怎么做直播网站开发需要多少钱
  • 德阳建设局网站凌云县城乡建设局网站
  • 海南找人做网站山东东营网络seo
  • 公司创建一个网站多少钱可以直接玩游戏的网址
  • 查询网站后台地址龙口建设网站
  • wordpress怎么建网店杭州网站排名seo
  • 自助建站好吗如何干电商
  • 鄞州区网站建设报价公司网站是怎么做的
  • ...课程网站建设简介济宁网站建设找哪家
  • 有哪些做策划的用的网站深圳网站建设toolcat
  • 扫码支付做进商城网站免费金融发布网站模板下载