MyBatis SqlSession核心机制解析

🎉 1. SqlSession概述

📝 1.1 基本概念

SqlSession是MyBatis中用于与数据库交互的核心接口,封装了JDBC连接、事务管理、SQL执行等功能。它通过SqlSessionFactory创建,生命周期与数据库会话绑定,是线程不安全的,必须确保每个线程独立使用

📝 1.2 生命周期
  • 创建:通过SqlSessionFactory.openSession()创建,参数包括是否自动提交(autoCommit)、事务隔离级别(TransactionIsolationLevel)、执行器类型(ExecutorType)。
  • 使用:执行SQL操作(selectOne/insert/update/delete)、管理事务(commit/rollback)。
  • 关闭:必须手动调用close()释放资源,否则会导致连接泄漏。在Spring环境中,SqlSessionTemplate会自动管理生命周期,但手动创建的SqlSession需显式关闭。
📝 1.3 核心方法
方法功能示例
T selectOne(String statement, Object parameter)执行查询并返回单个结果User user = sqlSession.selectOne("com.example.mapper.UserMapper.selectById", 1);
int insert(String statement, Object parameter)执行插入操作sqlSession.insert("com.example.mapper.UserMapper.insert", user);
void commit(boolean force)提交事务,force=true强制提交sqlSession.commit(true);
void rollback(boolean force)回滚事务,force=true强制回滚sqlSession.rollback(true);
void close()关闭会话,释放连接sqlSession.close();

🎉 2. 事务管理

📝 2.1 事务传播机制

MyBatis整合Spring事务时,通过@Transactionalpropagation属性控制事务传播行为,核心级别如下:

传播级别说明适用场景
REQUIRED(默认)若当前存在事务则加入,否则新建事务大多数业务场景,如用户注册(需插入用户和日志)
REQUIRES_NEW无论当前是否有事务,均新建事务日志记录(需独立于主事务,即使主事务回滚日志仍需提交)
NESTED嵌套事务,依赖数据库支持(如MySQL的SAVEPOINT)分阶段提交(如订单支付分“扣库存”和“减余额”,可回滚到中间状态)
SUPPORTS若当前有事务则加入,否则以非事务方式执行只读查询(如订单详情查询,有无事务均可)
MANDATORY必须在事务中执行,否则抛异常核心业务操作(如转账,无事务则拒绝执行)

代码示例

@Service
public class OrderService {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    // 主事务:创建订单(REQUIRED)
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void createOrder(Order order) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) { // autoCommit=true
            sqlSession.insert("com.example.mapper.OrderMapper.insert", order);
            logService.recordLog(order.getId(), "CREATE"); // 调用REQUIRES_NEW的方法
        } catch (Exception e) {
            throw new ServiceException("创建订单失败", e);
        }
    }
}

@Service
public class LogService {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    // 子事务:记录日志(REQUIRES_NEW)
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void recordLog(Long orderId, String action) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
            sqlSession.insert("com.example.mapper.LogMapper.insert", new Log(orderId, action));
        }
    }
}
📝 2.2 事务隔离级别

MyBatis事务隔离级别依赖JDBC支持,通过TransactionIsolationLevel枚举指定:

隔离级别说明问题
NONE不使用事务
READ_UNCOMMITTED允许读取未提交数据脏读、不可重复读、幻读
READ_COMMITTED只能读取已提交数据不可重复读、幻读(MySQL默认级别)
REPEATABLE_READ同一事务内多次读取结果一致幻读(InnoDB通过MVCC解决)
SERIALIZABLE串行执行所有事务性能极低,仅用于强一致性场景

配置示例

// 手动创建SqlSession时指定隔离级别
SqlSession sqlSession = sqlSessionFactory.openSession(TransactionIsolationLevel.REPEATABLE_READ);
📝 2.3 最佳实践
  • 显式指定回滚异常@Transactional(rollbackFor = Exception.class),避免RuntimeException以外的异常不回滚。
  • 避免长事务:拆分大事务为小事务,减少锁持有时间(如批量插入分批次提交)。
  • 手动管理事务:在非Spring环境中,使用try-with-resources确保事务正确关闭:
    try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) { // autoCommit=false
        try {
            sqlSession.insert("insertUser", user);
            sqlSession.insert("insertOrder", order);
            sqlSession.commit();
        } catch (Exception e) {
            sqlSession.rollback();
            throw e;
        }
    }
    

🎉 3. 连接池集成

MyBatis本身不实现连接池,需依赖第三方库(如HikariCP、Druid),通过DataSource注入SqlSessionFactory

📝 3.1 HikariCP配置(推荐)

HikariCP是性能最优的连接池,核心参数如下:

参数说明建议值
jdbcUrl数据库URLjdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
username/password数据库 credentials-
maximumPoolSize最大连接数CPU核心数×2 + 1(如8核→17)
minimumIdle最小空闲连接数maximumPoolSize相同(避免连接频繁创建/销毁)
idleTimeout空闲连接超时时间300000ms(5分钟)
connectionTimeout连接超时时间30000ms(3秒)
maxLifetime连接最大生命周期1800000ms(30分钟)

Spring Boot配置示例

# 🌟 application.yml
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    hikari:
      maximum-pool-size: 17
      minimum-idle: 17
      idle-timeout: 300000
      connection-timeout: 30000
      max-lifetime: 1800000
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity
📝 3.2 Druid配置(监控友好)

Druid提供丰富的监控功能,适合需要精细化监控的场景:

配置示例

@Configuration
public class DruidConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
    
    // 配置监控Servlet
    @Bean
    public ServletRegistrationBean<StatViewServlet> statViewServlet() {
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        bean.addInitParameter("loginUsername", "admin");
        bean.addInitParameter("loginPassword", "admin");
        return bean;
    }
    
    // 配置监控Filter
    @Bean
    public FilterRegistrationBean<WebStatFilter> webStatFilter() {
        FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>(new WebStatFilter());
        bean.addUrlPatterns("/*");
        bean.addInitParameter("exclusions", "*.js,*.css,/druid/*");
        return bean;
    }
}

application.yml

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    druid:
      initial-size: 5
      max-active: 20
      min-idle: 5
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
📝 3.3 连接池优化建议
  • 避免过度配置:最大连接数并非越大越好,过多连接会导致数据库线程竞争加剧。
  • 启用连接测试:设置test-while-idle=true定期检测空闲连接有效性。
  • 监控连接池状态:通过Druid监控页面(/druid)或HikariCP的HikariPoolMXBean查看连接使用情况。

🎉 4. 缓存配置

MyBatis提供两级缓存,用于减少数据库访问次数:

📝 4.1 一级缓存(SqlSession级别)
  • 默认开启,缓存范围为当前SqlSession线程私有
  • 失效场景:执行update/delete/insert操作、调用clearCache()、关闭SqlSession

示例

try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user1 = mapper.selectById(1); // 第一次查询,从数据库获取
    User user2 = mapper.selectById(1); // 第二次查询,从一级缓存获取
    System.out.println(user1 == user2); // true(同一对象)
}
📝 4.2 二级缓存(Mapper级别)
  • 默认关闭,需在Mapper接口或XML中开启,缓存范围为整个Mapper。
  • 实现条件:实体类需实现Serializable接口,Mapper中添加<cache/>标签。

XML配置示例

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache 
        eviction="LRU" <!-- 淘汰策略:LRU(默认)/FIFO/SOFT/WEAK -->
        flushInterval="60000" <!-- 自动刷新时间(ms) -->
        size="1024" <!-- 最大缓存对象数 -->
        readOnly="false"/> <!-- 是否只读:false(默认,可修改缓存) -->
    
    <select id="selectById" resultType="com.example.entity.User" useCache="true">
        SELECT * FROM user WHERE id = #{id}
    </select>
    
    <update id="update" parameterType="com.example.entity.User" flushCache="true">
        UPDATE user SET name = #{name} WHERE id = #{id}
    </update>
</mapper>

接口注解配置示例

@Mapper
@CacheNamespace(
    eviction = LruCache.class,
    flushInterval = 60000,
    size = 1024,
    readWrite = true
)
public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    @Options(useCache = true)
    User selectById(Long id);
    
    @Update("UPDATE user SET name = #{name} WHERE id = #{id}")
    @Options(flushCache = Options.FlushCachePolicy.TRUE)
    int update(User user);
}
📝 4.3 缓存优化建议
  • 禁用一级缓存:在高并发场景下,一级缓存可能导致数据不一致,可通过ExecutorType.BATCH或关闭一级缓存(不推荐)。
  • 合理设置缓存淘汰策略:热点数据用LRU,频繁修改数据用FIFO
  • 避免缓存大对象:缓存结果集应精简,避免存储Blob/Clob等大字段。

🎉 5. 日志绑定

MyBatis通过LogFactory集成日志框架,支持SLF4J、Log4j2、Logback等。

📝 5.1 配置步骤
  1. 添加依赖(以SLF4J+Logback为例):

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
    </dependency>
    
  2. 配置Logbacklogback.xml):

    <configuration>
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <!-- 开启MyBatis SQL日志 -->
        <logger name="com.example.mapper" level="DEBUG"/> <!-- Mapper接口包 -->
        <logger name="org.apache.ibatis" level="INFO"/> <!-- MyBatis核心日志 -->
    
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </configuration>
    
  3. 验证日志输出:执行SQL操作后,控制台会输出SQL语句及参数:

    15:30:00.123 [main] DEBUG com.example.mapper.UserMapper.selectById - ==>  Preparing: SELECT * FROM user WHERE id = ? 
    15:30:00.124 [main] DEBUG com.example.mapper.UserMapper.selectById - ==> Parameters: 1(Integer)
    15:30:00.125 [main] DEBUG com.example.mapper.UserMapper.selectById - <==      Total: 1
    
📝 5.2 高级配置
  • 显示SQL执行时间:通过MyBatis插件实现,如Mybatis-PlusPerformanceInterceptor
  • 屏蔽敏感参数:自定义LogInterceptor过滤密码等敏感信息。

🎉 6. 性能监控

📝 6.1 核心监控指标
  • SQL执行时间:单条SQL执行耗时(阈值:一般查询<100ms,复杂查询<500ms)。
  • 连接池指标:活跃连接数、空闲连接数、等待连接数。
  • 缓存命中率:一级缓存命中率((查询总数-数据库查询数)/查询总数)、二级缓存命中率。
  • 事务指标:事务成功率、回滚率、平均事务耗时。
📝 6.2 监控工具
  • Arthas:实时诊断SQL执行情况,如trace com.example.mapper.UserMapper selectById查看方法调用链。
  • Prometheus+Grafana:通过MyBatis-PlusMetricsInterceptor采集指标,配置Grafana dashboard可视化。
  • JMeter:压测SQL执行性能,模拟高并发场景。

Arthas示例

# 🌟 启动Arthas并attach到Java进程
arthas-boot.jar

# 🌟 跟踪Mapper方法执行时间
trace com.example.mapper.UserMapper selectById

Prometheus配置

@Configuration
public class MetricsConfig {
    @Bean
    public MetricsInterceptor metricsInterceptor() {
        return new MetricsInterceptor();
    }
    
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setPlugins(new Interceptor[]{metricsInterceptor()});
        return factoryBean.getObject();
    }
}

🎉 7. 常见问题与优化

📝 7.1 连接泄漏
  • 原因:未关闭SqlSession或连接池配置不当。
  • 解决:使用try-with-resources管理SqlSession,检查连接池的maxLifetimeidleTimeout参数。
📝 7.2 数据不一致
  • 原因:一级缓存导致不同SqlSession读取到旧数据。
  • 解决:在Mapper方法上添加@Options(flushCache = Options.FlushCachePolicy.TRUE)强制刷新缓存。
📝 7.3 慢SQL
  • 原因:SQL未优化、缺少索引、全表扫描。
  • 解决:通过EXPLAIN分析SQL执行计划,添加合适的索引,拆分复杂SQL。
📝 7.4 事务不回滚
  • 原因:未指定rollbackFor属性,导致 checked exception 不触发回滚。
  • 解决@Transactional(rollbackFor = Exception.class)

🎉 8. 总结

SqlSession是MyBatis与数据库交互的核心入口,其正确使用直接影响系统性能和稳定性。关键要点包括:

  • 生命周期管理:确保每个线程独立使用并及时关闭。
  • 事务控制:合理选择传播级别和隔离级别,避免长事务。
  • 连接池优化:使用HikariCP并调优参数,避免连接泄漏。
  • 缓存策略:根据业务场景选择一级/二级缓存,避免数据不一致。
  • 监控与诊断:利用Arthas、Prometheus等工具实时监控SQL执行情况。

通过本文的优化实践,可有效提升MyBatis应用的性能、可靠性和可维护性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值