一、Mybatis是什么
myBatis是常见的Java数据库访问层框架,相比于传统的JDBC,Mybatis可以进行更为细致的SQL优化,减少查询字段、统一的SQL管理。
二、Mybatis的原理
当我们使用mybatis的时候,通常需要配置一个xml文件,假如叫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>
<settings>
<setting name="logImpl" value="SLF4J"/>
<!--<setting name="cacheEnabled" value="true" />-->
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/tanjie?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="tanjie666"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
当我们配置了xml文件,然后需要一个mapper文件,这里我们叫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.tanjie.entity.mapper.UserMapper">
<select id="findUserById" resultType="com.tanjie.entity.User">
select * from user where id = #{id}
</select>
</mapper>
然而mybatis是如何根据我们的配置来和DB进行数据交互的?下面我们一步一步跟着源码来解答这些疑问。
2.1 构建SqlSessionFactory
package com.tanjie.mybatis;
import com.alibaba.fastjson.JSON;
import com.tanjie.entity.User;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MybatisDemo {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
//读取核心配置文件并返回InputStream流对象。
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建configuration对象,拿到数据源 dataSource,创建SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//根据一系列属性从SqlSessionFactory工厂中创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//从SqlSession中调用Executor执行数据库操作&&生成具体SQL指令
User user = sqlSession.selectOne("com.tanjie.entity.mapper.UserMapper.findUserById", 1);
System.out.println(JSON.toJSONString(user));
}
}
在这个main方法里面,首先我们读取mybatis-config.xml文件获取了一个InputStream,下一步我们构建了一个SqlSessionFactory对象,我们就从这一步入口,先看一下,mybatis是如何构建SqlSessionFactory的
当执行
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
会走到下面这段代码,
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
这里面我们需要关注的是var5=this.build(parser.parse());
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
上面代码片段的 核心就是解析我们的mybatis-config.xml文件,然后映射为一个Configuration对象,可以看到,传入的root对象就是我们的配置文件
在这个方法里面,会依次解析xml,读取里面的配置,比如我们的dataSource是配置在environments节点下的,入口为
this.environmentsElement(root.evalNode("environments"));
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (this.environment == null) {
this.environment = context.getStringAttribute("default");
}
Iterator i$ = context.getChildren().iterator();
while(i$.hasNext()) {
XNode child = (XNode)i$.next();
String id = child.getStringAttribute("id");
if (this.isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
this.configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
再比如我们配置的mapper对应的xml文件在节点mapper下面,入口为
this.mapperElement(root.evalNode("mappers"));
上面的代码即是映射为一个configuration对象,最后构建一个SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
2.2 创建SqlSession
执行代码如下
SqlSession sqlSession = sqlSessionFactory.openSession();
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
上面的代码代码片段主要做了几件事情,
2.2.1 根据configuration的Environment属性来创建事务工厂
2.2.2 根据事务工厂创建事务
2.2.3 创建执行器Executor
2.2.4 根据执行器创建SqlSession
下面的片段可以看到SqlSession对象持有Executor
2.3 Executor操作DB
当我们执行
User user = sqlSession.selectOne("com.tanjie.entity.mapper.UserMapper.findUserById", 1);
代码进入到
根据我们传入的statement,从configuration里面拿到MappedStatement,所以mybatis是根据类名+方法名来定位一个Sql的,故我们不能在我们的mapper文件里面重载某个方法。
然后拿到id对应的sql语句,和DB进行交互,返回Result
至此,mybatis是如何和DB进行交互的流程就走完了,我们画一个简单的时序图来回顾一下