MyBatis源码解析--加载流程(7)

Mybatis源码解析之执行流程解析 Mybatis源码解析之核心类分析 Mybatis源码解析之初始化分析 本篇文章将在前两篇文章的基础上解析Mybatis执行sql的流程。 一、 映射集mappers解析 对于在原生mybatis中的sql执行,需要在配置文件中通过标签引入mapper.xml文件。前文已经提到,对于xml配置文件,mybatis在XMLConfigBuilder#parseConfiguration(Xnode)... 阅读详情

SimpleExecutor
前面已经介绍了BaseExecutor,是Executor接口的直接继承,作用是提供模版,为它的继承类可以直接继承方法,其中SimpleExecutor会不需要关心一级缓存的问题,只要实现四个基本方法就可以。

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  //执行更新操作
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //创建StatementHandler对象
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      //创建对应的Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //执行更新
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  //查询的实现
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();//获取configuration对象
      //创建StatementHandler对象,
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
      stmt = prepareStatement(handler, ms.getStatementLog());
      //通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
      return handler.<E>query(stmt, resultHandler);
    } finally {
    //关闭statement
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>queryCursor(stmt);
  }

  @Override
  //不提供该方法
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    return Collections.emptyList();
  }

  //创建Statement
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取connection对象的动态代理,添加日志能力;
    Connection connection = getConnection(statementLog);
    //通过不同的StatementHandler,利用connection创建(prepare)Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    //使用parameterHandler处理占位符
    handler.parameterize(stmt);
    return stmt;
  }

}

ReuseExecutor 可重用的Executor实现类,主要将statement缓存了,遇到相同的不需要反复创建statement

public class ReuseExecutor extends BaseExecutor {
//statement的缓存,key是SQL语句,value是Statement
  private final Map<String, Statement> statementMap = new HashMap<>();

  public ReuseExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  //跟SimpleExecutor一样
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();//获取configuration对象
    //创建StatementHandler对象
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  //StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
  //通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
    return handler.<E>query(stmt, resultHandler);
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>queryCursor(stmt);
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
  //会将缓存所有的statement关闭并且将map清空
    for (Statement stmt : statementMap.values()) {
      closeStatement(stmt);
    }
    statementMap.clear();
    return Collections.emptyList();
  }
//跟SimpleExecutor的主要不同点
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();//获取sql语句
    if (hasStatementFor(sql)) {//根据sql语句检查是否缓存了对应的Statement
      stmt = getStatement(sql);//获取缓存的Statement
      applyTransactionTimeout(stmt);//设置新的超时时间
    } else {//缓存中没有statment,创建statment过程和SimpleExecutor类似
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);//放入缓存中
    }
  //使用parameterHandler处理占位符
    handler.parameterize(stmt);
    return stmt;
  }

  private boolean hasStatementFor(String sql) {
    try {
      return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
    } catch (SQLException e) {
      return false;
    }
  }

  private Statement getStatement(String s) {
    return statementMap.get(s);
  }

  private void putStatement(String sql, Statement stmt) {
    statementMap.put(sql, stmt);
  }

}

BatchExecutor 批量执行的Executor实现类

public class BatchExecutor extends BaseExecutor {

  public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
//缓存多个Statement的对象,其中每个statement都缓存了多条SQL语句
  private final List<Statement> statementList = new ArrayList<>();
  //记录批处理的结果。每个BatchResult对应一个Statement元素
  private final List<BatchResult> batchResultList = new ArrayList<>();
  //记录当前的SQL语句
  private String currentSql;
  //记录当前的MappedStatement对象
  private MappedStatement currentStatement;

  public BatchExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  //批处理只支持insert、update、delete等类型的SQL语句
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
  //获取配置内容
    final Configuration configuration = ms.getConfiguration();·
    //创建StatementHandler对象
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    //得到sql语句
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    //如果当前执行的SQL模式跟上次执行的SQL模式相同并且对应的MappedStatement对象相同
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
    //获得集合中的最后一个Statement对象
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      //设置事务超时时间
      applyTransactionTimeout(stmt);
     handler.parameterize(stmt);//绑定实参
     //获得最后一次的BatchResult对象,并添加参数到其中
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
    //如果不相同就会将其缓存到list中
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
  // handler.parameterize(stmt);
  //批处理
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

  @Override
  //batchExecutor查询实现
  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    Statement stmt = null;
    try {
      flushStatements();
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    flushStatements();
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Connection connection = getConnection(ms.getStatementLog());
    Statement stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return handler.<E>queryCursor(stmt);
  }

  @Override
  //批量处理SQL语句
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
    //用来储存批处理的结果
      List<BatchResult> results = new ArrayList<>();
      //如果要回滚就返回空集合
      if (isRollback) {
        return Collections.emptyList();
      }
      //遍历statementList集合
      for (int i = 0, n = statementList.size(); i < n; i++) {
      //获取对应的statement对象
        Statement stmt = statementList.get(i);
        applyTransactionTimeout(stmt);
        //获取对应的batchResult对象
        BatchResult batchResult = batchResultList.get(i);
        try {
        //更新batchResult中的SQL语句影响条数
          batchResult.setUpdateCounts(stmt.executeBatch());
          MappedStatement ms = batchResult.getMappedStatement();
          List<Object> parameterObjects = batchResult.getParameterObjects();
          //获取配置的KeyGenerator对象
          KeyGenerator keyGenerator = ms.getKeyGenerator();
          if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
            Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
            jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
          } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
            for (Object parameter : parameterObjects) {
              keyGenerator.processAfter(this, ms, stmt, parameter);
            }
          }
          // 关闭statement对象
          closeStatement(stmt);
        } catch (BatchUpdateException e) {
          StringBuilder message = new StringBuilder();
          message.append(batchResult.getMappedStatement().getId())
              .append(" (batch index #")
              .append(i + 1)
              .append(")")
              .append(" failed.");
          if (i > 0) {
            message.append(" ")
                .append(i)
                .append(" prior sub executor(s) completed successfully, but will be rolled back.");
          }
          throw new BatchExecutorException(message.toString(), e, results, batchResult);
        }
        //添加到结果集
        results.add(batchResult);
      }
      return results;
    } finally {
    //关闭statement们
      for (Statement stmt : statementList) {
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }

}

接下来是CachingExecutor,作用就是二级缓存,二级缓存是可以在多个sqlSession之间共享的,是以namespace为单位的。
其中在MappedStatement类中有private Cache cache,以为这可以给每个mapper指定不同的二级缓存。

public class CachingExecutor implements Executor {
//被委托的Executor对象
  private final Executor delegate;
  //TransactionalCacheManager 对象
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    //设置delegate被当前执行器所包装
    delegate.setExecutorWrapper(this);
  }

  @Override
  //直接调用委托执行器中对应的方法
  public Transaction getTransaction() {
    return delegate.getTransaction();
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      //如果强制回滚就回滚tcm
      if (forceRollback) { 
        tcm.rollback();
      } else {
      //如果强制提交,就提交
        tcm.commit();
      }
    } finally {
    //执行delegate对应的方法
      delegate.close(forceRollback);
    }
  }

  @Override
  public boolean isClosed() {
    return delegate.isClosed();
  }

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  //如果需要清空缓存就进行清空
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	//获取sql语句信息,包括占位符,参数等信息
    BoundSql boundSql = ms.getBoundSql(parameterObject);
  //拼装缓存的key值
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.queryCursor(ms, parameter, rowBounds);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
	//从MappedStatement中获取二级缓存
    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) {
          //二级缓存为空,才会调用BaseExecutor.query
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //查询后放入二级缓存中
          tcm.putObject(cache, key, list); 
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return delegate.flushStatements();
  }

  @Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    //执行tcm的commit方法
    tcm.commit();
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    try {
      delegate.rollback(required);
    } finally {
      if (required) {
      //回滚tcm
        tcm.rollback();
      }
    }
  }

  private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
      for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
        if (parameterMapping.getMode() != ParameterMode.IN) {
          throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
        }
      }
    }
  }

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  }

  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    return delegate.isCached(ms, key);
  }

  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    delegate.deferLoad(ms, resultObject, property, key, targetType);
  }

  @Override
  public void clearLocalCache() {
    delegate.clearLocalCache();
  }
//如果需要清空缓存就进行清空
  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {      
      tcm.clear(cache);
    }
  }

  @Override
  public void setExecutorWrapper(Executor executor) {
    throw new UnsupportedOperationException("This method should not be called");
  }

}

TransactionalCacheManager TransactionalCache 管理器

public class TransactionalCacheManager {
//因为一次事务操作可能会使用不同的mapper,所以需要用一个map来管理这些mapper对应的TransactionalCache
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
//清空对应的缓存
  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }
//获得缓存中指定的值
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  //添加kv到缓存当中
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }
//提交所有的TransactionCache
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }
//回滚所有的TransactionalCache
  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  private TransactionalCache getTransactionalCache(Cache cache) {
  //优先从transactionCaches获得Cache对象对应的TransactionalCache 对象,如果不存在就创建一个添加到缓存中。
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

}

TransactionalCache 支持事务的 Cache 实现类

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);
//二级缓存cache对象
  private final Cache delegate;
  //提交事务的时候会将底层的Cache清空
  private boolean clearOnCommit;
  //待提交的kv映射
  private final Map<Object, Object> entriesToAddOnCommit;
  //查找不到的key集合
  private final Set<Object> entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<>();
    this.entriesMissedInCache = new HashSet<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public Object getObject(Object key) {
    // 从缓存中尝试获取value
    Object object = delegate.getObject(key);
    if (object == null) {
    //如果没有获取到就在entriesMissedInCache中添加相应的key
      entriesMissedInCache.add(key);
    }
    // 如果 clearOnCommit 为 true ,表示处于持续清空状态,则返回 null
    if (clearOnCommit) {
      return null;
    } else {
    //返回 value
      return object;
    }
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  // 暂存 KV 到 entriesToAddOnCommit 中
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

  @Override
  public Object removeObject(Object key) {
    return null;
  }

  @Override
  public void clear() {
    clearOnCommit = true;
    //清空 entriesToAddOnCommit
    entriesToAddOnCommit.clear();
  }

  public void commit() {
  //如果 clearOnCommit 为 true ,则清空 delegate 缓存
    if (clearOnCommit) {
      delegate.clear();
    }
    // 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
    flushPendingEntries();
    // 重置
    reset();
  }

  public void rollback() {
  //从 delegate 移除出 entriesMissedInCache
    unlockMissedEntries();
    reset();
  }

  private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  private void flushPendingEntries() {
  // 将 entriesToAddOnCommit 刷入 delegate 中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    // 将 entriesMissedInCache 刷入 delegate 中
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
            + "Consider upgrading your cache adapter to the latest version.  Cause: " + e);
      }
    }
  }

}

分析完了Executor接口及其所有的实现类,那么看看Executor的调用:在Configuration中

//创建Executor对象
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//获得执行器类型
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : 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);
    }
    //如果有<cache>节点,通过装饰器,添加二级缓存的能力
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //通过interceptorChain遍历所有的插件为executor增强,添加插件的功能
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
MyBatis源码剖析 在二级缓存的设计上,MyBatis大量的运用了装饰者模式,如CacheExecutor,以及各种Cache接口的装饰器。二级缓存实现了sqlSession之间的缓存数据共享,属于namespace级别;二级缓存具有丰富的缓存策略;二级缓存可由多个装饰器与基础缓存组合而成;二级缓存工作由一个缓存装饰器CacheExecutor和一个事务型预缓存TransactionalCache完成。问题:在开发过程中很多时候我们并不总是需要在获取用户信息的时候加载订单信息,此时就是我们所说的延迟加载。 阅读详情

相关推荐

深入解析MyBatis源码与实战

摘要:本文系统解析MyBatis核心架构与实现原理,涵盖配置解析、SQL执行、结果映射三大模块。通过动态SQL生成、缓存机制(一级/二级缓存)、插件开发(Interceptor接口)等典型示例,深入剖析源码实现。结合SpringBoot集成场景,展示CRUD操作、动态SQL拼接、事务管理、多数据源配置等实战方案,并给出批量操作优化(BatchExecutor)、类型处理器扩展等高级特性实现。特别说明流程图关键节点:从SqlSessionFactory构建到Executor执行,通过StatementHand

keny-大成的博客 1359

第六章-Mybatis源码解析-StatementHandler语句处理和执行(一)

第五章-Mybatis源码解析-StatementHandler语句处理和执行

zhang527294844的博客 788

SLG项目开发经验二_大地图设计与实现

本文深入探讨了SLG游戏大地图设计与实现的关键技术,包括网格化业务框架、AOI系统优化、边界处理方案以及行军系统实现。通过Unity引擎的实践案例,详细解析了大地图性能优化的核心策略,如分块加载、GPU Instancing和差异同步协议,为SLG项目开发提供宝贵经验。

weixin_29001419的博客 216

Spring+mybatis整合声明式事务异常之org.springframework.transaction.TransactionTimedOutException: Transaction ..

1、异常信息 org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Tue Jul 23 18:06:32 CST 2019... 2、示例代码 @Service public class UserServiceImpl implements ...

静若清池,动如涟漪_ 1万+

Kettle Web版部署指南

看到以上的日志则表示启动成功。

2401_89221867的博客 1716

避坑指南:Dify+Docker+Ollama离线部署实战与疑难解析

本文详细解析了在国产化系统(如银河麒麟V10)内网环境中,如何从零开始离线部署Dify、Docker与Ollama的全栈AI应用开发平台。文章重点拆解了Docker安装中的“没有Release文件”和“GPG密钥错误”等典型报错解决方案,并提供了Ollama离线模型导入与Dify连接配置的完整实战指南,旨在帮助开发者高效完成本地部署,避开常见陷阱。

dd012的博客 462

MyBatis(三):StatementHandler

回顾三部分对应关系StatementHandlerStatementHandler结构PrepareStatementHandler执行流程构建StatementHandlerquery方法实现parameter方法实现 回顾 MyBatis的执行流程 Sqlsession (会话层面,门面模式)-》 Executor(执行器,模板方法,doQuery和query)-》 StatementHandler(JDBC处理器) JDBC分为三部分 获取Statement,声明Statement 设置Sql参.

GDUT_Trim的博客 922

QT编程系列1--QT学习前需知

本周学习了一些QT编程方面的知识,学写了几个程序,这里把记录下来。一方面对前面学习的知识做一个总结,便于以后复习、再学习和应用;另一方面也和大家做个交流,有不足的地方请各位看官多指点。   首先是做一些学习之前的交代:一,学习QT需要哪些基础;二,学习QT有哪些用途,QT较其他的开发工具有哪些特点;三,学习QT的思路。

霜月孤鸟&RTEMS&TinyOS 1115

java.sql.BatchUpdateException: Lock wait timeout exceeded; try restarting transaction

Lock wait timeout exceeded 原因是使用的InnoDB表类型的时候,默认参数:innodb_lock_wait_timeout设置锁等待的时间是50s,因为有的锁等待超过了这个时间,所以报了错。 尝试可以把这个时间加长,或者优化存储过程,事务避免过长时间的等待。 try restarting transaction 试着重新操作 转载于:https://www.cn...

weixin_34253539的博客 1998

SAP CO-PA数据传送避坑指南:销售开票与FI/MM记账的日常配置实操(含KEKF/KE4I/KEI2)

本文深入解析SAP CO-PA数据传送中的常见配置问题,特别是销售开票与FI/MM记账环节的实操避坑指南。通过KEKF/KE4I/KEI2等关键配置点的详细说明,帮助用户避免数据失真和重复记录,提升获利能力分析的准确性。文章结合实战案例,揭示官方文档未提及的'数据黑洞'形成机制,为SAP顾问提供实用的排雷手册。

weixin_26905487的博客 390

C#上位机实战:工业产线数据采集与监控系统开发(附源码思路)

模块化设计:四层架构清晰,各模块独立可替换,便于后期维护与扩展;稳定性强:支持断线重连、异常重试、并发采集,满足工业7×24小时运行需求;实用性高:覆盖产线监控核心功能,可视化组件适配工业场景,操作简单;源码可复用:通信类、采集服务、报警管理器等可直接复制到其他上位机项目。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1651

TransactionTimeout(事务超时)

Transaction timeout (unit is always seconds) can be configured in various ways:This argument is the same no matter if you are using JBossTM(=4.2).Globally: You can change this behavior globally by modifying the TransactionManagerService in /conf/jboss-

风雨亭 9529

YOLO训练/写作脚本目录一览 | 涉及标签格式转换、数据扩充、热力图、感受野、精度曲线、数量统计等近百个脚本文件

在大家购买专栏后,便可获得全部的脚本文件。在获取到文件后,只需按照将程序放在个人项目中即可一键运行。

Limiiiing的博客 2376

Mybatis-plus源码解读-自动装配与加载流程

本文分析了MyBatis-Plus的核心自动装配机制,重点介绍了MybatisPlusAutoConfiguration作为SpringBoot集成入口的关键作用。通过MybatisSqlSessionFactoryBuilder构建支持MyBatis-Plus特性的SqlSessionFactory,并详细解析了MybatisConfiguration、MybatisMapperRegistry等核心组件的扩展实现。文章还阐述了ISqlInjector接口及其实现类如何动态注入BaseMapper的CRU

源码老人的专栏 2038

Lammps模拟剪切作用下的位错滑移

Lammps模拟位错与障碍物作用。在lammps中实现周期性位错阵列模型模拟剪切作用下1/2刃位错与障碍物的交互作用

Rockyoung 3458

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

mybatis的执行流程

mybatis的执行流程如下代码: 第一步:通过Resources加载配置好的mybatis.xml配置文件。 Resources是ibatis.io包下面的类,也就是一个io流,用于读写文件,通过getResourceAsStream把xml文件加载进来,把配置文件解析为一个流。   第二步:然后看第二句话,这句话是关键。我们首先new了一个SqlSessionFactoryBuil...

落叶飘零z的博客 4万+

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

MyBatis源码剖析之延迟加载源码细节

在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时就是我们所说的延迟加载。举个栗⼦:在⼀对多中,当我们有⼀个⽤户,它有个100个订单在查询⽤户的时候,要不要把关联的订单查出来?在查询订单的时候,要不要把关联的⽤户查出来?回答在查询⽤户时,⽤户下的订单应该是,什么时候⽤,什么时候查询。在查询订单时,订单所属的⽤户信息应该是随着订单⼀起查询出来。延迟加载就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。

write less , do more 3334

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Mybatis源码解读-Configuration 配置信息加载流程源码分析

mybatis启动流程分析,解读源码如何从加载mybatis-config.xml配置到执行流程; 1、mybatis-config.xml配置文件加载 2、配置节点与Configuration全局配置映射 3、解读配置信息加载相关类梳理

源码老人的专栏 921

Mybatis源码解析

mybatis的流程分析

WinnerBear的博客 5218

Mybatis-SpringBoot源码解析之一:SqlSessionFactory加载流程

Mybatis-SpringBoot源码解析

zhengguofeng0328的博客 4178

Mybatis源码解析之配置加载(一)

Mybatis源码解析之配置加载(一) 用了好几年的mybatis了,但是很少来钻研mybatis原理所在,最近抽出空来,就把这一整套源码都研究了下,然后发现就是这些东西,mybatis没啥难度,于是决定把研究的这一整套写一个mybatis系列,记录一下,在这些完了以后,顺便写一个小的mybatis框架。 1. demo演示 还是从用法开始吧,就不开始就从源码说了,mybatis的conf配置文...

u012734441的专栏 3712

MyBatis 源码解析:Mapper 文件加载与解析

`Mapper` 文件是 MyBatis 中连接 SQL 和 Java 对象的桥梁。本文将通过自定义实现一个简单的 Mapper 文件加载与解析器,展示其工作原理,并对比 MyBatis 中的 Mapper 文件解析过程,帮助读者理解 MyBatis 内部的 Mapper 文件加载与解析机制。

捕风捉你 · 星桥API接入与大模型实战 1795

MyBatis源码解析--加载流程(1)

分析完11个提供支持的模块,接下来就完整地debug一遍加载的流程。 首先看下简单的流程: 1.初始化会加载mybatis-config.xml配置文件、mapper映射配置文件以及Mapper接口中的注解信息,这些信息都会形成相应的对象并保存到Configuration中。 2.利用Configuration创建SqlSessionFactory对象。 3.通过SqlSessionFactory创建SqlSession对象进行相关的数据库操作。 那么首先就从加载mybatis-config.xml文件开始

jacobbbbbbbb的博客 325

mybatis源码解析(二)-加载过程

mybatis源码解析(一)-开篇 mybatis源码解析(二)-加载过程 mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程 mybatis源码解析(四)-Mapper方法调用过程 mybatis源码解析(五)-mybatis如何实现的事务控制 mybatis源码解析(六)-配合spring-tx实现事务的原理 mybatis源码解析(七)-当my...

civil的专栏 5657

Mybatis源码之--XML完整解析流程

MyBatis源码解析(一) 如何编译源码 仓库地址 1、idea中打开项目 2、等待解析依赖 备注:解析依赖的过程中有可能某些jar包的版本找不到,可自行在https://mvnrepository.com/ 找寻替代版本 XML解析 mybatis3支持注解方式声明SQL语句,但是最终经过mybatis内部解析,作用和使用XML定义是一样的。 只要你理解了XML的整个解析流程,其实就已经吃透了mybatis。 在阅读源码的过程中,mybatis的中文官网必须时刻去翻阅。特别是如下几章内容: 入

twx843571091的博客 4574

MyBatis执行一条sql语句的流程(源码解析)

加载配置文件:通过类加载器ClassLoaderWrapper以及一些其他的类加载器加载mybatis配置文件获得输入流。SqlsessionFactory的创建。

m0_71338251的博客 1864
上一篇: MyBatis源码解析--加载流程(6)
下一篇: MyBatis源码解析--加载流程(8)
jacobbbbbbbb
博客等级 码龄7年 6粉丝 66原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值