文章目录
简述:
背景:
大神指点学习spring源码前,最好的敲门砖就是spring的mybatis。因此我准备先学习mybatis源码。
Mybatis与 JDBC 编程的比较 :
1、通过配置文件可以灵活方便的创建连接池
2、sql也是在配置文件中,将sql与java代码分离。
3、传参和解析结果都可以直接映射到对应的java对象。
ORM:
ORM(Object Relational Mapping)框架采用元数据来描述对象与关系映射的细节,元数据一般采用XML格式,并且存放在专门的对象一映射文件中。
个人理解:就是通过ORM来完成数据库表和任何一种编程语言中描述对象的映射和操作,以此来灵活高效的操作数据。
什么是MyBatis?
mybais官网:https://mybatis.org/mybatis-3
MyBatis是非常主流的持久性框架,支持自定义SQL,存储过程和高级映射。MyBatis消除了几乎所有的JDBC代码以及参数的手动设置和结果检索。MyBatis可以使用简单的XML或注释进行配置,并将原始数据类型,映射接口和Java POJO(普通的旧Java对象)映射到数据库记录。
入门:
下载源码:
参考上一篇spring5准备里的方法,使用码云来下载源码。
下载mybatis前要下载好mybatis的pren项目,因为mybatis项目是依赖于它的。
https://github.com/mybatis/parent
https://github.com/mybatis/mybatis-3
The MyBatis parent POM which has to be inherited by all MyBatis modules
所有mybatis的模块都要依赖于parent ,所以导入项目也要先导入parent 。
导入项目:
导入parent 项目后,执行maven clean install 。
存在一些警告,解决办法
新安装maven远程仓库没有指定为阿里云导致的,可以修改
maven目录/conf/settings.xml:
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
pom.xml依赖爆红。
主流程代码:
//1.读取mybatis的主配置文件
InputStream inputStream = Resources.getResourceAsStream("resources/mybatis-config.xml");
//2.使用构建者模式,得到SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
//3.使用工厂模式,得到一个可以创建dao代理对象的SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.使用代理模式,使用SqlSession创建mapper的代理对象
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
//5.使用代理对象执行操作:查询
List<Blog> blogs = mapper.selectOne(1);
System.out.println(blogs.get(0).getTitle());
1、读取mybatis的主配置文件源码路线:
InputStream inputStream = Resources.getResourceAsStream("resources/mybatis-config.xml");
底层流程:
-->Resources.getResourceAsStream("resources/mybatis-config.xml");
-->classLoaderWrapper.getResourceAsStream(resource, loader);
-->getResourceAsStream(String resource, ClassLoader[] classLoader)
-->ClassLoader.getResourceAsStream(resource);
-->URL url = getResource(name);
return url != null ? url.openStream() : null;
这一步出现的问题:如果路径想直接写文件名,就出现下面的问题。
下载下来的源码结构是这样的,我在resource文件夹中写的主配置文件,text类写在org.apache.ibatis.dome下。
原因:resources文件夹并不是Resources Root文件夹,需要改为Resources Root文件夹
因为resources文件夹在java下,所以无法被标记为,需要先将该文件夹移到与java文件夹同级,如下图,才可以解决
2、使用构建者模式,得到SqlSessionFactory对象
SqlSessionFactoryBuilder
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
底层流程:
SqlSessionFactoryBuilder:
build(inputStream)
-->build(inputStream, null, null);
-->XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
-->build(new XMLConfigBuilder(inputStream, environment, properties).parse());
XMLConfigBuilder:
第一步很简单就是创建一下XMLConfigBuilder:
-->this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
使用XPathParser来解析配置文件,获取所有的document
this调用私有方法XMLConfigBuilder:
new Configuration();
Configuration:
Configuration():为typeAliasRegistry的各个属性赋值。
第二步:使用XMLConfigBuilder进行解析:
XMLConfigBuilder:
public Configuration parse() {
if (parsed) {//parsed在上一步构建的时候设置为false
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
//每次使用就会设置为true,如果进来就是true代表已经解析过了,抛异常:每个XMLConfigBuilder只能使用一次
}
parsed = true;
//首先解析的就是configuration标签,因此主配置文件也是以configuration为顶级标签,如下xml实例
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
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>
<properties resource="jdbc.properties"></properties>
<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="mapper/BlogMapper.xml"/>
</mappers>
</configuration>
里面的解析方法evalNode:解析节点
evaluate(String expression, Object item, QName returnType)
-->Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
-->XPathImpl类:getResultAsType根据传来的XPathConstants.NODE类型,知晓解析的是xml中的节点。来完成进一步解析,获取xml中的信息并将完成
Maps to Java {@link org.w3c.dom.Node}的转换。
将解析得到的信息封装到Configuration:XMLConfigBuilder类:
//根据这个方法,也就知道了,mybatis的配置文件标签顺序,尽量按下面的解析顺序来配置,否则会出现启动报错的问题,比如后面的标签内容需要去前面标签内容的值来作为自己的值 这样的情况
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);
}
}
xml配置顺序和简单实例:
<configuration>
<properties resource="jdbc.properties"></properties>
<settings><!--settings用来设置全局参数-->
<!--开启缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>配置别名
<typeAlias alias="role" type="com.*.类名">
</typeAliases>
<plugins>
<!--在 MyBatis 中,对某种方法进行拦截调用的机制,被称为 plugin 插件-->
<plugin interceptor="">
<!--interceptor配置拦截器的类路径-->
</plugin>
</plugins>
<objectFactory type=""></objectFactory>
<objectWrapperFactory type=""/>
<reflectorFactory type=""/>
<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>
<databaseIdProvider type=""></databaseIdProvider>
<typeHandlers></typeHandlers>
<mappers></mappers>
</configuration>
再次使用evalNode解析根据不同标签名,来获取相关信息。然后根据不同标签和每种标签对应的属性来解析。标签名为xxx,解析的方法就是xxxElement
例如properties节点的propertiesElement解析
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
会判断路径是通过url还是相对路径resource来配置的,
是否合法等,来获取相应的资源。
最先获取解析的是properties,因此配置文件中properties标签要放第一个,
否则会导致后面的标签用到properties中的值时还没获取到值而报错。
这里我试了,如果你放到后面也会报语法错误。
再比如:environmentsElement(root.evalNode(“environments”));
获取environments的所有environment,然后读取environment的transactionManager和DataSource
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());
}
}
}
最终将xml解析的结果封装成configuration类。
第三步就是创建工厂:
用封装好的Configuration 创建DefaultSqlSessionFactory,再已多态的形式返回。DefaultSqlSessionFactory只有一个属性就是Configuration。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
3、使用工厂模式,得到一个可以创建dao代理对象的SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
底层调用的是
DefaultSqlSessionFactory:
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//从configuration获取数据源信息、事务配置、执行器类型创建Executor,并用这些信息创建DefaultSqlSession并返回
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
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();
}
}
Executor:后面再研究
//获取的处理器是默认的处理器:
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//处理器分三种,
public enum ExecutorType {
SIMPLE, REUSE, BATCH
}
4、使用代理模式,使用SqlSession创建mapper的代理对象
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
DefaultSqlSession类
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
MapperRegistry类
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//获取指定mapper接口对应的代理类工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//使用sqlSession的信息获取代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
最终调用工厂来获取mapper接口的动态代理对象
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
5.使用代理对象执行操作:查询
List blogs = mapper.selectOne(1);
System.out.println(blogs.get(0).getTitle());