本文将从宏观角度分析 Mybatis 的架构与工作原理。
Mybatis的功能架构分为三层:
大部分情况下,我们都是在 Spring 中集成 Mybatis,如果不使用 Spring 容器的时候,引入 Mybatis 需要引入 mybatis.jar
,还需要一个全局配置文件,mybatis-config.xml
,其中控制着 Mybatis 的核心行为。
我们在 mybatis.jar 的 /org/apache/ibatis/builder/xml 包中存在 mybatis-3-config.dtd
文件和 mybatis-config.xsd
文件,此 dtd 文件就是用来规范 mybatis-config.xml
文档结构的,每一个 xml 文件都可携带一个有关其自身格式的描述的 dtd 文件,通过 dtd 文件,可验证接收到的数据。而 xsd 也可以作为 xml 文档的约束文件。
在 xml 对 dtd 的引用:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
在 xml 中对 xsd 的应用:
<configuration
xsi:schemaLocation="http://mybatis.org mybatis-config.xsd">
dtd 与 xsd 的区别?
两个都可以定义 xml 文档结构,但 dtd 功能较少,不可扩展,不是使用 xml 语法编写的,不支持命名空间,只提供非常有限的数据类型。目前,xsd 正在变为主流。
我们可以看到 mybatis-config.xsd
文件中定义了 xml 文档的一些约束:
<xs:element name="configuration">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" ref="properties"/>
<xs:element minOccurs="0" ref="settings"/>
<xs:element minOccurs="0" ref="typeAliases"/>
<xs:element minOccurs="0" ref="typeHandlers"/>
<xs:element minOccurs="0" ref="objectFactory"/>
<xs:element minOccurs="0" ref="objectWrapperFactory"/>
<xs:element minOccurs="0" ref="reflectorFactory"/>
<xs:element minOccurs="0" ref="plugins"/>
<xs:element minOccurs="0" ref="environments"/>
<xs:element minOccurs="0" ref="databaseIdProvider"/>
<xs:element minOccurs="0" ref="mappers"/>
xs:sequence>
xs:complexType>
xs:element>
<xs:element name="properties">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="property"/>
xs:sequence>
<xs:attribute name="resource"/>
<xs:attribute name="url"/>
xs:complexType>
xs:element>
<xs:element name="property">
<xs:complexType>
<xs:attribute name="name" use="required"/>
<xs:attribute name="value" use="required"/>
xs:complexType>
xs:element>
<xs:element name="environments">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="environment"/>
xs:sequence>
<xs:attribute name="default" use="required"/>
xs:complexType>
xs:element>
上述只是列举 mybatis-config.xsd
中的一部分约束,可以看到其中定义的 configuration
标签是xml文件中的顶级元素,其中此元素下的子元素又有:properties
,settings
......
其中的properties
标签中又可以定义属性 resource
和 url
,而且约束 properties
元素个数最小是0,最大没有上限,引用了 property
标签
其中的environments
标签中可以定义 environment
子标签,而environment
子标签中可以定义属性 id
,还可以定义子标签 transactionManager
和 dataSource
官网给出了 mybatis-config.xml
文件示例:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
mappers>
configuration>
我们可以看到在 mybatis-config.xml
中定义了映射文件,这些映射的xml文件中包含了 SQL 和映射定义信息。mybatis-config.xml
文件可以加载多个映射文件,每个文件对应数据库中的一个表。
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory
的实例为核心的。SqlSessionFactory
的实例可以通过 SqlSessionFactoryBuilder
获得。而 SqlSessionFactoryBuilder
则可以从 XML 配置文件或一个预先配置的 Configuration
实例来构建出 SqlSessionFactory
实例。
mybatis-config.xml
文件构建 SqlSessionFactory
示例String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactory
示例DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
创建了 SqlSessionFactory
示例,就可以获得 SqlSession
示例,SqlSession
提供了在数据库执行 SQL 所需的所有方法,还可以通过 SqlSession
实例直接执行已映射的SQL语句。
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}
我们看看这段代码做了什么?
来看一个官网给出的 xml 映射语句的实例,它可以满足上述中 SqlSession
的调用。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
select>
mapper>
在命名空间 “org.mybatis.example.BlogMapper” 中定义了一个名为 “selectBlog” 的映射语句,这样你就可以用全限定名 “org.mybatis.example.BlogMapper.selectBlog” 来调用映射语句了。
其中,命名空间必须指定,作用有两个:
创建了 SqlSession
对象,就可以执行已映射的 SQL 语句了,而真正执行SQL的其实是 Executor
执行器。Mybatis 是一个持久层框架,用来操作数据库的,那么使用框架前我们用的是 jdbc 操作数据库,jdbc 执行 SQL 语句的是 Statement
类,mybatis 要想执行 SQL 也要依赖这个类,所以它对 JDBC 进行了封装,而封装起来的类就是 Executor
,它将根据 SqlSession 传递的参数动态的生成需要执行的 SQL 语句,同时负责查询缓存的维护。
在 Executor
接口的执行方法中有一个 MappedStatement
类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的 id,参数等信息。
MappedStatement
类位于 mybatis包的org.apache.ibatis.mapping
目录下,是一个 final
类,每一个 MappedStatement
实例对应 mapper.xml 中配置的一个 SQL 语句。
输入参数类型可以是 Map
,List
等集合类型,也可以是基本数据类型和 POJO
类型,输入参数映射过程类似于 JDBC 对 perparedStatement
对象设置参数的过程。
输出结果类型可以是 Map
,List
等集合类型,也可以是基本数据类型和 POJO 类型,输出结果映射过程类似于 JDBC 对结果集的解析过程。
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory
,就不再需要它了。 因此 SqlSessionFactoryBuilder
实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder
来创建多个 SqlSessionFactory
实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactory
一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory
的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory
被视为一种代码“坏习惯”。因此 SqlSessionFactory
的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
每个线程都应该有它自己的 SqlSession
实例。SqlSession
的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession
实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession
实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession
。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession
放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession
,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally
块中。
本文主要从宏观角度讲解 Mybatis 的架构模式与工作原理,关于更细节的实现原理会在后续文章中继续更新,如果感兴趣可收藏此专栏。