王有志,一个分享硬核Java技术的互金摸鱼侠
加入Java人的提桶跑路群:共同富裕的Java人
大家好,我是王有志。在上一篇文章的最后,我们写了一个简单的例子,今天我们就通过这个例子来看一看一个标准的 MyBatis 应用程序由哪些组件组成。
最后,文末会解答小伙伴在私信中提出的问题:当存在多个 Mapper.xml,且每个文件中都有同名的查询语句时在 MyBatis 应用中该如何进行查询?
Tips:文章中的示例,指的是《MyBatis 入门》正文中出现的简单示例和附录中“不使用 XML 构建 SqlSessionFactory”的例子,如无特别说明,默认为正文中的简单示例。
MyBatis 应用的组成
我们先来回忆一下构建简单示例的过程:
- 创建数据对象 UserDO,用于映射数据库中的 user 表;
- 创建接口 UserDAO,作为 MyBatis 映射器的命名空间;
- 创建映射器文件 UserMapper.xml,并编写了查询全部 user 表数据的 SQL 语句;
- 创建 MyBatis 的核心配置文件 mybatis-config.xml,配置了数据库信息和映射器。
以上的 4 步是我们在开始使用 MyBatis 前进行的前期配置工作,接下来是我们在应用程序中使用 MyBatis 的步骤:
- 通过 Resources 读取 mybatis-config.xml 文件,获取 Reader 对象;
- 通过 Reader 对象构建出 SqlSessionFactory,即 SQL 会话工厂;
- 通过 SqlSessionFactory 获取 SqlSession,即 SQL 会话;
- 通过 SqlSession 执行 UserMapper.xml 中的 SQL 语句,并获取到查询结果。
这 4 步是我们在应用程序中使用 MyBatis 的过程,综合以上两步的内容我们大概可以构建出如下图所示的 MyBatis 应用的基本组成:
图中的部分组件已经在我们的示例中出现过了, 但是如 Executor,MappedStatement,Configuration,ResultHandler 等组件并没有在我们的示例中出现。这是因为它们大都出现在 SqlSession 和 SqlSessionFactory 内部封装的调用过程中,因此我们在平时使用时可能“见不到”它们,但这并不是说它们不重要,相反它们是 MyBatis 中起到关键作用的组件。
关于它们我还会在 MyBatis 系列的源码篇着重进行分析,不过在此之前,我会按照图中自下向上的顺序,逐一对这些组件的作用做一个简单的说明。
Tips:Reader 是 Java 中 io 包下的工具类,因此在下文中不会出现关于 Reader 的内容。
Mapper.xml
Mapper.xml 是 MyBatis 的核心之一,是用于定义 SQL 语句和映射规则的 XML 文件,由核心配置文件 mybatis-config.xml 加载到 MyBaits 应用程序中。
Mapper.xml 的主要作用包括:
- 定义 SQL 语句:MyBatis 的 SQL 语句编写在 Mapper.xml 中(MyBatis 也支持通过注解的方式编写 SQL 语句),通过 MyBatis 提供的 XML 标签可以实现动态查询条件和嵌套查询等复杂的 SQL 语句;
- 映射结果集到 Java 对象:通过 MyBatis 的标签可以实现数据库表中的字段与 Java 对象中的字段的映射关系,可以实现一对一,一对多等复杂关系的映射;
- 接口方法绑定:Mapper.xml 中定义的 SQL 语句可以通过标签中的 id 字段与对应的 Mapper 接口中的方法进行绑定,通过调用 Mapper 接口的方法 MyBatis 将会执行 Mapper.xml 中的 SQL 语句。
下面我们对之前的示例稍作修改,来感受下 MyBatis 的中 Mapper.xml 与 Mapper 接口的方法绑定。
首先,在 UserMapper.xml 中定义一个新的查询语句,用于查询 id = 1 的用户:
<select id="selectFirstUser" resultType="com.wyz.entity.UserDO" >
select user_id, name, age, gender, id_type, id_number from user where user_id = 1
</select>
Tips:通常我们不会在 Mapper.xml 中编写如user_id = 1
这类硬编码,这里仅仅是为了举例说明,千万不要学~~~
接着我们修改 UserDAO 接口,添加两个对应的方法声明:
public interface UserMapper {
List<UserDO> selectAll();
UserDO selectFirstUser();
}
最后我们修改测试代码,通过 SqlSession 实例获取 UseDAO 接口的实例,并调用接口中的方法:
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
List<UserDO> users = userDAO.selectAll();
for(UserDO user : users) {
System.out.println(user.getName());
}
UserDO user = userDAO.selectFirstUser();
System.out.println(user.getName());
}
可以看到,这里我们通过 SqlSession 实例获取到接口 UserDAO 的实例,分别调用了接口中的方法并能够成功获取到数据,这表明我们已经将 UserMapper.xml 中编写的 SQL 语句与 UserDAO 接口中的方法绑定到了一起。
关于 Mapper.xml 的更多用法,我会在 MyBatis 系列的第 4 篇文章中和大家分享。
mybatis-config.xxml
mybatis-config.xml 是 MyBatis 应用中的核心配置文件,该文件中包含了 MyBatis 应用程序在运行时所需要的各种配置信息。
示例中,我只做了最基础的环境配置(数据库事务管理器配置,数据源配置)和映射器配置(加载映射器 UserMapper.xml),但实际上 mybatis-config.xml 中还提供了非常多的配置内容,如:别名配置(使用 typeAliases 标签),插件配置(使用 plugins 标签)和对象工厂配置(使用 objectFactory 标签)等等。
关于 mybatis-config.xml 的更多用法,我会在 MyBatis 系列的第 3 篇文章中和大家分享。
Resources
MyBatis 提供的资源加载工具,用于各种资源文件的加载和访问。Resources 提供了良好的封装,使用起来非常简单,只需要通过相对路径,即可将资源文件加载到应用程序中。
XMLConfigBuilder
XMLConfigBuilder 继承自 BaseBuilder,负责解析 MyBatis 中的 XML 配置文件(mybatis-config.xml),并通过调用XMLConfigBuilder#parse
方法构建出 Configuration 对象。
BaseBuilder 有多个子类:
BaseBuilder 的子类分别负责解析不同的文件,如:XMLConfigBuilder 负责解析 mybatis-config.xml 文件,XMLMapperBuilder 负责解析 Mapper.xml 文件等等。
Configuration
Configuration 是核心配置文件 mybatis-config.xml 在 Java 应用程序中的体现,是 MyBatis 在整个运行周期中的配置信息管理器,包含了 MyBatis 运行期间所需要的全部配置信息和映射器。
SqlSessionFactoryBuilder
**SqlSessionFactoryBuilder 使用了建造者模式,用来根据配置信息生成 SqlSessionFactory。**SqlSessionFactoryBuilder 提供了多个SqlSessionFactoryBuilder#build
的重载方法,分别接受 Reader,InputStream 和 Configuration 三种方式输入的配置信息。
示例中,我们已经在SqlSessionFactoryBuilder#build
方法中使用了 Reader 和 Configuration,而使用 InputStream 的方式与使用 Reader 的方式一模一样,代码如下所示:
@BeforeClass
public static void init() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
}
SqlSessionFactoryBuilder 的唯一作用是创建 SqlSessionFactory,当它完成了这个使命后,我们就应该毫不犹豫的抛弃它,**因此 SqlSessionFactoryBuilder 应该作为方法内的局部变量出现,生命周期仅在这个方法中,**就像示例中的那样。
SqlSessionFactory
**SqlSessionFactory 是 MyBatis 中的接口,也是 MyBatis 的核心组件之一,SqlSessionFactory 使用了工厂方法,定义了 MyBatis 获取 SqlSession 的规范。**MyBatis 官方对于 SqlSessionFactory 的定位是每个 MyBatis 应用的核心:
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
SqlSessionFactory 作为 MyBatis 应用程序中的核心,生命周期与整个 MyBatis 应用程序相同,随着应用的创建而创建,应用的停止而销毁。
SqlSessionFactory 有两个实现类:
DefaultSqlSessionFactory 是 SqlSessionFactory 的默认实现类,用于获取非线程安全的 SqlSession 实例,通过 DefaultSqlSessionFactory 获取的 SqlSession 实例在使用时还需要手动关闭(同时会提交事务),即调用SqlSession#close
方法。
SqlSessionFactory 接口提供了多个SqlSessionFactory#openSession
的重载方法:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
}
使用无参的SqlSession#openSession
方法可以获取具有如下特性的 SqlSession 实例:
- 不会自动提交数据库事务;
- 通过 mybatis-config.xml 配置的数据源获取的 Connection 实例;
- 使用数据源默认的事务隔离级别;
- 不会复用预处理语句,也不会批量进执行更新语句。
那么对于其它SqlSession#openSession
重载方法中的参数,我们能够很轻松得想到它们的作用:
boolean autoCommit
,设置 SqlSession 是否自动提交;Connection connection
,设置 SqlSession 中使用的 Connection 实例(允许通过其它数据源获取);TransactionIsolationLevel level
,设置 SqlSession 中使用的事务隔离级别。
至于 ExecutorType 参数,它是用来选择 MyBatis 执行器的,MyBatis 中定义了 3 种类型的执行器:
ExecutorType#SIMPLE
,该执行器会为每条 SQL 语句创建新的 PreparedStatement 实例;ExecutorType#REUSE
,该执行器会复用 PreparedStatement 实例;ExecutorType#BATCH
,该执行器会批量执行所有更新语句。
使用哪个SqlSession#openSession
的重载方法,需要我们根据具体的业务场景来进行选择。
Tips:因为 SqlSessionManager 同时也实现了 SqlSession 接口,而且在使用过程中更多的是作为 SqlSession 的实现而使用,所以我会将 SqlSessionManager 放在 SqlSession 的章节中进行说明。
SqlSession
SqlSession 是 MyBatis 的接口,同样也是 MyBatis 的核心组件之一,定义了 MyBatis 与数据库交互的规范,提供了执行 SQL 语句,提交/回滚事务以及获取映射器(Mapper 接口)实例的方法。
SqlSession 有两个实现类:
DefaultSqlSession 是 SqlSession 的默认实现类,通过 DefaultSqlSessionFactory 获取。
DefaultSqlSession 与 SqlSessionManager 的主要区别体现在两个方面:
- 线程安全:
- DefaultSqlSession 不是线程安全的 SqlSession 实例(也可以说是通过 DefaultSqlSessionFactory 获取的 SqlSession 实例不是线程安全的);
- SqlSessionManager 提供了线程安全的 SqlSession 实例。
- 事务管理:
- DefaultSqlSession 需要手动提交事务,或者在执行
SqlSession#close
方法时自动提交事务; - 通过 SqlSessionManager 执行 SQL 语句时,会自动的进行事务提交。
- DefaultSqlSession 需要手动提交事务,或者在执行
SqlSession 实例的生命周期对应一次数据库会话,当我们通过 SqlSessionFactory 获取 SqlSession 实例时是 SqlSession 生命周期的开始,而我们调用SqlSession#close
方法后,是 SqlSession 实例的生命周期的结束,这期间的过程通常对应着一项业务操作从开始到结束的过程,因此我们可以认为 SqlSession 实例的生命周期是一次业务操作从开始到结束的时间。
特别提醒,虽然每个 SqlSession 实例都有与之对应的 Connection 实例,且数据库交互是由 Connection 实例完成的,但由于数据库连接池的存在,调用SqlSession#close
方法后,SqlSession 实例只是将 Connection 实例“归还”到数据库连接池中,而不是调用Connection#close
来关闭 Connection 实例,因此我们不能将 SqlSession 实例的生命周期与 Connection 实例的生命周期画上等号。
Tips:通过 SqlSession 执行 SQL 语句是 iBATIS 时代的用法,在当下的环境中,特别是在 MyBatis 与 Spring 集成后,我们通常会选择通过 SqlSession 实例获取映射器实例后直接调用接口方法,即在文章开头中解释映射器接口方法绑定时的使用方式。
Executor
**Executor 是 MyBatis 中的接口,同样是 MyBatis 中的核心组件。Executor 接口定义了 MyBatis 与数据库交互的规范。**不同 Executor 的实现类提供了不同的特性,例如:SimpleExecutor 每次都会创建 PreparedStatement 对象,ReuseExecutor 会复用已经存在的 PreparedStatement 对象,BatchExecutor 用于批量执行 SQL 更新语句,CachingExecutor 提供了查询结果的缓存能力。
MyBatis 中 Executor 的体系结构如下:
关于 Executor 体系的中各实现类的具体作用与功能,我会在 MyBatis 系列的后续文章中继续和大家分享。
MappedStatement
MappedStatement 中封装了 Mapper.xml 文件中映射的 SQL 语句信息,包括 SQL 语句的 id,SQL 语句,参数映射信息,结果集映射信息,以及缓存策略等。
StatementHandler
StatementHandler 是 MyBatis 中的接口,负责 MyBatis 中的 SQL 处理,如预编译,参数设置,SQL 语句执行等。
MyBatis 中 StatementHandler 的体系结构如下:
ResultSetHandler
ResultHandler 是 MyBatis 中的接口,依旧是 MyBatis 中的核心组件。ResultHandler 只有一个实现类 DefaultResultSetHandler,负责将数据库返回的结果集映射为 Java 对象,需要注意的是 ResultSetHandler 与 ResultHandler 是不同的,ResultSetHandler 负责 MyBatis 内部将结果集映射为 Java 对象,而 ResultHandler 提供了对结果集数据的二次处理能力,允许开发者进行自定义,会在 ResultSetHandler 处理完结果集的映射后调用ResultHandler#handlerResult
方法。
问题答疑
上一篇文章中,我们只配置了一个 UserMapper.xml,因此有些小伙伴产生了迷惑,当存在多个 Mapper.xml,且每个文件中都有同名的查询语句时在 MyBatis 应用中该如何进行查询?
一句话概括就是通过 namespace + id 方式来关联到唯一的 SQL 语句映射上。类似于,当 Java 应用程序中存在多个同名 Java 类时,我们可以通过全限名的方式访问不同的 Java 类。
我们先随便建一个表,SQL 语句如下:
create table company (
id int not null primary key,
company_name varchar(50) not null,
company_address varchar(500) not null
);
接着按照上篇文章中的方式分别创建 company 表对应的 CompanyDO,CompanyDAO 和 CompanyMapper.xml,其中 CompanyMapper.xml 需要定义与 UserMapper.xml 中同名的查询语句,CompanyMapper.xml 内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wyz.dao.CompanyDAO">
<select id="selectAll" resultType="com.wyz.entity.CompanyDO" >
select id, company_name, company_address from company
</select>
</mapper>
接着我们修改 mybatis-config.xml 文件,添加映射文件 CompanyMapper.xml:
<configuration>
<!-- 省略数据库配置的部分 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/CompanyMapper.xml"/>
</mappers>
</configuration>
最后我们修改测试代码:
@Test
public void testSelectAll() {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<UserDO> users = sqlSession.selectList("com.wyz.dao.UserDAO.selectAll");
for(UserDO userDo:users) {
log.info(userDo.getName());
}
}
这样我们就可以通过 namespace + id 的方式映射到指定的 SQL 语句了。
好了,今天的内容就到这里了,如果本文对你有帮助的话,希望多多点赞支持,如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核 Java 技术的金融摸鱼侠王有志,我们下次再见!