乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

SqlSessionFactory与SqlSession

导学

通过本节学习,你将能够:

  • 区分 SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession 三者的职责边界
  • 掌握三者的正确作用域:方法级、应用级、请求级
  • 理解 SqlSession 的线程不安全性及资源释放规范
  • 写出符合生产标准的 SqlSession 获取与关闭代码

定义

它们分别是什么

对象职责类比
SqlSessionFactoryBuilder读取配置流,构建 SqlSessionFactory建筑工人(盖完楼即离开)
SqlSessionFactory创建 SqlSession 的工厂,持有全局配置楼盘工厂(全局唯一,持续生产)
SqlSession与数据库交互的会话,执行 SQL 语句单次看房专车(每次请求一辆,用完归还)

解决了什么痛点

在 JDBC 原始写法中,开发者需要手动处理以下问题:

  1. 连接管理混乱:每次操作都新建连接还是复用连接?代码中散落着 DriverManager.getConnection()
  2. 线程安全问题:多个线程共用一个 Connection 会导致并发异常
  3. 资源泄漏风险:忘记 close() 会造成连接池耗尽,系统崩溃

MyBatis 通过三层对象的设计,将这些问题分层隔离:

  • Builder 层负责一次性解析配置,避免重复解析 XML 的开销
  • Factory 层负责全局缓存配置和连接池,保证应用内统一视图
  • Session 层负责单次请求的完整生命周期,天然与请求线程绑定

核心原理

生命周期与作用域时序图

时序图解读:

  1. 应用启动时:Builder 一次性解析 XML,创建 Factory 后立即被垃圾回收
  2. 每次请求时:从 Factory 获取新的 Session,Session 从连接池借用 Connection
  3. 请求结束时:Session 关闭,Connection 归还连接池,Session 内部一级缓存清空

作用域规范

对象推荐作用域原因错误做法的后果
SqlSessionFactoryBuilder方法作用域(局部变量)只需创建一次 Factory,之后无存在价值长期持有浪费内存,且配置变更后无法重新加载
SqlSessionFactory应用作用域(单例/静态)创建代价高,包含解析后的全量配置和连接池多次创建导致连接池重复初始化,连接数暴涨
SqlSession请求/方法作用域线程不安全,持有数据库连接和一级缓存多线程共用导致并发修改异常;长期不关闭导致连接池耗尽

完整示例

场景说明

乐途公司学生管理系统需要查询学生信息。本节展示如何正确管理 SqlSessionFactory 和 SqlSession 的生命周期,包括单例工厂的实现和 try-with-resources 的规范用法。

操作前的数据库表结构及初始数据

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00

单例 SqlSessionFactory 工具类

package com.fly.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * MyBatis 工具类:应用级单例管理 SqlSessionFactory
 * 
 * 设计要点:
 * 1. 静态代码块初始化,保证类加载时即完成 Factory 创建
 * 2. SqlSessionFactoryBuilder 在创建完 Factory 后即被释放(方法作用域)
 * 3. SqlSessionFactory 作为静态变量全局唯一(应用作用域)
 */
public class MyBatisUtil {
    
    private static SqlSessionFactory sqlSessionFactory;
    
    static {
        String resource = "mybatis-config.xml";
        try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
            // Builder 是局部变量,build() 后即被回收
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            System.out.println("【初始化】SqlSessionFactory 创建成功,应用级单例");
        } catch (IOException e) {
            throw new RuntimeException("MyBatis 初始化失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 获取全局唯一的 SqlSessionFactory
     */
    public static SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }
    
    // 禁止实例化
    private MyBatisUtil() {}
}

SqlSession 的 5 种 openSession() 重载

// 方式1:默认开启事务,手动提交
SqlSession session = factory.openSession();

// 方式2:指定是否自动提交
SqlSession session = factory.openSession(true);  // 自动提交
SqlSession session = factory.openSession(false); // 手动提交(默认)

// 方式3:指定连接隔离级别
SqlSession session = factory.openSession(Connection.TRANSACTION_READ_COMMITTED);

// 方式4:指定是否自动提交 + 隔离级别
SqlSession session = factory.openSession(Connection.TRANSACTION_READ_COMMITTED, true);

// 方式5:指定 Executor 类型(SIMPLE/REUSE/BATCH)
SqlSession session = factory.openSession(ExecutorType.BATCH);

规范的 CRUD 操作代码

package com.fly.test;

import com.fly.entity.Student;
import com.fly.mapper.StudentMapper;
import com.fly.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;

public class SessionLifecycleDemo {
    
    /**
     * 查询操作:try-with-resources 自动关闭 Session
     */
    public Student findStudentById(int id) {
        // SqlSession 是方法作用域,每次请求新建
        try (SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession()) {
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            Student student = mapper.findById(id);
            
            System.out.println("【查询结果】id=" + id + " => " + student.getName());
            return student;
        } // Session 自动关闭,Connection 归还连接池
    }
    
    /**
     * 插入操作:需要手动提交事务
     */
    public int insertStudent(Student student) {
        try (SqlSession session = MyBatisUtil.getSqlSessionFactory().openSession()) {
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            int rows = mapper.insert(student);
            
            // 非自动提交模式下,必须手动 commit
            session.commit();
            System.out.println("【插入成功】影响行数: " + rows + ", 新学生: " + student.getName());
            return rows;
        }
    }
    
    /**
     * 错误示范:将 SqlSession 作为实例变量(线程不安全!)
     */
    // private SqlSession badSession = MyBatisUtil.getSqlSessionFactory().openSession(); // 严禁!
}

实际执行结果

查询执行:

【初始化】SqlSessionFactory 创建成功,应用级单例
【查询结果】id=1 => 大翔
==>  Preparing: SELECT id, name, age, major, score FROM student WHERE id = ?
==> Parameters: 1(Integer)
<==    Columns: id, name, age, major, score
<==        Row: 1, 大翔, 22, 计算机科学, 95.50
<==      Total: 1

插入执行(插入新学生 "赵六"):

【插入成功】影响行数: 1, 新学生: 赵六
==>  Preparing: INSERT INTO student (name, age, major, score) VALUES (?, ?, ?, ?)
==> Parameters: 赵六(String), 23(Integer), 人工智能(String), 91.00(BigDecimal)
<==    Updates: 1

插入后数据状态:

idnameagemajorscore
1大翔22计算机科学95.50
2白歌21软件工程88.00
3小崔20计算机科学92.00
4黄俪21信息安全90.50
5李眉22软件工程87.00
6赵六23人工智能91.00

易错场景 / 常见误区

误区错误代码/做法后果正解
SqlSessionFactoryBuilder 长期持有将 Builder 设为静态变量,每次 builder.build()内存泄漏,配置无法热更新Builder 用完即丢,局部变量
SqlSessionFactory 多次创建每次请求都 new SqlSessionFactoryBuilder().build()连接池重复初始化,连接数暴涨应用级单例,静态持有
SqlSession 作为实例/静态变量private SqlSession session = factory.openSession()多线程并发异常,连接长期占用方法级局部变量,try-with-resources
忘记提交事务插入后未调用 session.commit()数据回滚,看似成功实则未写入非自动提交模式下必须手动 commit
异常时未回滚catch 块中未 session.rollback()脏数据残留,数据不一致catch 中调用 rollback,finally 中 close
关闭顺序错误先关 Factory 再关 Session无意义,Factory 是应用级只关 Session,Factory 不关

面试考点

Q1:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession 的作用域分别是什么?为什么要这样设计?

A: SqlSessionFactoryBuilder 是方法作用域,因为它只需一次性解析配置创建 Factory,之后无存在价值,长期持有浪费内存。SqlSessionFactory 是应用作用域(单例),创建代价高且包含连接池,全局唯一可避免重复初始化。SqlSession 是请求/方法作用域,它线程不安全且持有数据库连接,每次请求新建一个,用完立即关闭,否则会导致连接池耗尽和并发异常。

Q2:SqlSession 是线程安全的吗?如果必须在多线程环境共享怎么办?

A: SqlSession 不是线程安全的。它内部持有数据库连接和一级缓存(HashMap),多线程并发操作会导致数据混乱和 JDBC 连接异常。正确做法是每个线程/每次请求独立获取自己的 SqlSession。如果必须在多线程环境共享数据,应使用 SqlSessionFactory 为每个线程创建独立的 Session,或使用 Spring 等框架的事务管理器来绑定线程与 Session。

Q3:openSession() 和 openSession(true) 有什么区别?生产环境推荐哪种?

A: openSession() 默认创建手动提交模式的 Session,DML 操作后必须调用 session.commit(),适合需要事务控制的业务场景。openSession(true) 开启自动提交,每条 SQL 执行后立即提交,无法保证事务原子性,仅适用于简单查询或无事务要求的场景。生产环境推荐手动提交模式,由业务层控制事务边界,确保数据一致性。

Q4:SqlSession 的三种 ExecutorType 有什么区别?

A: SIMPLE(默认)为每条语句创建新的 PreparedStatement。REUSE 复用 PreparedStatement,减少编译开销,适合同一会话中重复执行相同结构的 SQL。BATCH 将多条修改语句批量发送给数据库,减少网络往返,适合大批量插入/更新场景,但此时 select 操作可能返回不正确结果。


小结

本节深入讲解了 MyBatis 运行时最核心的两个对象:SqlSessionFactory 和 SqlSession。通过生命周期时序图,我们理解了 Builder 的 "用过即丢"、Factory 的 "全局唯一"、Session 的 "用完即关" 三大原则。完整的工具类和 CRUD 示例展示了生产级代码的写法,而易错场景表则警示了线程安全和资源泄漏的陷阱。

关键记忆点:

  • Builder = 方法级,Factory = 应用级单例,Session = 请求级 try-with-resources
  • SqlSession 线程不安全,严禁作为实例变量或静态变量
  • 非自动提交模式下,DML 必须手动 commit,异常时 rollback
  • 连接池中的 Connection 由 Session 借用,Session.close() 即归还

下一章引子

现在我们已经有了全局唯一的 SqlSessionFactory,也知道了如何正确打开和关闭 SqlSession。但 Session 拿到之后,具体怎么执行 SQL?实体类怎么写?Mapper 接口和 XML 怎么对应?下一节将带你完成第一个完整的 MyBatis 程序——从实体类、Mapper 接口、Mapper XML 到测试代码,手把手走完 CRUD 全流程,并展示真实的控制台 SQL 输出。

上一页
SqlSessionFactoryBuilder与openSession重载
下一页
SqlSession核心方法