mybatis源码分析:启动过程
mybatis在开发中作为一个ORM框架使用的比较多,所谓ORM指的是Object Relation Mapping,直译过来就是对象关系映射,这个映射指的是java中的对象和数据库中的记录的映射,也就是一个java对象映射数据库中的一条记录。了解了mybatis的背景及作用下面看mybatis的使用及从源码分析启动过程。
一、概述
要使用mybatis必须要引入mybatis的jar包,由于我这里需要查看源码,使用的mybatis源码作为依赖。首先需要下载源码,可执行从github上下载,mybatis下载下来是maven工程,按照maven导入的方式进行导入即可,详细的步骤在这里不在赘述。
引入了mybatis的依赖便可以开发mybatis的程序,我这里使用的源码版本为:3-3.4.x版本。
1、核心配置文件
mybatis核心配置文件,一般命名为mybatis-config.xml,说是核心配置文件一点也不错,这个文件包含了使用mybatis的时候的所有配置,只有正确加载了此文件,mybatis才可正常工作。下面是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>
<!-- 设置日志输出为LOG4J -->
<setting name="logImpl" value="LOG4J" />
<!--将以下画线方式命名的数据库列映射到 Java 对象的驼峰式命名属性中-->
<setting name= "mapUnderscoreToCamelCase" value="true" />
</settings>
<!--简化类命名空间 -->
<typeAliases> </typeAliases> <environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/test" />
<property name="username" value="user" />
<property name="password" value="user" />
</dataSource>
</environment>
</environments>
<mappers>
<!--常规做法-->
<mapper resource="cn/com/mybatis/dao/UserMapper.xml"/>
<mapper resource="cn/com/mybatis/dao/MenuMapper.xml"/>
<!--第二种做法-->
<!--
<package name="cn.com.mybatis.dao"/>
-->
</mappers>
</configuration>
上面是一个mybatis-config.xml文件的实例,在configuration标签中配置了mappers、settings、environments等标签,这些标签代表的意思及如何解析在后面会详细分析。
这里sql的配置方式有注解和映射文件两种方式,这里采用映射文件的方式,所以在mybatis-config.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="cn.com.mybatis.dao.UserMapper">
<select id="selectUser" resultType="hashmap">
select * from e_user
</select>
</mapper>
上面的UserMapper.xml只有一个select标签,另外在mapper标签中配置了namespace属性,这个属性很关键,代表的是一个应映射文件对应的接口。下面看UserMapper接口,
package cn.com.mybatis.dao; import java.util.HashMap;
import java.util.List; public interface UserMapper { public List<HashMap> selectUser();
}
细心的读者会发现接口中的方法名和映射文件中的select标签的id是一样的,没错这里必须是一致,必须一一对应,至于为什么要保持一致,后面会通过源码分析,并且在一同一个namespace中不能包含同名的方法,也就是映射文件中的id不允许重复。
有了上面的这些配置,便可以开始mybatis之旅了,下面看下每个文件的位置,
二、详述
上面已经把mybatis的环境及代码已经分析了,下面看测试代码,
package cn.com.mybatis.test; import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List; import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder; import cn.com.mybatis.dao.UserMapper; public class TestMybatis { public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
//加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//生成一个SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//创建一个SqlSessionFactory对象
SqlSessionFactory factory = builder.build(inputStream);
//获得一个SqlSession对象
SqlSession session=factory.openSession();
//获得一个UserMapper
UserMapper userMapper=session.getMapper(UserMapper.class); List<HashMap> users=userMapper.selectUser();
System.out.println(users.size());
} }
上面的代码,即使用mybatis的过程,下面来分析。
1、读取配置文件
下面看读取mybatis核心文件的代码,
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
这里就一句话,把mybatis-config.xml转化为InputStream对象,这里mybatis-config.xml文件是在类路径下(WEB-INF/classes)下,这里Resources类是如何读取文件,后续详细分析,只要明白这里会获得InputStream就好。
2、创建SqlSessionFactoryBuilder
下面需要创建一个SqlSessionFactoryBuilder对象,看这个类名可以猜到应该使用的是建造者模式,
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
看下具体的SqlSessionFactoryBuilder类,下面是其所有的方法,
可以看到都是build方法,那么其构造方法也就是默认的。在SqlSessionFactoryBuilder类中所有的方法都是build方法,这是标准的建造者模式,可以看到返回值都是SqlSessionFactory。在mybatis中很多地方都使用了建造者模式,后边会进行专门的分析。
下面看生成SqlSessionFactory,
SqlSessionFactory factory = builder.build(inputStream);
调用的SqlSessionFactoryBuilder的build(InputStream)方法,
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
又调用下面的方法,
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
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.
}
}
}
在上面的代码中,使用inputStream生成一个XMLConfigBuilder,这里又是一个建造者模式,看XMLConfigBuilder的构造方法,
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//调用下面的构造方法
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
} private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//初始化父类BaseBuilder类的configuration
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
下面看其parse()方法,此方法构造的是在这里建造的对象是Configuration对象,
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析mybatis-config.xml文件中的<configuration>标签,把该标签中的内容
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
从上面的代码中,可以看出调用parseConfiguration方法,这个方法就是解析<configuration>标签,如下,
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析properties标签
propertiesElement(root.evalNode("properties"));
//解析settings标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//解析别名标签,例<typeAlias alias="user" type="cn.com.bean.User"/>
typeAliasesElement(root.evalNode("typeAliases"));
//解析插件标签
pluginElement(root.evalNode("plugins"));
//解析objectFactory标签,此标签的作用是mybatis每次创建结果对象的新实例时都会使用ObjectFactory,如果不设置
//则默认使用DefaultObjectFactory来创建,设置之后使用设置的
objectFactoryElement(root.evalNode("objectFactory"));
//解析objectWrapperFactory标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析reflectorFactory标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//解析environments标签
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析<mappers>标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
上面的方法后面会逐一进行分析,主要就是解析核心配置文件mybatis-config.xml中的配置,并放到Configuration对象中。
再回到上面的build(parse.parse())方法,其定义如下,
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
从上面的代码,可以看到使用configuration生成一个DefaultSqlSessionFactory对象。
3、创建SqlSessionFactory
上面分析到SqlSessionFactoryBuilder最后会返回一个DefaultSqlSessionFactory对象,
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
可以看到,把Configuration对象直接赋值给了DefaultSqlSessionFactory对象的configuration属性。
4、创建SqlSession
SqlSession session=factory.openSession();
上面的代码调用factory的openSession()方法,也就是DefaultSqlSesssionFactory的openSession()方法,
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
调用了下面的方法,
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//获得mybatis核心配置文件中的environment信息,包括dataSource id trasacationFactory
final Environment environment = configuration.getEnvironment();
//获得transactionFactory,如果environment中没有则使用ManagedTransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
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();
}
}
上面的方法返回了一个DefaultSqlSession对象,具体的过程就是使用上面的参数构造一个DefaultSqlSession对象。
5、获取Mapper对象
使用下面的代码获取一个Mapper对象,有了Mapper对象便可以调用方法,进行数据库操作,
UserMapper userMapper=session.getMapper(UserMapper.class);
上面的代码从DefaultSqlSession中调用getMapper返回一个UserMapper对象,这里UserMapper是一个代理对象,至于为什么是代理对象,先不分析,先了解其过程,
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
可以看出是从DefaultSqlSession的Configuration中获得该Mapper,下面继续看,
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//返回的是下面的方法
return mapperRegistry.getMapper(type, sqlSession);
}
//调用此方法
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
从上面中可以看到从knownMappers中根据type,这里就是UserMapper.class返回一个MapperProxyFactory,最后返回MapperProxyFactory的一个实例,
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
下面看newInstance方法,
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//JDK动态代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
到这里我们可以看到最终返回的是一个代理对象,而且是JDK动态代理的一个对象,从我们只编写了接口也可以猜出这里返回的应该是一个JDK动态代理的类,因为JDK动态代理要求必须有接口。
6、执行操作
执行操作,则直接调用其方法即可,
List<HashMap> users=userMapper.selectUser();
从上面的分析制定useMapper是代理对象,那么代理类便是上面的MapperProxy类,那么执行selectUser方法,便会执行MapperProxy的invoke方法,那么该类肯定也会实现InvocationHandler接口,下面,
在看其invoke方法,
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用的是execute方法
return mapperMethod.execute(sqlSession, args);
}
从上面可以看到调用的是mapperMethod.execute方法,并且把sqlSession方法作为参数传进去。那也就是说最后调用的sqlSession的方法,下面看,
可以看到调用的sqlSession的方法,从这里大体可以看出sqlSession是个重要的类。
三、总结
上面分析了mybatis的启动过程,包括加载核心配置文件(mybatis-config.xml)、SqlSessionFactory、SqlSession、执行操作数据库方法。这里仅仅分析了其执行过程,很多细节后续会一一分析,像加载配置文件、Configuration类、DefaultSqlSession以及如何通过接口找到对应的Mapper文件等内容。
有不正之处,欢迎指正。
mybatis源码分析:启动过程的更多相关文章
- Symfony2源码分析——启动过程2
文章地址:http://www.hcoding.com/?p=46 上一篇分析Symfony2框架源码,探究Symfony2如何完成一个请求的前半部分,前半部分可以理解为Symfony2框架为处理请求 ...
- quartz2.x源码分析——启动过程
title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...
- mysql源码分析-启动过程
mysql源码分析-启动过程 概要 # sql/mysqld.cc, 不包含psi的初始化过程 mysqld_main: // 加载my.cnf和my.cnf.d,还有命令行参数 if (load_d ...
- Symfony2源码分析——启动过程1
本文通过阅读分析Symfony2的源码,了解Symfony2启动过程中完成哪些工作,从阅读源码了解Symfony2框架. Symfony2的核心本质是把Request转换成Response的一个过程. ...
- Spring Boot源码分析-启动过程
Spring Boot作为目前最流行的Java开发框架,秉承"约定优于配置"原则,大大简化了Spring MVC繁琐的XML文件配置,基本实现零配置启动项目. 本文基于Spring ...
- Nginx学习笔记(六) 源码分析&启动过程
Nginx的启动过程 主要介绍Nginx的启动过程,可以在/core/nginx.c中找到Nginx的主函数main(),那么就从这里开始分析Nginx的启动过程. 涉及到的基本函数 源码: /* * ...
- springboot集成mybatis源码分析-启动加载mybatis过程(二)
1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplication中的@EnableAutoConfiguration 2.EnableAuto ...
- MyBatis 源码分析 - 映射文件解析过程
1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...
- MyBatis 源码分析 - 配置文件解析过程
* 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...
随机推荐
- ES6整体内容
ES6内容: 附网站链接:http://www.jscwwd.com/article/5e6488e849a13d1a89caf574
- 小白学 Python 数据分析(16):Matplotlib(一)坐标系
人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析(1):数据分析基础 小白学 Python 数据分析(2):Pandas (一)概述 小白学 Python 数据分析(3):P ...
- vue练手项目——桌面时钟
用vue实现一个简单的网页桌面时钟,主要包括时钟显示.计时.暂停.重置等几个功能. 效果图如下,页面刚进来的时候是一个时钟,时钟上显示的时.分.秒为当前实际时间,点击计时器按钮后,页面变成一个计时器, ...
- iviewadmin url 加入 Router base #viewDesignAdmin
router/index.js const router = new Router({ routes, base: '/viewDesignAdmin/', mode: 'history' ...
- Python基础篇(三)_函数及代码复用
Python基础篇_函数及代码复用 函数的定义.使用: 函数的定义:通过保留字def实现. 定义形式:def <函数名>(<参数列表>): <函数体> return ...
- Java 注解简介
一,什么叫注解 用一个词就可以描述注解,那就是元数据,即一种描述数据的数据.所以,可以说注解就是源代码的元数据.比如,下面这段代码: 1 2 3 4 @Override public String t ...
- search(2)- elasticsearch scala终端:elastic4s
上篇谈到:elasticsearch本身是一个完整的后台系统,对其的操作使用是通过终端api进行的.elasticsearch本身提供了多种编程语言的api,包括java的esjava.而elasti ...
- Cobait Strike的socks与ew代理使用
cobait strike介绍 Cobalt Strike 一款以 metasploit 为基础的 GUI 的框架式渗透测试工具,集成了端口转发.服务扫描,自动化溢出,多模式端口监听,win exe ...
- java 实现全排列
public List<List<Integer>> permute(int[] nums) { List<List<Integer>> res = n ...
- PS2手柄在arduino上进行测试,可用,供喜欢diy的朋友借鉴
#include <PS2X_lib.h> //PS2手柄PS2X ps2x; // create PS2 Controller Class//////////PS2引脚///////// ...