MyBatis 作用域全解析:从踩坑到精通,一篇搞懂核心机制

引言

刚接触 MyBatis 的时候,是不是总被 SqlSessionSqlSessionFactory 这些“工厂”和“会话”的关系搞懵?为什么有时候明明关了 SqlSession 还是报连接泄漏?Mapper 接口到底该不该重复创建?今天咱们就从实际开发中的痛点出发,把 MyBatis 的三大核心组件的作用域扒个底朝天!

一、先搞清楚:MyBatis 最核心的三个“角色”

MyBatis 能高效操作数据库,离不开三个核心组件:

  • SqlSessionFactory:数据库连接的“总工厂”,负责生产 SqlSession(会话)。
  • SqlSession:数据库操作的“临时通道”,相当于 JDBC 里的 Connection,执行 SQL、管事务都靠它。
  • Mapper 接口:SQL 映射的“代理代言人”,咱们写的 UserMapper.java 其实是个接口,真正干活的是 MyBatis 动态生成的代理对象。

这三个角色的“作用域”(生命周期、线程安全、使用范围)直接决定了 MyBatis 能不能用对、用得稳。咱们一个一个拆解!

二、SqlSessionFactory:全局唯一,用错就崩!

1. 它是“数据库配置的总管家”

SqlSessionFactory 是 MyBatis 的“命门”——它一旦创建,就会加载所有配置(比如数据库连接池、映射文件、插件),后续所有的 SqlSession 都得靠它来“生”。

2. 为什么必须是“全局单例”?

想象一下:如果你每次操作数据库都新建一个 SqlSessionFactory,那相当于每次都要重新读取配置文件、解析映射、初始化连接池……这得多耗时?而且连接池这种资源只能全局共享,重复创建只会浪费内存,甚至导致连接泄漏!

结论SqlSessionFactory 必须是全局单例(整个应用生命周期只创建一次),就像你家的总电闸,只装一个!

3. 怎么创建?记住这行代码!

通常用 SqlSessionFactoryBuilder 读取配置文件生成:

// 读取 mybatis-config.xml 配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 构建 SqlSessionFactory(全局单例,只在应用启动时执行一次!)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

在 Spring 里更简单,直接通过 @Bean 注册成单例,完全不用自己操心~

三、SqlSession:线程隔离的“临时通道”,用完必须关!

1. 它是“数据库操作的临时工”

SqlSession 是咱们和数据库“对话”的桥梁:执行 selectinsert 要靠它,开启事务、提交回滚也要靠它。但它有个致命特点——线程不安全

2. 为什么不能跨线程/重复用?

假设你在一个线程里创建了 SqlSession,然后在另一个线程里接着用它——这时候 SqlSession 内部的连接状态(比如当前事务是否开启、缓存的 SQL 结果)可能已经被前一个线程改乱了,轻则数据错误,重则直接抛异常!

举个真实踩坑案例
之前有个同事在 Controller 里创建 SqlSession,然后传给 Service 层用,结果高并发下频繁报 Invalid bound statement(找不到 SQL 映射)。后来发现是因为 SqlSession 被多个线程共享,内部状态混乱,把刚加载的 Mapper 缓存给冲掉了……

3. 正确用法:一次请求一个会话,用完立刻关!

SqlSession 的生命周期应该和一次业务操作绑定(比如一次 HTTP 请求),用完必须关闭(释放数据库连接回连接池)。

手动管理(非 Spring 环境):

try-with-resources 自动关闭(Java 7+ 支持),安全又省心:

try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.selectById(1); // 业务操作
} // 离开作用域自动关闭 SqlSession,连接归还连接池!
Spring 整合场景:

Spring 用 SqlSessionTemplate 替代了原生 SqlSession,并且通过 ThreadLocal 实现了线程隔离。你在 Service 里注入 UserMapper,底层会自动从当前线程的 SqlSession 里拿代理对象,完全不用手动关

注意:如果用了 @Transactional 注解,Spring 会在事务结束后自动关闭 SqlSession,但如果自己手动调用了 sqlSession.close(),可能会导致事务失效,别手贱!

四、Mapper 接口:跟着 SqlSession“混”,它挂你也挂!

1. 它是“SqlSession 的提线木偶”

Mapper 接口(比如 UserMapper)本身是个普通接口,MyBatis 会通过 JDK 动态代理生成它的实现类。这个代理对象的生命周期完全依赖 SqlSession——SqlSession 什么时候关,代理对象就什么时候失效!

2. 同一个 SqlSession 下,Mapper 是“单例”的

在同一个 SqlSession 里,不管调用多少次 sqlSession.getMapper(UserMapper.class),拿到的都是同一个代理对象(MyBatis 内部缓存了)。所以别想着在 SqlSession 里重复创建 Mapper,浪费资源!

3. Spring 整合的“骚操作”:Mapper 变单例?

在 Spring 里,通过 @MapperScan 扫描 Mapper 接口,会把它们注册为 Spring Bean(默认 singleton 作用域)。但这和你手动创建的 Mapper 代理对象不冲突——Spring 底层用 SqlSessionTemplate 包装了原生 SqlSession,每次调用 Mapper 方法时,会从当前线程的 SqlSession 里动态获取代理对象,线程安全又省心

五、总结:一张表理清作用域关系

为了方便记忆,咱们总结个表格:

组件 作用域 线程安全吗? 怎么管生命周期? 常见错误
SqlSessionFactory 全局单例(应用级) 安全(无状态) 应用启动时创建,全局只用一个 重复创建导致连接池耗尽
SqlSession 线程局部(请求级) 不安全(勿共享) 一次业务操作创建,用完立刻关闭 跨线程共享、未关闭导致连接泄漏
Mapper 接口代理 依赖 SqlSession 由 SqlSession 决定 随 SqlSession 创建/销毁 跨 SqlSession 使用、手动关闭后调用

六、避坑指南:这些坑我替你踩过了!

  1. SqlSession 线程共享:别把 SqlSession 存到全局变量、static 里,更别传给其他线程!
  2. 忘记关 SqlSession:手动管理时用 try-with-resources,Spring 里别手贱调用 close()
  3. 跨 SqlSession 用 Mapper:关闭 SqlSession 后,Mapper 代理对象会失效,调用方法会报错!

最后唠叨一句:MyBatis 的作用域设计其实很简单——谁创建谁管理,线程隔离保安全。只要记住这三个组件的“生命周期”和“线程安全”特性,基本不会踩大坑!下次写代码时,不妨多检查下 SqlSession 的关闭逻辑,保证你的应用又稳又快~

觉得有用的话,点个赞收藏,开发时遇到问题再来翻!

你可能感兴趣的:(mybatis,java,mybatis作用域,SpringBoot)