🎉 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事务时,通过@Transactional的propagation属性控制事务传播行为,核心级别如下:
| 传播级别 | 说明 | 适用场景 |
|---|---|---|
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 | 数据库URL | jdbc: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 配置步骤
-
添加依赖(以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> -
配置Logback(
logback.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> -
验证日志输出:执行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-Plus的PerformanceInterceptor。 - 屏蔽敏感参数:自定义
LogInterceptor过滤密码等敏感信息。
🎉 6. 性能监控
📝 6.1 核心监控指标
- SQL执行时间:单条SQL执行耗时(阈值:一般查询<100ms,复杂查询<500ms)。
- 连接池指标:活跃连接数、空闲连接数、等待连接数。
- 缓存命中率:一级缓存命中率(
(查询总数-数据库查询数)/查询总数)、二级缓存命中率。 - 事务指标:事务成功率、回滚率、平均事务耗时。
📝 6.2 监控工具
- Arthas:实时诊断SQL执行情况,如
trace com.example.mapper.UserMapper selectById查看方法调用链。 - Prometheus+Grafana:通过
MyBatis-Plus的MetricsInterceptor采集指标,配置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,检查连接池的maxLifetime和idleTimeout参数。
📝 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应用的性能、可靠性和可维护性。
577

被折叠的 条评论
为什么被折叠?



