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

MyBatis源码分析(三)SqlSession的执行主流程

文章目录

  • 一、熟悉主要接口
  • 二、SqlSession的获取
    • 1、通过数据源获取SqlSession
  • 三、Mapper的获取与代理
    • 1、从SqlSession获取Mapper
    • 2、执行Mapper方法前准备逻辑
    • 3、SqlCommand的创建
    • 4、构造MethodSignature
  • 四、执行Mapper的核心方法
    • 1、执行Mapper的方法逻辑
  • 五、简单SELECT处理过程
    • 1、对参数进行解析
    • 2、DefaultSqlSession的selectOne逻辑
    • 3、Executor的query
    • 4、查询数据库的逻辑
      • (1)prepareStatement
    • 5、StatementHandler的query方法
    • 6、处理结果集
      • (1)处理结果集
      • (2)创建映射结果对象
      • (3)根据columnName和type属性名映射赋值
      • (4)applyPropertyMappings
  • 写在后面

一、熟悉主要接口

DefaultSqlSession:SqlSession接口的默认实现类

Executor接口:
在这里插入图片描述
BaseExecutor:基础执行器,封装了子类的公共方法,包括一级缓存、延迟加载、回滚、关闭等功能;

SimpleExecutor:简单执行器,每执行一条 sql,都会打开一个 Statement,执行完成后关闭;

ReuseExecutor:重用执行器,相较于 SimpleExecutor 多了 Statement 的缓存功能,其内部维护一个 Map<String, Statement> ,每次编译完成的 Statement 都会进行缓存,不会关闭;

BatchExecutor:批量执行器,基于 JDBC 的 addBatch、executeBatch 功能,并且在当前 sql 和上一条 sql 完全一样的时候,重用Statement,在调用doFlushStatements 的时候,将数据刷新到数据库;

CachingExecutor:缓存执行器,装饰器模式,在开启缓存的时候。会在上面三种执行器的外面包上 CachingExecutor;

StatementHandler接口:
在这里插入图片描述
在这里插入图片描述

MyBatis实际使用的StatementHandler,就是RoutingStatementHandler,拦截器拦截的也是RoutingStatementHandler。它会根据Exector类型创建对应的StatementHandler,保存到属性delegate中,所以插件分离还要分离出具体的StatementHandler。

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 实例。

RoutingStatementHandler:路由。Mybatis实际使用的类,拦截的StatementHandler实际就是它。它会根据Exector类型创建对应的StatementHandler,保存到属性delegate中;

ResultSetHandler接口:处理Statement执行后产生的结果集,生成结果列表;处理存储过程执行后的输出参数;

DefaultResultSetHandler:ResultSetHandler的 默认实现类。

二、SqlSession的获取

SqlSessionFactory接口的默认实现是DefaultSqlSessionFactory,其中实现了很多openSession的重载方法用来获取SqlSession,有几个关键的参数:

// DefaultSqlSessionFactory的重载方法
@Override
public SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {return openSessionFromDataSource(execType, null, autoCommit);
}

① ExecutorType

public enum ExecutorType {SIMPLE, REUSE, BATCH // 默认是SIMPLE
}

② autoCommit

// 是否自动提交
boolean autoCommit

③ TransactionIsolationLevel事务等级

public enum TransactionIsolationLevel {NONE(Connection.TRANSACTION_NONE),READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);
}

1、通过数据源获取SqlSession

// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;// 包装数据库连接。处理连接生命周期,包括:连接的创建、准备、提交/回滚和关闭。try {final Environment environment = configuration.getEnvironment();// 在配置文件中或者API方式定义默认的JdbcTransactionFactoryfinal TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType); // 创建执行器return new DefaultSqlSession(configuration, executor, autoCommit); // 创建默认的SqlSession} 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();}
}

我们来看一下创建执行器的逻辑:

// org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 默认是SIMPLEExecutor 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);}// 责任链模式,使用JDK动态代理增强所有的拦截器,为后续的拦截器的使用做准备executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}

三、Mapper的获取与代理

1、从SqlSession获取Mapper

官方文档中提供了一种使用SqlSession做查询的实例:

try (SqlSession session = sqlSessionFactory.openSession()) {Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

但是推荐使用下面的方式进行处理:

try (SqlSession session = sqlSessionFactory.openSession()) {BlogMapper mapper = session.getMapper(BlogMapper.class);Blog blog = mapper.selectBlog(101);
}

我们对DefaultSqlSession的getMapper方法进行分析:

// org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);
}// org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);
}// org.apache.ibatis.binding.MapperRegistry#getMapper
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 {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}
}

我们发现,就是根据接口的Class类型,从MapperRegistry中获取,在SqlSessionFactory接口中我们分析到,在创建SqlSessionFactory时,会在MapperRegistry存放一个MapperProxyFactory,此时我们会直接拿出来这个MapperProxyFactory。

最终调用mapperProxyFactory.newInstance方法:

// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);
}

最终使用就JDK动态代理,生成了一个代理类,用于处理mapper的方法。

2、执行Mapper方法前准备逻辑

上面说到,从SqlSession中获取的Mapper是被JDK动态代理过的(JDK动态代理请自行学习),当执行Mapper接口的方法时,会调用其关键方法为MapperProxy的invoke方法:

// org.apache.ibatis.binding.MapperProxy#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {// Object的方法直接放行,不需要代理return method.invoke(this, args);} else { // 调用invoke方法return cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}
}// org.apache.ibatis.binding.MapperProxy#cachedInvoker
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {try {// 如果缓存没有的话,将方法放入缓存return MapUtil.computeIfAbsent(methodCache, method, m -> {if (m.isDefault()) {// 如果是default方法,用这种方式执行try {if (privateLookupInMethod == null) {return new DefaultMethodInvoker(getMethodHandleJava8(method));} else {return new DefaultMethodInvoker(getMethodHandleJava9(method));}} catch (IllegalAccessException | InstantiationException | InvocationTargetException| NoSuchMethodException e) {throw new RuntimeException(e);}} else { // 正常的方法,会创建PlainMethodInvoker,装饰MapperMethodreturn new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}});} catch (RuntimeException re) {Throwable cause = re.getCause();throw cause == null ? re : cause;}
}

最终会new一个MapperMethod,MapperMethod构造方法中包含着两个关键对象的创建:

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

3、SqlCommand的创建

我们看一下SqlCommand的构造方法

// org.apache.ibatis.binding.MapperMethod.SqlCommand#SqlCommand
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {final String methodName = method.getName();final Class<?> declaringClass = method.getDeclaringClass();// 获取MappedStatementMappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);if (ms == null) {// Flush注解标注的接口if (method.getAnnotation(Flush.class) != null) {name = null;type = SqlCommandType.FLUSH;} else { // 没找到XML对应的statementthrow new BindingException("Invalid bound statement (not found): "+ mapperInterface.getName() + "." + methodName);}} else { // 查找到MappedStatementname = ms.getId();type = ms.getSqlCommandType();if (type == SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method for: " + name);}}
}

在resolveMappedStatement方法中,有以下逻辑:

// org.apache.ibatis.binding.MapperMethod.SqlCommand#resolveMappedStatement
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,Class<?> declaringClass, Configuration configuration) {// 接口全限定名 + 方法名,就是statementIdString statementId = mapperInterface.getName() + "." + methodName;if (configuration.hasStatement(statementId)) { // 在addMapper时就会解析xmlreturn configuration.getMappedStatement(statementId);} else if (mapperInterface.equals(declaringClass)) {return null;}// 查询父接口for (Class<?> superInterface : mapperInterface.getInterfaces()) {if (declaringClass.isAssignableFrom(superInterface)) {MappedStatement ms = resolveMappedStatement(superInterface, methodName,declaringClass, configuration);if (ms != null) {return ms;}}}return null;
}

此时我们获取了MappedStatement并且包装好了SqlCommand。

到这,我们应该了解了MyBatis的Statement,其实和JDBC的Statement的含义有些区别,MyBatis的Statement其实就是代表着XML中的每一个SELECT、UPDATE、DELETE等标签中的SQL信息。

SqlCommand中包含着Mapper.xml的id与SQL类型。

4、构造MethodSignature

// MethodSignature的构造方法
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);// 设置返回值类型if (resolvedReturnType instanceof Class<?>) {this.returnType = (Class<?>) resolvedReturnType;} else if (resolvedReturnType instanceof ParameterizedType) {this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();} else {this.returnType = method.getReturnType();}// 是否返回voidthis.returnsVoid = void.class.equals(this.returnType);// 是否返回多条this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();// 是否返回游标this.returnsCursor = Cursor.class.equals(this.returnType);// 是否返回Optionalthis.returnsOptional = Optional.class.equals(this.returnType);this.mapKey = getMapKey(method);this.returnsMap = this.mapKey != null;this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);this.paramNameResolver = new ParamNameResolver(configuration, method);
}

处理一些方法签名相关信息。

四、执行Mapper的核心方法

上面我们分析到,使用PlainMethodInvoker包装了MapperMethod,PlainMethodInvoker的invoke方法(JDK动态代理的)最终会调用MapperMethod的execute方法:

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 {return mapperMethod.execute(sqlSession, args);}
}

1、执行Mapper的方法逻辑

正常的方法会调用MapperMethod的execute方法,该方法中包含着对sql的操作增删改查:

// org.apache.ibatis.binding.MapperMethod#execute
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:// 返回值为voidif (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) { // 返回多个result = executeForMany(sqlSession, args);} else if (method.returnsMap()) { // 返回Mapresult = executeForMap(sqlSession, args);} 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;case FLUSH: // FLUSHresult = 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;
}

我们此处按照官方文档,只分析简单的SELECT查询,其他同理。

五、简单SELECT处理过程

1、对参数进行解析

先执行convertArgsToSqlCommandParam方法对参数进行解析:

// org.apache.ibatis.binding.MapperMethod.MethodSignature#convertArgsToSqlCommandParam
public Object convertArgsToSqlCommandParam(Object[] args) {return paramNameResolver.getNamedParams(args);
}// org.apache.ibatis.reflection.ParamNameResolver#getNamedParams
public Object getNamedParams(Object[] args) {final int paramCount = names.size();if (args == null || paramCount == 0) { // mapper中的sql不需要参数return null;} else if (!hasParamAnnotation && paramCount == 1) { // mapper中的sql需要1个参数Object value = args[names.firstKey()];return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);} else { // mapper中的sql需要多个参数final Map<String, Object> param = new ParamMap<>();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 + (i + 1);// ensure not to overwrite parameter named with @Paramif (!names.containsValue(genericParamName)) {param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}
}// org.apache.ibatis.reflection.ParamNameResolver#wrapToMapIfCollection
// 只需要一个参数时,需要判断是否是集合、数组或者普通类型
public static Object wrapToMapIfCollection(Object object, String actualParamName) {if (object instanceof Collection) { ParamMap<Object> map = new ParamMap<>();map.put("collection", object);if (object instanceof List) {map.put("list", object);}Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;} else if (object != null && object.getClass().isArray()) {ParamMap<Object> map = new ParamMap<>();map.put("array", object);Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;}return object;
}

2、DefaultSqlSession的selectOne逻辑

解析完参数之后,然后执行DefaultSqlSession的selectOne逻辑:

// org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)
@Override
public <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);} 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;}
}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)
@Override
public <E> List<E> selectList(String statement, Object parameter) {// RowBounds:返回结果集最大值return this.selectList(statement, parameter, RowBounds.DEFAULT);
}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}// org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {try {// 获取到已经处理过的MappedStatement ,就是对应Mapper中的sql等信息MappedStatement ms = configuration.getMappedStatement(statement);// 最终调用执行器的query// RowBounds是用来逻辑分页(按照条件将数据从数据库查询到内存中,在内存中进行分页)// 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();}
}

3、Executor的query

Executor接口中包含了增删改查以及提交回滚与对缓存的处理,是真正的sql执行器。

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

Configuration中cacheEnabled属性值默认为true,会执行CachingExecutor的query方法:

org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 获取SQL基本信息,sql参数会变成 ? 比如“SELECT * FROM user WHERE id = ? ”BoundSql boundSql = ms.getBoundSql(parameter);// 获取缓存key,通过算法确保key的唯一性CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}// org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.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) {// 当为select语句时,flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存// 当为insert、update、delete语句时,useCache默认为true,表示会将本条语句的结果进行二级缓存// 刷新二级缓存 (存在缓存且flushCache为true时)flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);// // 从二级缓存中查询数据@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);// 如果二级缓存中没有查询到数据,则查询数据库if (list == null) {// 委托给BaseExecutor执行list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}// 委托给BaseExecutor执行return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}// BaseExecutor执行query
// org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
@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++;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;
}

4、查询数据库的逻辑

如果一级缓存没有数据,则从数据库查询数据。

// org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
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;
}

在doQuery方法中,最终执行的是SimpleExecutor的doQuery方法,正式进行JDBC那一套了:

// org.apache.ibatis.executor.SimpleExecutor#doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null; // java.sql.Statement try {// 获取Configuration对象Configuration configuration = ms.getConfiguration();// 创建RoutingStatementHandler,用来处理Statement// RoutingStatementHandler类中初始化delegate类(SimpleStatementHandler、PreparedStatementHandler)StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 子流程1:设置参数stmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {// Statement关闭closeStatement(stmt);}
}

(1)prepareStatement

我们看一下准备Statement的逻辑:

// org.apache.ibatis.executor.SimpleExecutor#prepareStatement
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());// 相关参数处理handler.parameterize(stmt);return stmt;
}

获取连接的逻辑:

// org.apache.ibatis.executor.BaseExecutor#getConnection
protected Connection getConnection(Log statementLog) throws SQLException {Connection connection = transaction.getConnection(); // 从事务管理器中拿到连接if (statementLog.isDebugEnabled()) {return ConnectionLogger.newInstance(connection, statementLog, queryStack);} else {return connection;}
}// org.apache.ibatis.transaction.jdbc.JdbcTransaction#getConnection
@Override
public Connection getConnection() throws SQLException {if (connection == null) {openConnection();}return connection;
}protected void openConnection() throws SQLException {if (log.isDebugEnabled()) {log.debug("Opening JDBC Connection");}connection = dataSource.getConnection();if (level != null) {connection.setTransactionIsolation(level.getLevel());}setDesiredAutoCommit(autoCommit);
}

我们发现,获取链接就是默认从DataSource中获取链接。

MyBatis中对Connection 的管理是交给Transaction接口管理的,Transaction接口包含了对Connection 的获取、提交回滚、关闭等操作。
在其实现类中,实际上最终的获取和关闭Connection ,是由DataSource来处理的,相当于使用Transaction接口做了一层包装。

拿到Connection 之后,就开始获取Statement了:

// org.apache.ibatis.executor.statement.BaseStatementHandler#prepare
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {// connection.createStatement() 创建Statementstatement = 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);}
}

获取完Statement,开始设置参数:

// org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize
@Override
public void parameterize(Statement statement) throws SQLException {
// 通过ParameterHandler处理参数parameterHandler.setParameters((PreparedStatement) statement);
}// org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
@Override
public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != 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 {MetaObject 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方法typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException | SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}}
}

到此时,Statement已经准备就绪了,也通过typeHandler将参数注入到Statement中了。

5、StatementHandler的query方法

// org.apache.ibatis.executor.statement.RoutingStatementHandler#query
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {return delegate.query(statement, resultHandler);
}// org.apache.ibatis.executor.statement.PreparedStatementHandler#query
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute(); // 执行,如果是true就可以拿到ResultSetreturn resultSetHandler.handleResultSets(ps); // 提取结果集
}

6、处理结果集

execute执行完毕之后就是拿到结果集了:

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
@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);
}

(1)处理结果集

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {try {if (parentMapping != null) {// 处理行数据handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);} else {if (resultHandler == null) {DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); // 处理行数据multipleResults.add(defaultResultHandler.getResultList());} else {// 处理行数据handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);}}} finally {// 关闭ResultSet// issue #228 (close resultsets)closeResultSet(rsw.getResultSet());}
}// org.apache.ibatis.executor.resultset.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);}
}// org.apache.ibatis.executor.resultset.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);// 处理对象嵌套的映射关系// 使用ResultHandler进行结果集映射storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}
}// 将查询结果封装到POJO中
// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.lang.String)
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); // 创建MetaObjectboolean foundValues = this.useConstructorMappings;// 是否应用自动映射,也就是通过resultType进行映射if (shouldApplyAutomaticMappings(resultMap, false)) {// 根据columnName和type属性名映射赋值(Mapper中的resultType映射)foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}// 根据我们配置ResultMap的column和property映射赋值,也就是Mapper中的resultMap映射// 如果映射存在nestedQueryId,会调用getNestedQueryMappingValue方法获取返回值foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}return rowValue;
}

(2)创建映射结果对象

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, org.apache.ibatis.executor.loader.ResultLoaderMap, java.lang.String)
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {this.useConstructorMappings = false; // reset previous mapping resultfinal 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) {// issue gcode #109 && issue #149// 延迟加载处理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 resultreturn resultObject;
}// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.util.List<java.lang.Class<?>>, java.util.List<java.lang.Object>, java.lang.String)
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);}throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

(3)根据columnName和type属性名映射赋值

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyAutomaticMappings
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)applyPropertyMappings

根据我们配置ResultMap的column和property映射赋值,如果映射存在nestedQueryId,会调用getNestedQueryMappingValue方法获取返回值

// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyPropertyMappings
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 itcolumn = 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 optionalfinal 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;
}

写在后面

如果本文对你有帮助,请点赞收藏关注一下吧 ~
在这里插入图片描述

相关文章:

MyBatis源码分析(三)SqlSession的执行主流程

文章目录一、熟悉主要接口二、SqlSession的获取1、通过数据源获取SqlSession三、Mapper的获取与代理1、从SqlSession获取Mapper2、执行Mapper方法前准备逻辑3、SqlCommand的创建4、构造MethodSignature四、执行Mapper的核心方法1、执行Mapper的方法逻辑五、简单SELECT处理过程1…...

环境搭建01-Ubuntu16.04如何查看显卡信息及安装NVDIA显卡驱动

1. 查看显卡型号、驱动 ubuntu-drivers devices2. 安装NVIDIA显卡驱动 &#xff08;1&#xff09;验证是否禁用nouveau lsmod | grep nouveau若有输出&#xff0c;没有禁用&#xff0c;进行以下操作禁用。 sudo gedit /etc/modprobe.d/blacklist.conf在文件末尾中添加两条&…...

内网渗透测试理论学习之第四篇内网渗透域的横向移动

文章目录一、IPC二、HashDump三、PTH四、PTT五、PsExec六、WMI七、DCOM八、SPN九、Exchange在内网中&#xff0c;从一台主机移动到另外一台主机&#xff0c;可以采取的方式通常有文件共享、计划任务、远程连接工具、客户端等。 一、IPC IPC&#xff08;Internet Process Conn…...

20 | k8s v1.20集群搭建master和node

1 单节点master 1.1 服务器整体规划 1.2 单Master架构图 1.3 初始化配置 1.3.1 关闭防火墙 systemctl stop firewalld systemctl disable firewalld1.3.2 关闭selinux sed -i s/enforcing/disabled/ /etc/selinux/config # 永久 setenforce 0 # 临时 1.3.3 关闭swap …...

《商用密码应用与安全性评估》第一章密码基础知识1.1应用概念

密码的概念与作用 概念 密码&#xff1a;采用特定变换的方法对信息进行加密保护、安全认证的技术、产品和服务。 密码技术&#xff1a;密码编码、实现、协议、安全防护、分析破译、以及密钥产生、分发、传递、使 用、销毁等技术。 密码技术核心&#xff1a;密码算法…...

【博学谷学习记录】超强总结,用心分享丨人工智能 深度学习 神经网络基础知识点总结

目录神经网络激活函数引入激活函数原因&#xff1a;sigmoid激活函数tanh 激活函数ReLU 激活函数&#xff08;最常用&#xff09;SoftMax如何选择反向传播参数初始化方法优化方法正则化批量归一层网络模型调优的思路神经网络 简单的神经网络包括三层&#xff1a;输入层&#xf…...

Python+tkinter添加滚动条

大家好&#xff0c;我是IKUN的真爱粉&#xff0c;有时候我们需要在tkinter上加滚动条&#xff0c;那么怎么制作呢&#xff0c;我们先看下面的视频展示效果&#xff0c;是不是你想要的 展示 感觉制作的略微粗糙&#xff0c;各位可以后期自己慢慢调整 创建滚动条重要的步骤是&a…...

大V龚文祥造谣董明珠恋情被禁言

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 因造谣董明珠与王自如恋情&#xff0c;知名大V龚文祥老师被今日头条禁言。龚文祥说&#xff0c;69岁的董明珠&#xff0c;找了一个小自己34岁的男友&#xff0c;引的网友议论纷纷。 2月26日&#…...

深入浅出Reactjs

深入浅出Reactjs 介绍 React是一个流行的JavaScript库&#xff0c;用于开发复杂的用户界面。它可以帮助开发人员构建灵活、高效和可维护的应用程序。本文将深入浅出地介绍React开发框架。 React的核心概念 React框架的核心概念是组件。组件是一个独立的、可复用的代码块&am…...

《C++ Primer Plus》第18章:探讨 C++ 新标准(1)

本章首先复习前面介绍过的C11功能&#xff0c;然后介绍如下主题&#xff1a; 移动语义和右值引用。Lambda 表达式。包装器模板 function。可变参数模板。 本章重点介绍 C11 对 C 所做的改进。本书前面介绍过多项 C11 功能&#xff0c;本章首先复习这些功能&#xff0c;并详细…...

PCB板漏孔、漏槽怎么办?看工程师避坑“SOP”

本文为大家介绍PCB画板时常见的钻孔问题&#xff0c;避免后续踩同样的坑。钻孔分为三类&#xff0c;通孔、盲孔、埋孔。不管是哪种孔&#xff0c;孔缺失的问题带来的后果是直接导致整批产品不能使用。因此钻孔设计的正确性尤为重要。 案例讲解 问题1&#xff1a;Altium设计的文…...

mysql数据库同步方案:springboot+集成cannal

1授权 -- 使用命令登录&#xff1a;mysql -u root -p -- 创建用户 用户名&#xff1a;canal 密码&#xff1a;Canal123456 create user canal% identified by Canal123456; -- 授权 *.*表示所有库 grant SELECT, REPLICATION SLAVE, REPLICATION CLIENT on *.* to canal% ident…...

oracle 19c 创建物化视图并测试logminer进行日志挖掘

1、创建物化视图 alter session set containerpdb; grant create materialized view to scott; create materialized view 物化视图名 -- 1. 创建物化视图 build [immediate | deferred] -- 2. 创建方式&#xff0c;默认 immediate refre…...

2.1 黑群晖驱动:10代u核显硬解驱动(解决掉IP、重启无法连接问题)

本文提供了两种10代核显驱动方式&#xff1a;1&#xff09;第一种(本文&#xff1a;二、仅修改i915.ko驱动10代u核显方法)为网上流传最多但是对主板兼容性要求很高&#xff0c;网上评论常会出现操作后无法识别IP&#xff08;掉IP&#xff09;的问题。因此&#xff0c;采用第一种…...

二、CSS

一、CSSHTML的结合方式 1、第一种&#xff1a;在标签的style属性上设置"key:value value;"&#xff0c;修改标签样式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title>…...

变分推断 (Variational Inference) 解析

前言 如果你对这篇文章可感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 变分推断 在贝叶斯方法中&#xff0c;针对含有隐变量的学习和推理&#xff0c;通常有两类方式&#xff0c;其一是马尔可…...

27. 移除元素

题目链接&#xff1a;https://leetcode.cn/problems/remove-element/给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输…...

hive临时目录清理

hive运行失败会导致临时目录无法自动清理&#xff0c;因此需要自己写脚本去进行清理 实际发现hive临时目录有两个&#xff1a; /tmp/hive/{user}/* /warehouse/tablespace//hive/**/.hive-staging_hive 分别由配置hive.exec.scratchdir和hive.exec.stagingdir决定: 要注意的…...

如何创建发布新品上市新闻稿

推出新产品对任何企业来说都是一个激动人心的时刻&#xff0c;但向潜在客户宣传并围绕您的新产品引起轰动也可能是一个挑战。最有效的方法之一就是通过发布新品上市新闻稿。精心制作的新闻稿可以帮助我们通过媒体报道、吸引并在目标受众中引起关注。下面&#xff0c;我们将讲述…...

关于.bashrc和setup.bash的理解

在创建了ROS的workspace后&#xff0c;需要将workspace中的setup.bash文件写入~/.bashrc 文件中&#xff0c;让其启动&#xff1a; source /opt/ros/melodic/setup.bash这句话的目的就是在开新的terminal的时候&#xff0c;运行这个setup.bash&#xff0c;而这个setup.bash的作…...

03 Android基础--fragment

03 Android基础--fragment什么是fragment&#xff1f;fragment生命周期&#xff1f;动态的fragment与静态的fragmentfragment常用的两个类与APIFragment与Activity通信什么是fragment&#xff1f; 碎片&#xff0c;一个activity中可以使用多个fragment&#xff0c;可以把activi…...

Redis使用,AOF、RDB

前言 如果有人问你&#xff1a;"你会把 Redis 用在什么业务场景下&#xff1f;" 我想你大概率会说&#xff1a;"我会把它当作缓存使用&#xff0c;因为它把后端数据库中的数据存储在内存中&#xff0c;然后直接从内存中读取数据&#xff0c;响应速度会非常快。…...

SOLIDWORKS Premium 2023 SP1.0 三维设计绘图软件

SOLIDWORKS 中文完美正式版提供广泛工具来处理最复杂的问题,并提供深层技术完成关键细节工作。新功能可助您改善产品开发流程,以更快地将创新产品投入生产。Solidworks 是达索公司最新推出的三维CAD系统,它可让设计师大大缩短产品的设计时间,让产品得以快速、高效地投向市场…...

PyQGIS开发--自动化地图布局案例

前言创建地图布局是 GIS 作业结束时的一项常见任务。 它用于呈现最终结果的输出&#xff0c;作为与用户交流的一种方式&#xff0c;以便从地图中获取信息、知识或见解。 在包括 QGIS 在内的任何 GIS 软件中制作地图布局都非常容易。 但另一方面&#xff0c;当我们必须生成如此大…...

严格模式和非严格模式下的this指向问题

一、全局环境 1.函数调用 非严格模式&#xff1a;this指向是Window // 普通函数 function fn () { console.log(this, this); } fn() // 自执行函数 (function fn () { console.log(this, this); })() 严格模式&#xff1a;this指向是undefined //…...

vue2、vue3组件传值,引用类型,对象数组如何处理

vue2、vue3组件传值&#xff0c;引用类型&#xff0c;对象数组如何处理 Excerpt 所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定&#xff1a;父级 prop 的更新会向下流动到子组件中&#xff0c;但是反过来则不行。这样会防止从子组件意外变更父… 下述组件传值指引…...

165. 小猫爬山

Powered by:NEFU AB-IN Link 文章目录165. 小猫爬山题意思路代码165. 小猫爬山 题意 翰翰和达达饲养了 N只小猫&#xff0c;这天&#xff0c;小猫们要去爬山。 经历了千辛万苦&#xff0c;小猫们终于爬上了山顶&#xff0c;但是疲倦的它们再也不想徒步走下山了&#xff08;呜咕…...

ECharts教程(详细)

ECharts教程(详细) 非常全面的ECharts教程&#xff0c;非常全面的ECharts教程&#xff0c;目前线条/节点颜色、线条粗细、线条样式、线条阴影、线条平滑、线条节点大小、线条节点阴影、线条节点边框、线条节点边框阴影、工具提醒、工具提醒样式、工具自定义提醒、工具提醒背景…...

pinia

目录一、介绍二、快速上手1.安装2.基本使用与state3.actions的使用4.getters的使用5.storeToRefs的使用6.pinia模块化三、数据持久化1.安装2.使用插件3.模块开启持久化4.按需缓存模块的数据一、介绍 pinia从使用角度和之前Vuex几乎是一样的&#xff0c;比Vuex更简单了。 在Vu…...

mysql中insert语句的五种用法

文章目录前言一、values参数后单行插入二、values参数后多行插入三、搭配select插入数据四、复制旧表的信息到新表五、搭配set插入数据总结前言 insert语句是标准sql中的语法&#xff0c;是插入数据的意思。在实际应用中&#xff0c;它也演变了很多种用法来实现特殊的功能&…...

建立音乐网站/营销方案案例

有关换行的问题首先提一个问题&#xff0c;如下。python程序代码如下&#xff1a;print("Im Bob. Whats your name?")上一行代码的输出如下&#xff1a;Im Bob. Whats your name?上面的输出没有换行。想在What之前换行&#xff0c;效果是&#xff1a;Im Bob.Whats …...

知名的网站建设公司/关键词免费下载

1.下列除哪组外都是镇心安神药组&#xff08;&#xff09; A.龙骨、牡蛎 B.朱砂、磁石 C.龟板、鳖甲 D.珍珠、琥珀 E.珍珠母、紫贝齿 2.下列忌火煅的药物是&#xff08;&#xff09; A.青礞石 B.牡蛎 C.朱砂 D.石决明 E.代赭石 3.既能镇惊安神&#xff0c;平肝潜阳&#xf…...

郴州市地图/百度搜索关键词优化

近年来&#xff0c;越来越多的人加入了测试大军中&#xff0c;很多人也想通过自学来学习软件测试技术加入这个行业&#xff0c;但是现在软件测试的书籍越来越多&#xff0c;也良莠不齐&#xff0c;而且软件测试涉及的技术也越来越多。鉴于此情况&#xff0c;为了回馈大家&#…...

重庆有效的网站推广/如何让百度快速收录新网站

在个人Mac电脑上安装并使用Spark&#xff1a; 第一步&#xff0c;网站上下载最新Spark包。 官网地址&#xff1a;https://spark.apache.org/downloads.html 第二步&#xff0c;查看是否运行良好&#xff0c;是否需要安装其他工具&#xff0c;比如JDK。【SSH连接本地Local Sh…...

wordpress 更换模板/鹤壁搜索引擎优化

1.java.lang.Object类的说明: 1.Object类是所Java类的根父类 2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类 3.Object类中的功能(属性、方法)就具通用性。 属性:无 方法:equals() / toString() / getClass() /hashCode() / clone() …...

网站模板怎么引用/怎么推广网站链接

感谢原作者&#xff1a;http://blog.csdn.net/lipeijs3/article/details/5137160 一、 Ant 与 Makefile : GNU Make 缺乏平台无关性&#xff0c;为了调用一个 Java 类&#xff0c;必须用命令行 java 调用 JVM, 再将类名作为命令行参数进行传递 Makefile 的 t…...