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>
<!-- 配置 mybatis的环境-->
<environments default="development">
<!-- 配置环境-->
<environment id="development">
<!-- 配置事物类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源[连接池]-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- jdbc:mysql://localhost:3306/db_school?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC-->
<!-- 和javaWeb servlet三层架构中的区别这里是只需要设置时区就可以了-->
<property name="url" value="jdbc:mysql://localhost:3306/db_school?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="h123456"/>
</dataSource>
</environment>
</environments>
<!-- 注册StudentDao接口映射文件位置-->
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
</configuration>
核心配置文件参数详解:
environment标签中的id属性必须和enviroments标签中的default属性一致。
事务管理器:
第一种采用JDBC事务类型,直接使用了JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
第二种采用MANAGEd事务类型,它从提交或者回滚一个连接,而是让容器来管理事务的整个生命周期(比如JEE应用服务器的上下文)。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将closeConnection属性设置为false来阻止默认的关闭行为。例如:
< transactionManager type=“MANAGED”>
< property name=“closeConnection” value=“false”/>
</ transactionManager>
数据源(dataSource)
dataSource元素使用标准的JDBC数据源接口来配置JDBC连接对象的资源。大多数 MyBatis应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type =“[UNPOOLED|POOLED|JNDI]”)
UNPOOLED- 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据连接可用性要求不高的简单应用程序来说,是一个很好的选择。性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
POOLED- 这种数源的实现利用"池"的概念将JDBC连接对象组织起来,避免了创建新建的连接实列时所必须的初始化和认证时间。这种处理方式很流行,能使并发web应用快速响应请求。
JNDI - 这个数据源实现是为了能在入EJB 或者应用服务器这类容器中使用的,容器可以集中或外部配置数据源,然而放置一个JNDI上下文的数据源引用。
mapper 中的resource属性用于指定映射文件的位置。mapper中有多个属性可以描述映射文件,分别为:
resource=“mapper/StudentMapper.xml"文件在多级目录中要用分隔符”/"隔开。
class=“com.etime.dao.StudentDao” 指定StudentDao接口文件位置,但此时StudentDao.xml必须和接口处在同一个包中并且文件名要相同。
映射文件:
StudentMapper.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">
<!--namespace是当前mapper对应的dao接口-->
<mapper namespace="com.etime.dao.StudentDao">
<!-- select指定当前进行数据库操作的是查询-->
<!-- id的值对应的是当前dao层接口中的方法名字-->
<!-- resultType指定当前查询得到的数据要封装成的类型-->
<select id="getAllStudents" resultType="com.etime.pojo.Student">
select * from student
</select>
</mapper>
映射文件参数详解
namespacce必须为接口的完全限定名(即包名+类名的格式)
select标签中的id必须和接口中声明的方法同名
如果接口中方法有返回值,resultType必须跟方法返回值一致并采用返回值的完全限定名来表示。
底层源码分析
- 利用Resources的getResourceAsStream方法读取mybatis核心配置文件,该配置文件中注册数据源[dataSource]和映射的文件的位置[mappers标签中的mapper子标签的resource属性]
inputStream in = Resources.getResourceAsStream("config.xml");
- 展开映射文件StudentMapper.xml中定义了子查询方法、查询时所封装 结果类型和所要执行的sql语句。
< select id = "getAllStudents" resultType="com.etime.pojo.Student">
select * from student
</ select>
- 使用构建者模式SqlSessionFactoryBuilder类的builder方法创建SqlSessionFactory对象,在build方法中从字节输入流中解析数据
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(in);
SqlSessionFactoryBuilder.java的build方法:
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;
}
XMLConfigBuilder的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;
}
}
XMLConfigBuilder的parseConfiguration方法:
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
XMLConfigBuilder的mapperElement方法:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
上述代码中我们可以看到创建了一个XMLConfigBuilder对象用来解析XML,把配置文件中所有标签及标签中属性值放封装Configuration对象中并返回SqlSessionFactory对象。
- 调用SqlSessionFactory对象的openSession方法返回一个SqlSession对象,SqlSessionFactory是一个接口,我们找到它的实现类DefaultSqlSessionFactory类,点击它的openSession方法,打开openSessionFromDataSource方法。
SqlSession sqlSession = sqlSessionFactory.openSession();
DefaultSqlSessionFactory.java的openSessionFromDataSource方法:
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);
//创建DefaultSqlSession对象,即SqlSession对象
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;
}
- 调用sqlSession.getMapper(StudentDao.class)方法返回一个StudentDao接口的代理类对象。
DefaultSqlSessionFactory.java的getMapper方法:
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
Configuration.java的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry.java的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
mapperProxyFactory.java的newInstance方法:
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}