『手撕 Mybatis 源码』09 - MyBatis 插件

news/2024/4/23 21:20:09/文章来源:https://blog.csdn.net/zzz805/article/details/131370781

MyBatis插件

概述

  1. 问题:什么是Mybatis插件?有什么作用?
  • Mybatis插件本质上来说就是一个拦截器,它体现了 JDK 动态代理和责任链设计模式的综合运用
  1. Mybatis 中所允许拦截的方法如下
    在这里插入图片描述
  • Executor 【SQL执行器】【update,query,commit,rollback】
  • StatementHandler 【Sql语法构建器对象】【prepare,parameterize,batch,update,query 等】
  • ParameterHandler 【参数处理器】【getParameterObject,setParameters 等】
  • ResultSetHandler 【结果集处理器】【handleResultSets,handleOuputParameters 等】

自定义插件

  1. 用于定义插件的类
  • 指定要拦截哪个对象里面哪个方法
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {Signature[] value(); // 详细的配置要拦截那些对象里面的那些方法
}
  1. 由于一个拦截器可以同时拦截多个对象的多个方法,,所以就使用了 Signture 数组,该注解定义了拦截的完整信息
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {// 拦截的类Class<?> type(); // 举例:type = StatementHandler.class// 拦截的方法String method(); // 举例:method = "prepare"// 拦截方法的参数Class<?>[] args(); // 举例:agrs = Connection.class
}
  1. 已经知道了该拦截哪些对象的哪些方法,拦截后要干什么就需要实现 Intercetor#intercept 方法,在这个方法里面实现拦截后的处理逻辑
public interface Interceptor {/*** 真正方法被拦截执行的逻辑** @param invocation 主要目的是将多个参数进行封装需求*/Object intercept(Invocation invocation) throws Throwable;// 生成目标对象的代理对象default Object plugin(Object target) {return Plugin.wrap(target, this);}// 可以拦截器设置一些属性default void setProperties(Properties properties) {// NOP}
}
  1. 自定义插件
  • 需求:把 Mybatis 所有执行的 sql 都记录下来
  • 相关类
    • Invocation
public class Invocation {private final Object target; // 要拦截的对象,举例:statementHandlerprivate final Method method; // 拦截的对象的方法,举例:prepare()private final Object[] args; // 参数对象
}
  • 创建 Interceptor 的实现类
public class MyPlugin implements Interceptor {/*** 拦截方法:每次执行目标方法时,都会进入到 intercept 方法中* @param invocation :多个参数的封装类* @return* @throws Throwable*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 增强逻辑:将执行的 sql 进行记录(打印)StatementHandler statementHandler = (StatementHandler) invocation.getTarget();BoundSql boundSql = statementHandler.getBoundSql();String sql = boundSql.getSql();System.out.println("拦截方法,记录Sql:" + sql);return invocation.proceed();}/*** 将目标对象生成代理对象,添加到拦截器链中* * @param target :目标对象* @return*/@Overridepublic Object plugin(Object target) {// wrap 将目标对象,基于JDK动态代理生成代理对象return Plugin.wrap(target, this);}/*** 设置属性** @param properties 插件初始化的时候,会设置的一些值的属性集合*/@Overridepublic void setProperties(Properties properties) {System.out.println("插件配置的初始化参数:" + properties);}
}
  • 使用 @Intercepts 注解完成插件签名 说明插件的拦截四大对象之一的哪一个对象的哪一个方法
@Intercepts({@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class})
})
  • 将写好的插件注册到全局配置文件中
<configuration><plugins><plugin interceptor="com.itheima.interceptor.MyPlugin"><property name="someProperty" value="100"/></plugin></plugins>
</configuration>

插件源码分析

  1. 核心思想
  • 使用 JDK 动态代理的方式,对这四个对象进行包装增强
  • 具体的做法是,创建一个类实现 Mybatis 的拦截器接口,并且加入到拦截器链中
  • 在创建核心对象的时候,不直接返回,而是遍历拦截器链,把每一个拦截器都作用于核心对象中
  • 这么一来,Mybatis 创建的核心对象其实都是代理对象,都是被包装过的
    在这里插入图片描述
  1. 问题:
  • 插件对象是如何实例化的?
  • 插件的实例对象如何添加到拦截器链中的?
  • 组件对象的代理对象是如何产生的?
  • 拦截逻辑的执行
  1. 为了了解清楚上述的问题,首先先看解析配置文件部分
public class MybatisTest {...@Testpublic void test2() throws IOException {// 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中 注意:配置文件并没有被解析InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");// 2. (1) 解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);...}
}
  1. 下面直接跳过读入配置文件部分,进入解析 /plugins 标签部分
public class XMLConfigBuilder extends BaseBuilder {private void parseConfiguration(XNode root) {try {...// 解析 </plugins> 标签pluginElement(root.evalNode("plugins"));...} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}
}
  1. 读取 /plugins 标签后,首先获取自定义拦截器名称,然后获取配置的属性。根据配置文件中配置的插件类的全限定名,反射实例化对象,得到 Intercepetor 对象(也就是 MyPlugin),然后对于 Intercepetor 执行 setProperties() 方法,并把创建好的 Intercepetor 加入到 configuration 配置类中,这就解决第一个问题
public class XMLConfigBuilder extends BaseBuilder {protected final Configuration configuration;...private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {// 1. 获取拦截器String interceptor = child.getStringAttribute("interceptor");// 2. 获取配置的 Properties 属性Properties properties = child.getChildrenAsProperties();// 3. 根据配置文件中配置的插件类的全限定名 进行反射初始化Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();// 4. 将属性添加到 Intercepetor 对象interceptorInstance.setProperties(properties);// 5. 添加到配置类的 InterceptorChain 属性,InterceptorChain 类维护了一个 List<Interceptor>configuration.addInterceptor(interceptorInstance);}}}
}
  1. 接下来继续看是怎么把 Intercepetor 对象添加到插件链中,首先是由 Configuration 对象调用 addInterceptor() 方法,Configuration 对象会将 Intercepetor 对象添加到 InterceptorChain 对象中
public class Configuration {// mybatis 插件列表protected final InterceptorChain interceptorChain = new InterceptorChain();...public void addInterceptor(Interceptor interceptor) {interceptorChain.addInterceptor(interceptor);} 
}
  1. InterceptorChain 对象会把 Intercepetor 对象添加到集合 interceptors 里面,这里就回答了第 2 个问题,将实力添加到拦截链当中
public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<>();// 1. 添加到集合 interceptors 中public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}
}
  1. 通过配置完成 Configuration 对象并创建 SqlSessionFactory 后,然后执行 sqlSessionFactory.openSession() 创建 SqlSession 对象
public class MybatisTest {@Testpublic void test2() throws IOException {// 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中 注意:配置文件并没有被解析InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");// 2. (1) 解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);// 3.问题:openSession()执行逻辑是什么?// 3. (1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession();...}
}
  1. 当执行 openSession() 时,会调用 openSessionFromDataSource() 方法,交由 Configuration 对象来创建执行器对象,然后封装到 DefaultSqlSession 当中
public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;...@Overridepublic SqlSession openSession() {// 调用openSessionFromDataSource 参数1:执行器类型  参数2:事务隔离级别  参数三:指定事务是否自动提交return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);//# 获取默认执行类型,level:事务隔离级别, autoconmmit 是否自动提交}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {// 从configuration对象中获取environment对象final Environment environment = configuration.getEnvironment();// environment 里面封装 dataSource 和 事务工厂// 获得事务工厂对象final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);// 构建事务对象tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 看类型,才决定用哪个工厂,因为 xml 写的是 JBBC,所以用 JdbcTransactionFactory。如果是 Managed 就是交给容器来管理事务// 1. 创建执行器对象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();}
}
  • Configuration 对象根据传入的 ExecutorType,创建指定类型的 Executor,如果允许缓存的话,还会进一步装饰成 CachingExecutor。然后基于插件链,遍历插件,通过 JDK 动态代理的方式,封装新的 Executor
public class Configuration {// 默认执行器类型protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;// 是否启用缓存protected boolean cacheEnabled = true;// mybatis插件列表protected final InterceptorChain interceptorChain = new InterceptorChain();...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);}// 如果允许缓存,会通过 CachingExecutor 去代理一层if (cacheEnabled) { // 默认 trueexecutor = new CachingExecutor(executor);}// 1. 拦截器插件executor = (Executor) interceptorChain.pluginAll(executor);return executor;}
}
  • InterceptorChain 会遍历 interceptors 插件链对每个对象执行 plugins() 方法
public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<>();...public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {// 1. 对每个 target 执行 plugin() 方法target = interceptor.plugin(target);}return target;}
}
  • 因为只配置了一个自定义 Interceptor,所以会调用到 MyPluginplugin() 方法
public class MyPlugin implements Interceptor {.../*** 将目标对象生成代理对象,添加到拦截器链中** @param target :目标对象* @return*/@Overridepublic Object plugin(Object target) {// 1. wrap 将目标对象,基于JDK动态代理生成代理对象// 这里的 target 就是传进来的 CachingExecutorreturn Plugin.wrap(target, this);}
}
  • Plugin.wrap() 的执行首先会解析该拦截器所拦截的所有接口及对应拦截接口的方法,存入 signatureMap,然后拿到目标对象所有实现的接口,判断是否命中要拦截的接口,如果是的话,进行 JDK 动态代理返回,否则就返回原目标对象。因为实现的 MyPlugin 对象主要拦截 StatementHandlerprepare() 方法,所以就直接返回源对象。这个地方就解答了第 3 个问题,代理对象就是这么创建
public class Plugin implements InvocationHandler {...public static Object wrap(Object target, Interceptor interceptor) {// 1.解析该拦截器所拦截的所有接口及对应拦截接口的方法Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();// 2.获取目标对象实现的所有被拦截的接口Class<?>[] interfaces = getAllInterfaces(type, signatureMap);// 3.目标对象有实现被拦截的接口,生成代理对象并返回if (interfaces.length > 0) {// 4. 通过JDK动态代理的方式实现return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}// 5. 目标对象没有实现被拦截的接口,直接返回原对象return target;}private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {Set<Class<?>> interfaces = new HashSet<>();while (type != null) {for (Class<?> c : type.getInterfaces()) {if (signatureMap.containsKey(c)) {interfaces.add(c);}}type = type.getSuperclass();}return interfaces.toArray(new Class<?>[0]);}
}
  1. 创建完 sqlSession 后,从 sqlSession 中获取 UserMapper 代理对象并开始执行 findByCondition()
public class MybatisTest {@Testpublic void test2() throws IOException {// 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中 注意:配置文件并没有被解析InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");// 2. (1) 解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);// 3.问题:openSession()执行逻辑是什么?// 3. (1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession();// 4. JDK动态代理生成代理对象UserMapper mapperProxy = sqlSession.getMapper(UserMapper.class);// 5.代理对象调用方法User user = mapperProxy.findByCondition(1);...}
}
  1. 因为返回的 UserMapper 是个代理对象,所以首先创建 MapperMethod 来构建 PlainMethodInvoker,然后调用 MapperMethod 来执行 execute() 方法
public class MapperProxy<T> implements InvocationHandler, Serializable {...private static class PlainMethodInvoker implements MapperMethodInvoker {private final MapperMethod mapperMethod;public PlainMethodInvoker(MapperMethod mapperMethod) {super();this.mapperMethod = mapperMethod;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {// 1. 执行具体方法return mapperMethod.execute(sqlSession, args);}}
}
  1. 根据 SqlCommand 的方法类型选择执行 SqlSession 指定方法,这里是执行 SelectOne() 返回结果
public class MapperMethod {private final SqlCommand command;...public Object execute(SqlSession sqlSession, Object[] args) {Object result;// 判断mapper中的方法类型switch (command.getType()) {// 添加case INSERT: {...break;}// 更新case UPDATE: {...break;}// 删除。 对于增删改,都是update 操作case DELETE: {...break;}// 查询case SELECT:// 无返回结果,并且有ResultHandler方法参数,将查询结果交给ResultHandler进行处理if (method.returnsVoid() && method.hasResultHandler()) {...} else if (method.returnsMany()) {...// 执行查询、返回Map} else if (method.returnsMap()) {...// 执行查询、返回Cursor} else if (method.returnsCursor()) {...} else {// 转换参数Object param = method.convertArgsToSqlCommandParam(args);// 1. 查询单条result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:...break;default:throw new BindingException("Unknown execution method for: " + command.getName());}...return result;}
}
  1. 执行 SelectOne() 方法,实际是底层是调用 selectList(),并经过多轮嵌套以后,交由 Executor 来执行查询语句
public class DefaultSqlSession implements SqlSession {private final Configuration configuration;private final Executor executor;...@Overridepublic <T> T selectOne(String statement, Object parameter) {// 1. 执行嵌套的 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 {return 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) {// 参数4:结果集处理器return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);}private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {try {// 根据传入的 statementId 即 user.findUserById,获取 MappedStatement 对象MappedStatement ms = configuration.getMappedStatement(statement);// 2. 调用执行器的查询方法// wrapCollection(parameter)是用来装饰集合或者数组参数return executor.query(ms, wrapCollection(parameter), rowBounds, handler);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
}
  1. CachingExecutor 首先生成缓存键,尝试从缓存中拿到结果,如果没有,就委派包装的 SimpleExecutor 来执行获取结果
public class CachingExecutor implements Executor {private final Executor delegate;private final TransactionalCacheManager tcm = new TransactionalCacheManager();...//第一步@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 1. 获取绑定的 SQL 语句,比如 "SELECT * FROM user WHERE id = ? "BoundSql boundSql = ms.getBoundSql(parameterObject);// 2. 生成缓存 KeyCacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {// 获取二级缓存,需要在配置文件中配置 <cache></cache> 标签//   <select id="findUserById" parameterType="int" resultType="com.itheima.pojo.User" flushCache="true">Cache cache = ms.getCache();if (cache != null) {// 刷新(每次查询前清空)二级缓存 (存在缓存且 flushCache 为true时)flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) { // 默认就是 true    <select id="findUserById" parameterType="int" resultType="com.itheima.pojo.User" useCache="true">ensureNoOutParams(ms, boundSql);// 处理输出参数@SuppressWarnings("unchecked")// 3. 从二级缓存中查询数据List<E> list = (List<E>) tcm.getObject(cache, key);// 如果二级缓存中没有查询到数据,则查询一级缓存及数据库if (list == null) {// 4. 委托给 BaseExecutor 执行list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 将查询结果 要存到二级缓存中(注意:此处只是存到map集合中,没有真正存到二级缓存中)tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}// 委托给 BaseExecutor 执行return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}
}
  1. SimpleExecutor 尝试从一级缓存 localCache 获取数据,没有,就从数据库查询数据
public abstract class BaseExecutor implements Executor {protected PerpetualCache localCache;...@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.");}// 如果配置了flushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存if (queryStack == 0 && ms.isFlushCacheRequired()) {// 清空缓存clearLocalCache();}List<E> list;try {...// 1. 从一级缓存中获取数据list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {...} else {// 2. 没有缓存结果,则从数据库查询结果list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {// 查询堆栈数 -1queryStack--;}...return list;}private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;// 1. 首先向本地缓存中存入一个 ExecutionPlaceholder 的枚举类占位 valuelocalCache.putObject(key, EXECUTION_PLACEHOLDER);try {// 2. 执行doQuery方法list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {...}...return list;}
}
  1. SimpleExecutor 将会执行 doQuery() 方法,首先从 MappedStatement 中获取到 Configuration 对象,然后由 Configuration 对象创建 StatementHandler 来预处理 sql 语句后,再真正执行语句并返回执行结果
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();// 1. new一个StatementHandler实例StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 准备处理器,主要包括创建 statement 以及动态参数的设置stmt = prepareStatement(handler, ms.getStatementLog());// 执行真正的数据库操作调用return handler.query(stmt, resultHandler);} finally {// 关闭statementcloseStatement(stmt);}}
}
  1. Configuration 对象执行newStatementHandler() 时,根据参数从中创建不同类型的 RoutingStatementHandler
public class Configuration {// mybatis插件列表protected final InterceptorChain interceptorChain = new InterceptorChain();...public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 1. 创建路由功能的 StatementHandler,根据 MappedStatement 中的 StatementType,可以在 sql 中配置StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);// 插件机制:对核心对象进行拦截statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}
}
  1. RoutingStatementHandler 根据 MappedStatement 类型,选择创建指定的 StatementHandler,这里是创建 PreparedStatementHandler
public class RoutingStatementHandler implements StatementHandler {private final StatementHandler delegate;...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:// 1. 创建这个方法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());}}
}
  1. PreparedStatementHandler 的构造直接交由父类 BaseStatementHandler
public class PreparedStatementHandler extends BaseStatementHandler {public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 1. 创建交由父类super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);}
}
  1. BaseStatementHandler 又会通过 Configuration 来执行创建 ParameterHandler
public abstract class BaseStatementHandler implements StatementHandler {protected final Configuration configuration;protected final ObjectFactory objectFactory;protected final TypeHandlerRegistry typeHandlerRegistry;protected final ResultSetHandler resultSetHandler;protected final ParameterHandler parameterHandler;protected final Executor executor;protected final MappedStatement mappedStatement;protected final RowBounds rowBounds;protected BoundSql boundSql;...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 statementgenerateKeys(parameterObject);boundSql = mappedStatement.getBoundSql(parameterObject);}this.boundSql = boundSql;// 1. 创建 ParameterHandlerthis.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);}
}
  1. 关键】从创建 ParameterHandler 这一刻,就会调用 interceptorChainpluginAll() 来对 ParameterHandler 进行 JDK 代理配置,代理的逻辑在上面已经阐述过了,这里就不再重复
public class Configuration {// mybatis插件列表protected final InterceptorChain interceptorChain = new InterceptorChain();...public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);// 1. 执行代理parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;}
}
  1. 完成 ParameterHandler 创建后,BaseStatementHandler 还需要创建 ResultSetHandler
public abstract class BaseStatementHandler implements StatementHandler {protected final Configuration configuration;...protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {...this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);// 1. 创建 resultSetHandler this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);}
}
  1. 关键】从创建 ResultSetHandler 这一刻,就会调用 interceptorChainpluginAll() 来对 ResultSetHandler 进行 JDK 代理配置
public class Configuration {// mybatis插件列表protected final InterceptorChain interceptorChain = new InterceptorChain();...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);// 1. 执行代理resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}
}
  1. 关键】通过创建 ParameterHandlerResultSetHandler 等完成 RoutingStatementHandler 的构造并返回到 newStatementHandler() 方法,这时候也需要对 StatementHandler 核心对象进行拦截,执行 JDK 动态代理
public class Configuration {// mybatis插件列表protected final InterceptorChain interceptorChain = new InterceptorChain();...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);// 1. 插件机制:对核心对象进行拦截statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}
}
  1. 创建完 StatementHandlerSimpleExecutor 需要准备处理器,包括创建 Statement 以及动态参数的设置,这时候,就会调用 StatementHandlerprepare() 来预处理得到 Statement 对象
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();// new一个StatementHandler实例StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 1. 准备处理器,主要包括创建 statement 以及动态参数的设置stmt = prepareStatement(handler, ms.getStatementLog());// 执行真正的数据库操作调用return handler.query(stmt, resultHandler);} finally {// 关闭statementcloseStatement(stmt);}}private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 获取代理后(增加日志功能)的Connection对象Connection connection = getConnection(statementLog);// 2. 创建 Statement 对象(可能是一个 SimpleStatement,一个 PreparedStatement 或 CallableStatement)stmt = handler.prepare(connection, transaction.getTimeout());// 参数化处理handler.parameterize(stmt);// 返回执行前最后准备好的Statement对象return stmt;}
}
  1. 因为 StatementHandler 已经被代理了,这时候会调用 Plugininvoke(),然后执行 MyPluginintercept() 方法
public class Plugin implements InvocationHandler {private final Object target;private final Interceptor interceptor;...@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// 获取被拦截的方法 key:拦截的组件对象 value:拦截的组件对象的方法Set<Method> methods = signatureMap.get(method.getDeclaringClass());// 1. 如果当前执行的方法属于拦截方法,那就执行代理对象的方法interceptif (methods != null && methods.contains(method)) {return interceptor.intercept(new Invocation(target, method, args));}// 如果没有方法被代理,则调用原方法return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}
}
  1. 然后执行完成后就会返回查询结果,这个查询的具体流程完整可以参考 『手撕 Mybatis 源码』05 - SqlSession 执行主流程
  2. 总结
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_325573.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker服务编排之Docker Compose的使用

Docker服务编排 概念&#xff1a;按照一定的业务规则批量的管理容器 微服务架构的应用系统中一般包含很多微服务&#xff0c;一个微服务中又包含很多的实例&#xff0c;每个微服务都要手动管理&#xff0c;维护的工作量很大。 拉去镜像&#xff0c;创建多个容器&#xff0c;分…

【Vue3】学习笔记-自定义hook函数

概念 什么是hook? 本质是一个函数&#xff0c;把setup函数中使用的Composition API进行了封装。 类似于vue2.x中的mixin。(但是mixins会组件的配置项覆盖。vue3使用了自定义hooks替代mixnins&#xff0c;hooks本质上是函数&#xff0c;引入调用。) 自定义hook的优势: 复用代…

【密码学基础】半/全同态加密算法基础学习笔记

文章目录 1 半同态加密Pailliar加法同态加密Paillier加解密过程Paillier的同态性Paillier的安全性 El Gamal乘法同态加密RSA乘法同态加密 2 全同态加密BFV全同态加密BFV的编码方式BFV加解密过程BFV的安全性BFV的同态性自举Bootstrapping 3 同态加密应用场景场景1&#xff1a;安…

0基础学习VR全景平台篇 第54篇: 高级功能-皮肤

功能位置示意 一、本功能将用在哪里&#xff1f; 皮肤功能&#xff0c;摆脱传统VR全景展示样式&#xff0c;自行选择场景与全景分组的界面模板&#xff0c;从而与不同的应用行业风格相互适应&#xff0c;达到最贴切的展示效果。 是在各种风格的VR全景作品中&#xff0c;最快实…

ubuntu安装MobaXterm和WPS

文章目录 ubuntu安装MobaXtermi386 架构wine操作步骤 ubuntu安装WPS操作步骤WPS版本知识补充 ubuntu安装MobaXterm i386 架构 sudo dpkg --add-architecture i386 是一个Linux系统中的命令&#xff0c;用于添加一个新的架构&#xff08;architecture&#xff09;支持到当前系统…

netwox构造免费ARP数据包【网络工程】(保姆级图文)

目录 构造免费的 ARP 数据包。1) 构造免费的 ARP 数据包2) 使用 Wireshark 进行抓包 总结 欢迎关注 『网络工程专业』 系列&#xff0c;持续更新中 欢迎关注 『网络工程专业』 系列&#xff0c;持续更新中 温馨提示&#xff1a;对虚拟机做任何设置&#xff0c;建议都要先快照备…

IDEA中集成zookeeper的插件

IDEA中集成zookeeper的插件 一、IDEA中集成插件 搜索插件并安装&#xff1a; 安装完成&#xff0c;重启IDEA 配置zk集群 连接成功

【PortAudio】PortAudio 音频处理库Demo

1. 介绍 PortAudio是一个免费、跨平台、开源的音频I/O库。看到I/O可能就想到了文件&#xff0c;但是PortAudio操作的I/O不是文件&#xff0c;而是音频设备。它能够简化C/C的音频程序的设计实现&#xff0c;能够运行在Windows、Macintosh OS X和UNIX之上&#xff08;Linux的各种…

从零开始 Spring Boot 57:JPA中的一对多关系

从零开始 Spring Boot 57&#xff1a;JPA中的一对多关系 图源&#xff1a;简书 (jianshu.com) 在上篇文章中我们介绍了如何在 JPA 中实现实体的一对一关系&#xff0c;在关系型数据库设计中&#xff0c;除了一对一关系&#xff0c;还存在一对多关系。本篇文章介绍如何在 JPA 中…

【Python】NLP参数控制模板

前言 学过AI的都知道训练一个模型需要调整很多参数&#xff0c;为了有效的管理这些参数、不至于让代码的参数写的乱七八糟&#xff0c;有必要写一套控制参数的模板。 argparser argparser是python当中的参数解析器&#xff0c;在NLP当中主要是用来接受和使用参数的。一个使用它…

QT学习笔记:TCP客户端的实现

QT一般用来做客户端&#xff0c;我这里就简单讲一下怎么开发基于QT的TCP客户端。 1、用QtCreator创建项目 2、界面 3、.pro文件添加network QT core gui network 4、mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include &l…

SpringBoot的缓存管理

缓存是分布式系统中的重要组件&#xff0c;主要解决数据库数据的高并发访问问题。在实际开发中&#xff0c;尤其是用户 访问量较大的网站&#xff0c;为了提高服务器访问性能、减少数据库的访问压力、提高用户体验&#xff0c;使用缓存显得 尤为重要。Spring Boot对缓存提供了良…

基于单片机的盲人导航智能拐杖老人防丢防摔倒发短息定位

功能介绍 以STM32单片机作为主控系统&#xff1b; OLED液晶当前实时距离&#xff0c;安全距离&#xff0c;当前经纬度信息&#xff1b;超声波检测小于设置的安全距离&#xff0c;蜂鸣器报警提示&#xff1a;低于安全距离&#xff01;超声波检测当前障碍物距离&#xff0c;GPS进…

从零开始备战数学建模国赛之线性规划1.1

从零开始备战数学建模国赛之线性规划1.1 现在距离2023年的数学建模国赛还有不足三个月的时间&#xff0c;想与大家共同备战国赛。 这是我自己总结的一些代码和资料&#xff08;本文中的代码以及参考书籍等&#xff09;&#xff0c;放在github上供大家参考&#xff1a;https://…

Redis学习(一)数据类型、Java中使用redis、缓存概念

文章目录 常用数据结构String类型Hash类型List类型Set类型SortedSet 类型 通用命令key的层级结构 Spring Data Redis快速入门RedisTemplate的序列化方式StringRedisTemplateRedisTemplate的Hash类型操作 实战操作短信登录发送验证码校验登录信息校验登录状态 商家查询缓存缓存更…

怎么学习PHP表单处理与验证? - 易智编译EaseEditing

要学习PHP表单处理与验证&#xff0c;可以按照以下步骤进行&#xff1a; 掌握PHP基础知识&#xff1a; 在学习PHP表单处理与验证之前&#xff0c;首先需要对PHP编程语言有基本的了解。学习PHP的语法、变量、数据类型、数组、函数等基础知识是必要的。 学习HTML表单&#xff1…

MySQL - 第13节 - MySQL用户管理

1.MySQL用户管理概念 MySQL用户管理概念&#xff1a; • 与Linux操作系统类似&#xff0c;MySQL中也有超级用户和普通用户之分。 • 如果一个用户只需要访问MySQL中的某一个数据库&#xff0c;甚至数据库中的某一个表&#xff0c;那么可以为其创建一个普通用户&#xff0c;并为…

交流220v转12v给单片机供电芯片

客户的应用需求&#xff1a;AD220V转DC12V 体积要非常小&#xff0c;单片机使用&#xff0c;单片机设备12V 电流很小不会超过100mA&#xff1f; 【AD220V转DC12V体积小的问题】 问题&#xff1a;我需要将交流电&#xff08;220V&#xff09;转换为直流电&#xff08;12V&…

【CSS】CSS使用变量与变量定义

如何定义可以在CSS中使用的变量 CSS变量&#xff08;也称为自定义属性&#xff09;的定义规则如下&#xff1a; 使用–作为前缀&#xff0c;后跟变量名。变量名可以由字母、数字、连字符和下划线组成&#xff0c;并且不能以连字符开头。变量名区分大小写。变量定义在选择器范…

Arrays类概述,Lambda表达式

数组操作工具类&#xff0c;专门用于操作数组元素 2&#xff1a;常用API Lambda概述 Lambda表达式是JDK开始后的一种新语法形式作用&#xff1a;简化匿名内部类的代码写法 格式&#xff1a; 注意&#xff1a;Lambda表达式只能简化函数式接口的匿名内部类的写法形式。 什么是…