目录
10.3【MyBatis】 框架原理
10.3.1 【MyBatis】 整体架构
10.3.2 【MyBatis】 运行原理
10.4 【MyBatis】 核心组件的生命周期
10.4.1 SqlSessionFactoryBuilder
10.4.2 SqlSessionFactory
10.4.3 SqlSession
10.4.4 Mapper Instances
与 Hibernate 框架相比,MyBatis 学习成本相对较低。在 MyBatis 中,SQL语句是单独存放在 XML 文件中的,这样使得 SQL 语的修改和优化比较方便,使用MyBatis 框架也变得较为灵活,因而,MyBatis 框架可适用于需求变化较多的项目。使用 MyBatis 框架可以让程序员集中精力于 SOL 语句的开发上。当前互联网电商项目多使用 MyBatis 作为持久层框架,这样不仅增强了灵活性,还可以提高数据库访问的速度。
MyBatis 整体架构可分为以下3层:接口层、核心处理层和基础支撑层。
(1)接口层:其核心是 SqlSession 接口,该接口中定义了 MyBatis 暴露给应用程序调用的 API,也就是上层应用与 MyBatis 交的桥梁。接口层在接收到调用请求时,会调用核心处理层的相应模块来完成具体的数据库操作。
(2)核心处理层:在核心处理层中实现了 MyBatis 的核心处理流程其中包括MyBatis 的初始化以及完成一次数据库操作涉及的全部流程。核心处理层实现的主要功能有配置解析、SOL 解析、参数映射、SOL 执行、结果集映射等。
(3)基础支撑层:包含整个 MyBatis 的基础模块,为核心处理层的功能提供了良好的支撑。其主要模块有反射模块、类型转换模块、日志模块、事务管理模块、缓存模块、解析器模块、资源加载模块、数据源模块等。
每一个 MyBatis 应用都以一个 SqlSessionFactory 实例为中心,SqlSessionFactory 的生命周期应存在于整个 MyBatis 应用,与此 MyBatis 应用共存亡。SqlSessionFactory 的作用是创建 SqlSession 接口对象。SqlSessionFactory 实例可以通过 SqlSessionFactoryBuilder 类的 build 方法来获得。具体来说,在 SqlSessionFactoryBuilder 类的 build 方法中通过解析 XML 配置文件或者只使用代码(本质上都是先构建 Configuration 对象)来构建 SqlSessionFactory 实例这里使用了建造者模式(Builder Patem)。在前面的示例 mybatis_first_demo 工程中,是通过 XML 文件(配置文件和映射文件来构建 SqlSessionFactory 对象的,而以下的代码是不借助 XML 文件而只使用代码来创建 SqlSessionFactory 的实例。
//这里是关于DataSource对象的构建,此处代码略
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development",transactionFactory,datasource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(StudentMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
MyBatis 使用 SqlSession 对象来封装对数据库的一次会话访问通过 SqlSession 对象实现事务的控制和数据查询。SqlSession 包含了执行 SQL 所需要的所有方法可以通过 SqlSession 实例直接运行映射的 SOL 语句,完成对数据的增、删、改、查和事务提交等(其中,增、删改操作要执行 commit 操作),事务提交后,关闭 SqlSession。MyBatis 的整个执行流程如图10-4 所示。
图10-4 MyBatis 的执行流程
以下是添加 student 表中一条记录的示例代码,用以说明 MyBatis 的执行流程。这里为了简单起见,没有在 insertStudentTest() 方法中处理异常(捕捉异常)而是在定义 insertStudentTest() 方法时抛出异常,表明异常处理是由调用 insertStudentTest() 方法的方法来处理的。
public void insertUserTest throws Exception {
// 1. 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 根据配置文件创建 sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 通过 SqlSessionFactory 获得 SqlSession 接口对象
// 获得会话,事务处理开始
SqlSession sqlSession=sqlSessionFactory.opSession();
// 4. 通过 SqlSession 接口对象获得 StudentMapper 实例
StudentMapper mapper = session.getMapper(StudentMapper.class);
// 这里设置的 Student 对象作为执行 SQL 语句的参数
// StudentMapper 接口中声明的方法: publicint addStudent(Student student);
// addStudent(Student student) 方法映射相应的 SQL 语句
Student student = new Student();
student.setSno("20171622");
student.setName("李白");
student.setAge(88);
student.setSex("男");
// 5. StudentMapper 实例执行映射的 SQL 语句,并返回映射结果
mapper.ddStudent(student);
// 切记:增、删、改操作时,要执行 commit 操作
sqlSession.commit();
// 6. 关闭 SqlSession();
sqlSession.close();
}
需要强调的是,SqlSession 的 getMapper 方法是联系应用程序和 MyBatis 的纽带,应用程序访问 getMapper 时,MyBatis 会根据传入的接口类型和对应的 XML 配置文件生成一个代理对象,这个代理对象就称为 Mapper 对象。应用程序获得 Mapper 对象后,就通过它来访问数据库。
一旦通过 SqlSessionFactoryBuilder 创建了 SqlSessionFactory,SqlSessionFactoryBuilder 就不需要存在了。因此,SqlSessionFactoryBuilder 实例的最佳生命周期是只存在于创建 SqlSessionFactory 方法中(即本地方法变量)。
SqlSessionFactory 实例一旦被创建,应该在开发者的应用程序执行期间都存在。倘若SqlSessionFactory 实例在应用程序运行期间被重复创建多次这样的代码会被出“腐化软件的气味”。关于 SqlSessionFactory 实例的创建,可以考虑使用单例模式静态单例模式或者依赖注入。
SqlSession 是一个会话,相当于 JDBC 的一个 Connection 对象,它的生命周期应该是在请求数据库处理事务的过程中。每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不能共享使用,它也是线程不安全的。因此,其最佳的存在范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在一个类的静态字段甚至是实例字段中,也不能将 SqlSession 实例的引用放在任何类型的管理范围中如 Servlet 框架的 HttpSession 对象中如果现在正使用某种 Web 框架,则要考虑将 SqlSession 放在一个和 HTTP 请求对象相似的范围内。换句话说,收到 HTTP 请求后,开发者可以打开一个 SqlSession,但返回响应后就要关闭它。关闭 Session 很重要,应该确保使用 finally 块来关闭。
映射器是用来绑定映射语句的接口。映射器接口对象可以从 SalSession 中获得。从技术上来说,当被请求时,任意映射器对象的生存最大范围与 SqlSession 对象是相同的。不管怎样,映射器对象的生存最佳范围是方法范围。也就是说,它们应该在使用它们的方法中被请求,然后就抛弃掉。例如:
SqlSeesion session=sqlSessionFactory.openSession();
try {
BologMapper mapper=session.getMapper(BlogMapper.class);
// do work
} finally {
session.close()
}