sqlsession是mybatis核心执行器,有状态且线程不安全,必须通过sqlsessionfactory.opensession()获取,不可new;其代理mapper接口并隔离事务与缓存,返回null多因映射失败。

SqlSession 是什么,为什么不能直接 new
SqlSession 是 MyBatis 的核心执行器,负责 CRUD、事务、缓存等实际操作。它不是普通工具类,而是有状态的、线程不安全的对象,内部持有了 Executor、Configuration 和数据库连接等资源。
常见错误是试图 new SqlSession() 或自己管理生命周期——这会绕过 MyBatis 的配置解析、插件链、一级缓存等机制,导致 SQL 不执行、参数绑定失败、甚至连接泄漏。
- 必须通过
SqlSessionFactory.openSession()获取,且每次请求应使用独立实例(或由 Spring 管理) - 手动使用时务必配对调用
session.close(),否则连接不会归还到池中 - 不要在多线程间共享同一个
SqlSession实例,哪怕只读也不安全
Mapper 接口怎么“凭空”调用 SQL
你写的 Mapper 接口没有实现类,却能执行方法,是因为 MyBatis 在启动时用 JDK 动态代理为每个接口生成了代理对象。调用 userMapper.selectById(123) 时,实际触发的是代理逻辑:根据方法签名匹配 <select id="selectById"></select> 标签,再交由 SqlSession 执行。
这个过程依赖严格约定:
立即学习“Java免费学习笔记(深入)”;
- Mapper 接口名必须与 XML 文件的
namespace完全一致(包括包路径) - 方法名必须与 XML 中
id属性完全一致 - 方法参数类型需能被 MyBatis 类型处理器识别;单个参数推荐用
@Param显式命名,避免#{param1}这种易错写法 - 返回类型若为集合,XML 中
resultType或resultMap必须可映射,否则抛BindingException
SqlSession.getMapper() 背后发生了什么
调用 sqlSession.getMapper(UserMapper.class) 并非简单反射创建实例,而是从 Configuration 中查出该接口已注册的 MappedStatement,再结合当前 SqlSession 的执行环境(如是否自动提交、是否启用缓存),组装成一个带上下文的代理对象。
这意味着:
- 同一个
UserMapper接口,从不同SqlSession获取的两个 mapper 实例,其事务、缓存作用域是隔离的 - 如果在 XML 中写了
flushCache="true",调用对应方法会清空当前SqlSession的一级缓存,但不影响其他 session - Spring 中
@MapperScan自动注册的 bean,底层仍是调用getMapper(),只是由容器托管了代理对象的生命周期
为什么有时候 mapper 方法返回 null,但日志显示 SQL 已执行
典型现象:控制台打印了完整 SQL 和参数,也看到数据库确实查到了数据,但 Java 方法返回 null。这不是网络或连接问题,而是结果映射失败。
最常见原因:
- XML 中
resultType指向了一个无默认构造函数的类,或字段名与数据库列名不匹配且没配resultMap - SQL 查询返回空结果集,而方法声明返回
User(非User?或Optional<user></user>),MyBatis 默认返回null而非抛异常 - 用了
selectOne()但数据库返回多行,MyBatis 只取第一行,其余丢弃;若想报错,应改用selectList()+ 判断 size - Mapper 方法返回
List<string></string>,但 XML 写了resultType="java.lang.String"—— 这是对的;若写成resultType="String"(没包名),则找不到类,静默失败
这种问题往往没有明显异常,只能靠日志里的「Mapped statement」和「Result handler」线索定位。别跳过 log4j2.xml 中对 org.apache.ibatis 的 DEBUG 级别输出。










