Mybatis源码分析与技术原理
Posted 愉悦滴帮主)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis源码分析与技术原理相关的知识,希望对你有一定的参考价值。
前言
Mybatis框架属于ORM框架,全称(Object Relational Mapping)。用于实现面向对象main车工语言里不同类型系统之间的数据之间的转换。我们在开发中Mybatis框架通常作为Java链接mysql的工具,那么mytais底层是如何查询数据库的?又是如何将数据库对应的类型(varchar)转换为对应的Java基本类型的?下面我们来介绍mybatis的底层原理。
一、Mybatis是什么?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
二、传统 JDBC 的弊端:
1、jdbc 底层没有用连接池、操作数据库需要频繁的创建和关联链接。消耗很大的资源
2、写原生的 jdbc 代码在 java 中,一旦我们要修改 sql 的话,java 需要整体编译,不利于系统维护
3、使用 PreparedStatement 预编译的话对变量进行设置 123 数字,这样的序号不利于维护
4、返回 result 结果集也需要硬编码。
二、传统Mybatis使用步骤
1.引入包
1.如果不是Maven项目,要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。
2.如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
2.从 XML 中构建 SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。后面会再探讨 XML 配置文件的详细内容,这里先给出一个简单的示例:
<?xml version="1.0" encoding="UTF-8" ?>
<!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>
<?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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="org.apache.ibatis.demo.Blog">
SELECT * FROM Blog WHERE id = #{id}
</select>
</mapper>
当然,还有很多可以在 XML 文件中配置的选项,上面的示例仅罗列了最关键的部分。 注意 XML 头部的声明,它用来验证 XML 文档的正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。
3.从 SqlSessionFactory 中获取 SqlSession
既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:
try (SqlSession session = sqlSessionFactory.openSession()) {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}
4.最后的测试类
public class MybatisMain {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
System.out.println(blog);
}
}
三、Mybatis源码编译与下载
1.首先从github上拉去Mybatis源码项目。
在目标文件夹下,通过git clone的方式拉取代码。
2.将Mybatis源码项目导入idea开发工具。注意导入的是maven项目。
3.解决parent依赖问题:
在构建的过程中会出现找不到pom.xml中依赖的父模块mybatis-parent的问题。我们需要将paren工程克隆到本地目录中:git clone https://github.com/mybatis/parent.git ,然后先进入parent工程下进行mvn clean install 将parent工程依赖的包下载下来、并保证parent工程编译通过,这步不会出现问题,在编译的输出信息中我们会看到parent工程的版本号,如图所示:
pom.xml文件parent依赖的version标签处,如下文。
接下来修改mybatis工程的pom.xml文件中标识parent依赖的地方:
<parent>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-parent</artifactId>
<version>28-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
|
告诉我们部分插件没有指定的相应的版本号,出于工程的稳定性考虑需要对使用的插件指定其版本号,并给出了合适的版本号,如图红色方框中的文字。我们只要在mybatisg工程的pom.xml文件中找到相对应的插件处添加<version>$NUM</version> 标签即可, $NUM代表具体的版本号。到这我们再执行mvn clean install 指令就可以将mybatis工程构建成功了。
四、Mybatis连接数据库流程
Mybatis连接MySQL数据库,首先Mybatis首先会获取数据源,对数据源执行SQL语句,在执行SQL语句的时候会先获取到连接,创建句柄防止sql注入,访问数据库,最后返回结果集。
1.Mybatis是如何获取数据源
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
Blog blog = session.selectOne("org.apache.ibatis.demo.BlogMapper.selectBlog", 101);
System.out.println(blog);
}
我们的数据源是放在mybatis-config.xml这个配置文件中的,也就是说谁去解析这个配置文件谁就获得了数据源。我们通过debug上述代码的形式去分析。
debug调用链:
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
此时我们已经得到了datesource数据源
2.Mybatis是如何执行sql语句
Sql语句主要放在BlogMapper.xml这个配置文件中,也就是说谁去解析BlogMapper.xml文件,谁就获得执行sql的权利。
debug调用链:
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
此时通过buildStatementFromContext(context.evalNodes("select|insert|update|delete"));这行代码已经得到了BlogMapper.xml配置文件中的信息。后面就看如何执行sql。
面试题:Mybatis 加载 Mapper配置的方式有几种?
Mybatis官网中说到映射器(mappers)的配置方式。如下:
1.使用相对于类路径的资源引用 resource
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
2.使用完全限定资源定位符 url
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
3.使用映射器接口实现类的完全限定类名 class
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
4.将包内的映射器接口实现全部注册为映射器 package
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers
这四种加载mapper的方式中哪种优先级最高呢?
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 使用包配置的情况
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 使用mapper配置的情况
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// resource属性不为空
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// url属性不为空
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// class属性不为空
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
// resource、url和class只能存在一个
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
3.Mybatis是如何执行数据库
通过上述步骤我们已经可以得到具体的sql语句,只要交给Mybatis的执行器去执行。
Mybatis的执行器有几种?
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
Mybatis中默认使用哪一种Executor执行器?
答:SimpleExecutor执行器。
Mybatis中如何指定使用哪一种Executor执行器?
答:在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。
总结:
以上是关于Mybatis源码分析与技术原理的主要内容,如果未能解决你的问题,请参考以下文章