mybatis事务DefaultSqlSession-策略模式

这篇博客深入探讨了MyBatis的DefaultSqlSession类如何利用策略模式来实现灵活的数据库操作。DefaultSqlSession作为Context角色,Executor接口及其实现作为具体策略,通过这种方式,可以根据不同场景选择不同的执行策略。博客还提到了反射技术与策略模式的结合,以增强系统的动态性。同时,详细展示了DefaultSqlSession的select、insert、update和delete等核心方法的实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:定义多种行为,根据不同的场景选择不同的行为;
策略模式:定义了一些列算法,将每一个算法封装起来,由不同的类进行管理,并让他们之间相互替换。这样,每种算法都可以独立地变化。
在这里插入图片描述
Context:行为的调用者
Strategy:行为的统一抽象接口
ConcreteStrategy:具体的算法实现(多个)
行为增加时:增加ConcreteStrategy;策略方案变更时:新增Context;
可以将反射技术和策略模式结合,这样应用程序就不需要了解所有Strategy接口实现类,而是在运行时通过反射的方式创建实例使用的Strategy对象。
在这里插入图片描述
在DefaultSqlSession中使用到了策略模式,DefaultSqlSession中是context
的角色,而将所有数据库相关的操作全部封装到Executor接口实现中,并通过executor字段选择不同的Executor实现。

/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.session.defaults;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.result.DefaultMapResultHandler;
import org.apache.ibatis.executor.result.DefaultResultContext;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.ParamNameResolver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;

/**
 * The default implementation for {@link SqlSession}.
 * Note that this class is not Thread-Safe.
 *
 * @author Clinton Begin
 */
public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

  public DefaultSqlSession(Configuration configuration, Executor executor) {
    this(configuration, executor, false);
  }

  @Override
  public <T> T selectOne(String statement) {
    return this.selectOne(statement, null);
  }

  @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;
    }
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
    return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
    return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
            configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
    final DefaultResultContext<V> context = new DefaultResultContext<>();
    for (V o : list) {
      context.nextResultObject(o);
      mapResultHandler.handleResult(context);
    }
    return mapResultHandler.getMappedResults();
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement) {
    return selectCursor(statement, null);
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter) {
    return selectCursor(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
      registerCursor(cursor);
      return cursor;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void select(String statement, Object parameter, ResultHandler handler) {
    select(statement, parameter, RowBounds.DEFAULT, handler);
  }

  @Override
  public void select(String statement, ResultHandler handler) {
    select(statement, null, RowBounds.DEFAULT, handler);
  }

  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public int insert(String statement) {
    return insert(statement, null);
  }

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

  @Override
  public int update(String statement) {
    return update(statement, null);
  }

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public int delete(String statement) {
    return update(statement, null);
  }

  @Override
  public int delete(String statement, Object parameter) {
    return update(statement, parameter);
  }

  @Override
  public void commit() {
    commit(false);
  }

  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void rollback() {
    rollback(false);
  }

  @Override
  public void rollback(boolean force) {
    try {
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public List<BatchResult> flushStatements() {
    try {
      return executor.flushStatements();
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }

  private void closeCursors() {
    if (cursorList != null && !cursorList.isEmpty()) {
      for (Cursor<?> cursor : cursorList) {
        try {
          cursor.close();
        } catch (IOException e) {
          throw ExceptionFactory.wrapException("Error closing cursor.  Cause: " + e, e);
        }
      }
      cursorList.clear();
    }
  }

  @Override
  public Configuration getConfiguration() {
    return configuration;
  }

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

  @Override
  public Connection getConnection() {
    try {
      return executor.getTransaction().getConnection();
    } catch (SQLException e) {
      throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);
    }
  }

  @Override
  public void clearCache() {
    executor.clearLocalCache();
  }

  private <T> void registerCursor(Cursor<T> cursor) {
    if (cursorList == null) {
      cursorList = new ArrayList<>();
    }
    cursorList.add(cursor);
  }

  private boolean isCommitOrRollbackRequired(boolean force) {
    return (!autoCommit && dirty) || force;
  }

  private Object wrapCollection(final Object object) {
    return ParamNameResolver.wrapToMapIfCollection(object, null);
  }

  /**
   * @deprecated Since 3.5.5
   */
  @Deprecated
  public static class StrictMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -5741767162221585340L;

    @Override
    public V get(Object key) {
      if (!super.containsKey(key)) {
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
      }
      return super.get(key);
    }

  }

}

### MyBatis 关闭非事务SqlSession 的日志含义及解决方法 #### 日志信息的含义 当应用程序运行时,如果看到 `Creating a new SqlSession` 或者 `Closing non transactional SqlSession` 这类日志消息,则表明当前的操作并非在一个显式的事务上下文中完成。具体来说: - **创建新的 SqlSession**:这表示 MyBatis 正在为某个操作创建一个新的会话实例[^1]。 - **关闭非事务性的 SqlSession**:这意味着该 SqlSession 并未绑定到任何事务管理器中,在执行完成后会被立即释放并关闭[^2]。 这种行为通常发生在以下场景下: 1. 当前环境并未启用 Spring 提供的声明式事务或者编程式事务支持; 2. 数据访问层的方法调用没有被正确包裹在事务边界之内; 3. 配置文件中的某些设置可能干扰了默认事务机制的工作逻辑[^3]。 因此,“Closing non transactional SqlSession”的出现往往暗示着潜在的问题——即事务未能正常启动或传播,从而影响业务功能的一致性和可靠性。 #### 解决方案分析 针对此类现象可以采取以下几个方面的措施来排查和修复问题: ##### 1. 检查Spring事务配置是否正确 确认 spring-boot 应用程序已经启用了必要的依赖项以及相应的注解扫描路径。例如,在 application.properties 中应包含如下配置项以激活 JPA/MyBatis 支持及相关特性: ```properties spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=password mybatis.mapperLocations=classpath*:mapper/*.xml ``` 同时还需要确保 @EnableTransactionManagement 注解已被应用至合适的 Java Config 类上,并且 service 层的相关方法已标注@Transactional 来定义期望的行为模式[^4]。 ##### 2. 调整 Mapper 接口实现方式 对于基于 XML 映射文件的方式构建 DAO 层接口而言,默认情况下它们并不会自动参与全局事务控制流程之中除非特别指定。可以通过下面两种途径之一加以改进: - 方法一:让所有的 Repository Bean 继承自 org.mybatis.spring.support.SqlSessionDaoSupport 抽象基类,这样就可以利用其内部维护好的 session 实例来进行持久化处理而无需每次都重新获取资源对象。 ```java public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper { @Override public void insertUser(User user){ getSqlSession().insert("com.example.User.insert",user); } } ``` - 方法二:采用更现代化的做法也就是借助 mybatis-spring 扩展库所提供的 FactoryBean 工具类来自动生成代理版本的目标组件形式代替原始 POJO 定义样式。如此一来便可享受到框架层面给予的支持效果比如 AOP 切面拦截等功能模块带来的便利之处。 ##### 3. 修改 logging level 设置降低冗余输出频率 假如仅仅是因为频繁打印这些调试性质的消息让人困扰的话也可以考虑调整 logback.xml (或者其他类似的 logger framework configuration file)里的 threshold 参数值以便过滤掉不必要的细节部分只保留关键事件记录即可满足日常运维需求的同时又能保持良好的性能表现水平。 ```xml <logger name="org.apache.ibatis" level="WARN"/> <!-- Or --> <root level="INFO"> <appender-ref ref="STDOUT"/> </root> ``` 以上三种策略可以根据实际项目的具体情况灵活组合运用最终达到消除异常状况的目的同时也提升了整体代码质量标准符合最佳实践指南的要求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值