首页 简历|笔试面试

1.2万字深度解析MyBatis源码(完结)

  • 25年9月4日 发布
  • 2.72MB 共44页
1.2万字深度解析MyBatis源码(完结)1.2万字深度解析MyBatis源码(完结)1.2万字深度解析MyBatis源码(完结)1.2万字深度解析MyBatis源码(完结)1.2万字深度解析MyBatis源码(完结)

1.2 万字深度解析 MyBatis 源码(完结)

昨天有球友问 MyBatis 源

手,

接着

是一

步一

的剥

面的

?

料,

刚好

前整

理过

这里

我们

MyBatis 的源,配合大量的高清无,

个简

码图,。 发车? 。

MyBatis 使用案例

添加 MyBatis 和 MySQL 相关 pom 依赖。

<!-- MyBatis 依赖 -->

<dependency>

<groupId>org.MyBatis</groupId>

<artifactId>MyBatis</artifactId>

<version>3.5.2</version>

</dependency>

<!-- MySQL 依赖 -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>8.0.16</version>

<scope>runtime</scope>

</dependency>

本地创建数据库,创建一张表。同时可以初始化几条数据,方便后面 debug。

CREATE TABLE `t_user` (

`id` int NOT NULL AUTO_INCREMENT,

`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT

NULL,

`pwd` varchar(255) DEFAULT NULL,

`gender` int DEFAULT NULL,

`age` int DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

实体类(entity):

public class User {

private Integer id;

private String userName;

private Integer age;

private Integer gender;

//省略.....

}

创建 UserMapper.java 接口:

public interface UserMapper {

User selectById(Integer id);

}

创建 UserMapper.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.tian.MyBatis.mapper.UserMapper">

<resultMap id="User" type="com.tian.MyBatis.entity.User">

<id column="id" property="id"/>

<result column="name" property="userName"/>

</resultMap>

<select id="selectById" resultMap="User">

select * from t_user

<where>

<if test="id != null">

id = #{id}

</if>

</where>

</select>

</mapper>

创建 MyBatis-config.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="com.mysql.cj.jdbc.Driver"/>

<property name="url" value="jdbc:mysql://localhost:3306/tian?

useUnicode=true"/>

<property name="username" value="root"/>

<property name="password" value="123456"/>

</dataSource>

</environment>

</environments>

<mappers>

<mapper resource="mapper/UserMapper.xml"/>

</mappers>

</configuration>

使用案例:

public class MyBatisApplication {

public static final String URL = "jdbc:mysql://localhost:3306/mblog?

useUnicode=true";

public static final String USER = "root";

public static final String PASSWORD = "123456";

public static void main(String[] args) {

String resource = "MyBatis-config.xml";

InputStream inputStream = null;

SqlSession sqlSession = null;

try {

//读取 MyBatis-config.xml

inputStream = Resources.getResourceAsStream(resource);

//解析 MyBatis-config.xml 配置文件,创建 sqlSessionFactory

SqlSessionFactory sqlSessionFactory = new

SqlSessionFactoryBuilder().build(inputStream);

//创建 sqlSession

sqlSession = sqlSessionFactory.openSession();

//创建 userMapper 对象(UserMapper 并没有实现类)

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

//调用 userMapper 对象的方法

User user = userMapper.selectById(1);

System.out.println(user);

} catch (Exception e) {

e.printStackTrace();

} finally {

//关闭资源

try {

inputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

sqlSession.close();

}

}

}

运行输出:

User{id=1, userName='沉默王二', age=23, gender=1}

就这么简单的搞定了,下面我们来详细的分析这个案例背后的设计原理。

猜想 MyBatis 是如何设计的

从上面的案例中,我们可以大致可以猜测到 MyBatis 一共做了哪些步骤。

1.定位到 MyBatis-config.xml 并读取装载。获取输入流 InputStream。

2.解析输入流 InputStream,把 MyBatis-config.xml 配置文件中相关配置项解析,校验,保

存起来。

3.创建 sqlSessionFactory 对象,在我们的印象里,session 就是一次会话,所以我们可以

理解 sqlSessionFactory 就是个工厂类,就专门创建 sqlSession 对象,并且这个

sqlSessionFactory 工厂类是唯一不变的(单例)。

4.创建 sqlSession,SqlSession 中肯定保存了配置文件内容信息和执行数据库相关的操作。

5.获取 userMapper 对象,但是 UserMapper 是接口,并且没有实现类。怎么就可以调用

其方法呢?这里猜想可能用到了动态代理。

6.userMapper 接口中的方法是如何关联到 SQL 的,这个猜想可能是有个专门映射的类,

另外,肯定使用到了接口全路径名+方法名称,这个才能确保方法和 SQL 关联(主要是使

用的时候,都是方法名必须和 SQL 中 statementId 一致,由此猜想的)。

7.最后底层使用 JDBC 去操作数据库。

8.作为一个持久化框架,很有可能会使用到缓存,用来存储每次查询数据。

面试中遇到,让你来设计一个 MyBatis 如何设计?

配置文件解析

因为第一步是读取 MyBatis-config.xml 配置文件,这里我们就没必要阅读这部分源码了,

这里得到是 InputStream 输入流。接下来就是基于这个输入流进行一系列牛逼的操作。

我们将从下面这行代码开始:

SqlSessionFactory sqlSessionFactory = new

SqlSessionFactoryBuilder().build(inputStream);

SqlSessionFactory 没有构造方法,那么这里使用的就是默认无参构造方法,所以我们直接

进去 build 方法。

//这个方法啥也没干

public SqlSessionFactory build(InputStream inputStream) {

//调用的是另外一个 build 方法

return build(inputStream, null, null);

}

public SqlSessionFactory build(InputStream inputStream, String environment,

Properties properties) {

try {

//创建一个 XMLConfigBuilder 对象

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream,

environment, properties);

return build(parser.parse());

} catch (Exception e) {

throw ExceptionFactory.wrapException("Error building SqlSession.", e);

} finally {

ErrorContext.instance().reset();

try {

inputStream.close();

} catch (IOException e) {

// Intentionally ignore. Prefer previous error.

}

}

}

public SqlSessionFactory build(Configuration config) {

return new DefaultSqlSessionFactory(config);

}

public SqlSessionFactory build(Reader reader, String environment, Properties

properties) {

try {

XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment,

properties);

return build(parser.parse());

} catch (Exception e) {

throw ExceptionFactory.wrapException("Error building SqlSession.", e);

} finally {

ErrorContext.instance().reset();

try {

reader.close();

} catch (IOException e) {

// Intentionally ignore. Prefer previous error.

}

}

}

该类中的 build 重载的方法如下:

5236024a-e978-4508-b6c7-d4cbd9ea42a4.png

SqlSessionFactory 中提供了三种读取配置信息的方法后:字节流、字符流和

Configuration 配置类。

创建 XMLConfigBuilder 对象,这个类是 BaseBuilder 的子类,BaseBuilder 类图:

792bd358-3362-4aa5-9731-038ba59aeca9.png

看到这些子类基本上都是以 Builder 结尾,所以这里使用的是建造者设计模式。

这个类名可以猜出给类就是解析 xml 配置文件的。然后我们继续进入

public XMLConfigBuilder(InputStream inputStream, String environment,

Properties props) {

this(new XPathParser(inputStream,...);

}

MyBatis 对应解析包 org.apache.ibatis.parsing:

07c49c58-1893-4b8d-a56d-1c9c7cc22d59.png

XPathParser 基于 Java XPath 解析器,用于解析 MyBatis 中

• MyBatis-config.xml

• mapper.xml

等 XML 配置文件 。

XPathParser 主要内容:

dbdee088-13f9-4824-84ff-f20b10c85bd0.png

继续上面的源码分析:

private XMLConfigBuilder(XPathParser parser, String environment, Properties

props) {

super(new Configuration());

ErrorContext.instance().resource("SQL Mapper Configuration");

this.configuration.setVariables(props);

this.parsed = false;

this.environment = environment;

this.parser = parser;

}

构造一个 XMLConfigBuilder 对象,给属性设置相应值。

然后我们再回到 SqlSessionFactoryBuilder 中的 build 方法里:

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment,

properties);

build(parser.parse());

先看 parser.parse()方法:

public Configuration parse() {

if (parsed) {

throw new BuilderException("Each XMLConfigBuilder can only be used once.");

}

parsed = true;

//MyBatis-config.xml 的一级标签

parseConfiguration(parser.evalNode("/configuration"));

return configuration;

}

继续 parseConfiguration()方法:

private void parseConfiguration(XNode root) {

try {

//issue #117 read properties first

propertiesElement(root.evalNode("properties"));

Properties settings = settingsAsProperties(root.evalNode("settings"));

loadCustomVfs(settings);

loadCustomLogImpl(settings);

typeAliasesElement(root.evalNode("typeAliases"));

pluginElement(root.evalNode("plugins"));

objectFactoryElement(root.evalNode("objectFactory"));

objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

reflectorFactoryElement(root.evalNode("reflectorFactory"));

settingsElement(settings);

// read it after objectFactory and objectWrapperFactory issue #631

environmentsElement(root.evalNode("environments"));

databaseIdProviderElement(root.evalNode("databaseIdProvider"));

typeHandlerElement(root.evalNode("typeHandlers"));

mapperElement(root.evalNode("mappers"));

} catch (Exception e) {

throw new BuilderException("Error parsing SQL Mapper Configuration.

Cause: " + e, e);

}

}

结合 MyBatis-config.xml 配置文件和解析方法,可以得出如何关联:

cc3935ee-c6dd-495c-b675-0fc21d20eb6c.png

MyBatis-config.xml 中如果把标签当做一级标签,那么有多少个二级标签可与定义呢?

在 org.apache.ibatis.builder.xml 下的 MyBatis-3-config.dtd 中已经定义了

<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?,

objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?,

environments?, databaseIdProvider?, mappers?)>

与之对应的 MyBatis-config.xsd 中

<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>

同理,我们最关心的 Mapper.xml 中能定义哪些标签,也在 MyBatis-3-mapper.dtd 中也定

义了,另外也有与之对应的

MyBatis-mapper.xsd 文件中也能找到:

e9d8ff5d-c42c-4f6c-bc92-5a49e3481902.png

关于 MyBatis 中标签相关就介绍到此。关于 MyBatis 配置文件中有哪些标签现在是不是觉

得很轻松就能找到了?其实在 IDEA 中也会提示的。

下面我们来看看这些标签内容是如何存入 configuration 对象中?(这里例举部分,挑几

个相对重要的)。

propertiesElement()方法:

5dd7b818-5ff6-4b34-8c55-95fa89cd8d08.png

typeAliasesElement()方法:

67e1a50e-e661-4b92-a224-d2cb4edb4a02.png

插件 plugins 解析

pluginElement()方法:

private void pluginElement(XNode parent) throws Exception {

if (parent != null) {

//可以定义多个插件

for (XNode child : parent.getChildren()) {

String interceptor = child.getStringAttribute("interceptor");

Properties properties = child.getChildrenAsProperties();

Interceptor interceptorInstance = (Interceptor)

resolveClass(interceptor).newInstance();

interceptorInstance.setProperties(properties);

configuration.addInterceptor(interceptorInstance);

}

}

}

Configuration 中 interceptorChain 用来存储所有定义的插件。

//interceptorChain 中有个 List<Interceptor> interceptors

protected final InterceptorChain interceptorChain = new InterceptorChain();

//存入 interceptors 中

public void addInterceptor(Interceptor interceptor) {

interceptorChain.addInterceptor(interceptor);

}

InterceptorChain 插件链(连接链),责任链模式。

public class InterceptorChain {

private final List<Interceptor> interceptors = new ArrayList<>();

public Object pluginAll(Object target) {

for (Interceptor interceptor : interceptors) {

target = interceptor.plugin(target);

}

return target;

}

public void addInterceptor(Interceptor interceptor) {

interceptors.add(interceptor);

}

}

我们继续看看 Mapper.xml 是如何解析的。

mapper.xml 解析

我们的 Mapper.xml 在 MyBatis-config.xml 中的配置是这样的:

f6750035-1bc9-4eae-9e12-ac902bf0754c.png

使用方式有以下四种:

<--! 1 使用类路径 -->

<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 路径 -->

<mappers>

<mapper url="file:///var/mappers/AuthorMapper.xml"/>

<mapper url="file:///var/mappers/BlogMapper.xml"/>

<mapper url="file:///var/mappers/PostMapper.xml"/>

</mappers>

<--! 3 使用 java 类名 -->

<mappers>

<mapper class="org.MyBatis.builder.AuthorMapper"/>

<mapper class="org.MyBatis.builder.BlogMapper"/>

<mapper class="org.MyBatis.builder.PostMapper"/>

</mappers>

<--! 4 自动扫描包下所有映射器 -->

<mappers>

<package name="org.MyBatis.builder"/>

</mappers>

源码分析:

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 中

configuration.addMappers(mapperPackage);

} else {

String resource = child.getStringAttribute("resource");

String url = child.getStringAttribute("url");

String mapperClass = child.getStringAttribute("class");

//使用 java 类名

if (resource != null && url == null && mapperClass == null) {

ErrorContext.instance().resource(resource);

//根据文件存放目录,读取 XxxMapper.xml

InputStream inputStream = Resources.getResourceAsStream(resource);

//映射器比较复杂,调用 XMLMapperBuilder

//注意在 for 循环里每个 mapper 都重新 new 一个 XMLMapperBuilder,来解析

XMLMapperBuilder mapperParser = new

XMLMapperBuilder(inputStream, configuration, resource,

configuration.getSqlFragments());

mapperParser.parse();

//使用绝对 url 路径

} else if (resource == null && url != null && mapperClass == null) {

ErrorContext.instance().resource(url);

InputStream inputStream = Resources.getUrlAsStream(url);

//映射器比较复杂,调用 XMLMapperBuilder

XMLMapperBuilder mapperParser = new

XMLMapperBuilder(inputStream, configuration, url,

configuration.getSqlFragments());

mapperParser.parse();

//使用类路径

} else if (resource == null && url == null && mapperClass != null) {

Class<?> mapperInterface = Resources.classForName(mapperClass);

//直接把这个映射加入配置

configuration.addMapper(mapperInterface);

} else {

throw new BuilderException("A mapper element may only specify a url,

resource or class, but not more than one.");

}

}

}

}

}

public BaseBuilder(Configuration configuration) {

this.configuration = configuration;

this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();

this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();

}

private XMLMapperBuilder(.....) {

super(configuration);

this.builderAssistant = new MapperBuilderAssistant(configuration, resource);

this.parser = parser;

this.sqlFragments = sqlFragments;

this.resource = resource;

}

把这些 UserMapper 类似接口保存到 configuration 对象中。

configuration.addMapper(mapperInterface);

到这里,配置文件 MyBatis-config.xml 和我们定义映射文件 XxxMapper.xml 就全部解析完

成。

关于其他配置项,解析方式类似,最终都保存到了一个 Configuration 大对象中。

Configuration 对象类似于单例模式,就是整个 MyBatis 中只有一个 Configuration 对象。

回到 SqlSessionFactoryBuilder 类

前面讲到了 XMLConfigBuilder 中的 parse 方法,并返回了一个 Configuration 对象。

build(parser.parse());

这个 build 方法就是传入一个 Configuration 对象,然后构建一个 DefaultSqlSession 对

象。

public SqlSessionFactory build(Configuration config) {

return new DefaultSqlSessionFactory(config);

}

继续回到我们的 demo 代码中这一行代码里。

SqlSessionFactory sqlSessionFactory = new

SqlSessionFactoryBuilder().build(inputStream);

这一行代码就相当于:

SqlSessionFactory sqlSessionFactory = new new DefaultSqlSessionFactory();

到此。配置文件解析完毕。

配置文件解析流程

90ee49ba-5977-4e93-ae4d-2440eaa25cb8.png

既然已经获取到了 SqlSessionFactory,那么我们就可以构建 SqlSession 了。下面我们来看

看构建 SqlSession 的整个过程。

构建 SqlSession

前面已经做了配置文件的解析,那么现在我们来构建 SqlSession。

sqlSession = sqlSessionFactory.openSession();

前面已经分析了,这里的 sqlSessionFactory 是 DefaultSqlSessionFactory。那么此时调用

的 openSession()方法为 DefaultSqlSessionFactory 中的方法。

public class DefaultSqlSessionFactory implements SqlSessionFactory {

//配置文件所有内容

private final Configuration configuration;

//创建 session

@Override

public SqlSession openSession() {

//调用的是另外一个 openSessionFromDataSource 方法

return openSessionFromDataSource(configuration.getDefaultExecutorType(),

null, false);

}

//其实是调用这个方法

//protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

private SqlSession openSessionFromDataSource(ExecutorType execType,

TransactionIsolationLevel level, boolean autoCommit) {

Transaction tx = null;

try {

//对应 xml 标签<environments> ,这个在配置文件解析的时候就已经存放到

configuration 中了。

final Environment environment = configuration.getEnvironment();

//构建事务工厂

final TransactionFactory transactionFactory =

getTransactionFactoryFromEnvironment(environment);

//构建一个失误对象对象

tx = transactionFactory.newTransaction(environment.getDataSource(), level,

autoCommit);

//创建一个 executor 来执行 SQL

final Executor executor = configuration.newExecutor(tx, execType);

//创建一个 DefaultSqlSession 对象并返回

return new DefaultSqlSession(configuration, executor, autoCommit);

} catch (Exception e) {

closeTransaction(tx); // may have fetched a connection so lets call close()

throw ExceptionFactory.wrapException("Error opening session. Cause: " + e,

e);

} finally {

ErrorContext.instance().reset();

}

}

private TransactionFactory

getTransactionFactoryFromEnvironment(Environment environment) {

if (environment == null || environment.getTransactionFactory() == null) {

return new ManagedTransactionFactory();

}

return environment.getTransactionFactory();

}

创建事务 Transaction

Transaction 类图:

5957bd44-d29e-4528-bb8a-54d0904aebba.png

事务工厂类型可以配置为 JDBC 类型或者 MANAGED 类型。

• JdbcTransactionFactory 生产 JdbcTransaction。

• ManagedTransactionFactory 生产 ManagedTransaction。

如果配置的 JDBC,则会使用 Connection 对象的 commit()、rollback()、close()方法来管

理事务。

如果我们配置的是 MANAGED,会把事务交给容器来管理,比如 JBOSS,Weblogic。因为我

们是本地跑的程序,如果配置成 MANAGED 就会不有任何事务。

但是,如果是 Spring+MyBatis,则没有必要配置,因为我们会直接在

applicationContext.xml 里配置数据源和事务管理器,从而覆盖 MyBatis 的配置。

把事务传给 newExecutor()方法创建执行器 Executor 对象。

configuration.newExecutor(tx, execType)

创建 Executor

调用 configuration 的 newExecutor 方法创建 Executor。

final Executor executor = configuration.newExecutor(tx, execType);

//Configuration 中

public Executor newExecutor(Transaction transaction, ExecutorType

executorType) {

executorType = executorType == null ? defaultExecutorType : executorType;

executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

Executor executor;

//第一步

if (ExecutorType.BATCH == executorType) {

executor = new BatchExecutor(this, transaction);

} else if (ExecutorType.REUSE == executorType) {

executor = new ReuseExecutor(this, transaction);

} else {

executor = new SimpleExecutor(this, transaction);

}

//第二步

if (cacheEnabled) {

executor = new CachingExecutor(executor);

}

//第三步

executor = (Executor) interceptorChain.pluginAll(executor);

return executor;

}

此方法分三个步骤。

第一步:创建执行器

Executor 的基本类型有三种:SIMPLE 为默认类型。

public enum ExecutorType {

SIMPLE, REUSE, BATCH

}

Executor 类图:

1d5193eb-f1cc-44ea-9f4d-b735a92d4fc8.png

为什么要让抽象类 BaseExecutor 实现 Executor 接口,然后让具体实现类继承抽象类呢?

这就是模板方法模式的实现。

模板方法模式就是定义一个算法骨架,并允许子类为一个或者多个步骤提供实现。模板方

法是得子类可以再不改变算法结构的情况下,重新定义算法的某些步骤。

抽象方法是在子类汇总实现的,每种执行器自己实现自己的逻辑,BaseExecutor 最终会

调用到具体的子类。

抽象方法

protected abstract int doUpdate(MappedStatement ms, Object parameter)

throws SQLException;

protected abstract List<BatchResult> doFlushStatements(boolean isRollback)

throws SQLException;

protected abstract <E> List<E> doQuery(MappedStatement ms, Object

parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql

boundSql) throws SQLException;

protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms,

Object parameter, RowBounds rowBounds, BoundSql boundSql) throws

SQLException;

第二步:缓存装饰

在上面代码中的第二步

if (cacheEnabled) {

executor = new CachingExecut

开通会员 本次下载免费

所有资料全部免费下载! 推荐用户付费下载获取返佣积分! 积分可以兑换商品!
一键复制 下载文档 联系客服