【MyBatis 运行过程】
传统的 JDBC 编程查询数据库的代码和过程总结:
(一)、加载驱动。
(二)、创建连接,Connection 对象。
(三)、根据 Connection 创建 Statement 或者 PreparedStatement 来执行 sql 语句。
(四)、返回结果集到 ResultSet 中。
(五)、手动将 ResultSet 映射到 JavaBean 中。
编码方式实现 MyBatis 查询数据库,方便大家理解,不使用 SpringMybatis,加入 Spring 后整体流程会复杂很多。使用 MyBatis 后能将原来的传统的 JDBC 编程编的如此简单,具体流程总结:
(一)、使用配置文件构建 SqlSessionFactory。
(二)、使用 SqlSessionFactory 获得 SqlSession,SqlSession 相当于传统 JDBC 的 Conection。
(三)、使用 SqlSession 得到 Mapper。
(四)、用 Mapper 来执行 sql 语句,并返回结果直接封装到 JavaBean 中。
总结
【MyBatis 源码分析】
下面来具体分析 MyBatis 代码的执行过程
(一)、整体架构
(二)、源码分析
大部分框架的代码流程:
(三)、我们的配置文件:
(四)、SqlSession 的实现流程
SqlSession 的接口定义:(里面定义了增删改查和提交回滚等方法)
接下来用 sqlSession 获取对应的 Mapper:
DefaultSqlSession 的 getMapper 实现:
MapperRegistry 里 getMapper 的最终实现:
这里就要说明一下,我们的接口里面只定义了抽象的增删改查,而这个接口并没有任何实现类,那么这个 xml 到底是如何与接口关联起来并生成实现类那?
接下来我们看看 newInstance 的具体实现:
(五)、正常流程的动态代理:
与传统的动态代理相比,MyBatis 的接口是没有实现类的,那么它又是怎么实现动态代理的那?
MapperProxy 的源码:
MapperMethod 的定义:
进入 DefaultSqlSession 执行对应的 sql 语句:
Executor 的实现类里面执行 query 方法:
【手动实现一个简单的 MyBatis】
(一)、创建 SqlSessionFactory 实例。
(二)、实例化过程,加载配置文件创建 Configuration 对象。
(三)、通过 factory 创建 SqlSession。
(四)、通过 SqlSession 获取 mapper 接口动态代理。
(五)、动态代理回调 SqlSession 中某查询方法。
(六)、SqlSession 将查询方法转发给 Executor。
(七)、Executor 基于 JDBC 访问数据库获取数据。
(八)、Executor 通过反射将数据转换成 POJO并返回给 SqlSession。
(九)、将数据返回给调用者。
项目整体使用 Maven 构建,mybatis-demo 是脱离 Spring 的 MyBatis 使用的例子。paul-mybatis 是我们自己实现的 mybatis 框架。
首先按照我们以前的使用 mybatis 代码时的流程,创建 mapper 接口,xml 文件,和 POJO以及集一些配置文件。
1、接口:TUserMapper
2、xml 文件:
3、实体类,属性应该与数据库想匹配:
4、数据库连接配置文件、db.properties:
关注 xml 文件,mapper 文件里的 namespace,id,resultType 和 sql 语句都要存储起来,我们定义一个 POJO 来存储这些信息。
创建一个 Configuration 类,用来保存所有配置文件和 xml 文件里的信息:
有了配置类之后,我们可以通过这个配置类构建一个 SqlSessionFactory 了。
5、 SqlSessionFactory 抽象模版
Default 实现类主要完成了两个功能,加载配置信息到 Configuration 对象里,实现创建 SqlSession 的功能。
package com.paul.mybatis.factory;
import com.paul.mybatis.confiuration.Configuration;
import com.paul.mybatis.confiuration.MappedStatement;
import com.paul.mybatis.sqlsession.DefaultSqlSession;
import com.paul.mybatis.sqlsession.SqlSession;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
*
* 1.初始化时就完成了 configuration 的实例化
* 2.工厂类,生成 sqlSession
*
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory{
//希望Configuration 是单例子并且唯一的
private final Configuration configuration = new Configuration();
// xml 文件存放的位置
private static final String MAPPER_CONFIG_LOCATION = "mappers";
// 数据库信息存放的位置
private static final String DB_CONFIG_FILE = "db.properties";
public DefaultSqlSessionFactory() {
loadDBInfo();
loadMapperInfo();
}
private void loadDBInfo() {
InputStream db = this.getClass().getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
Properties p = new Properties();
try {
p.load(db);
} catch (IOException e) {
e.printStackTrace();
}
//将配置信息写入Configuration 对象
configuration.setJdbcDriver(p.get("jdbc.driver").toString());
configuration.setJdbcUrl(p.get("jdbc.url").toString());
configuration.setJdbcUsername(p.get("jdbc.username").toString());
configuration.setJdbcPassword(p.get("jdbc.password").toString());
}
//解析并加载xml文件
private void loadMapperInfo(){
URL resources = null;
resources = this.getClass().getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
File mappers = new File(resources.getFile());
//读取文件夹下面的文件信息
if(mappers.isDirectory()){
File[] files = mappers.listFiles();
for(File file:files){
loadMapperInfo(file);
}
}
}
private void loadMapperInfo(File file){
SAXReader reader = new SAXReader();
//通过read方法读取一个文件转换成Document 对象
Document document = null;
try {
document = reader.read(file);
} catch (DocumentException e) {
e.printStackTrace();
}
//获取根结点元素对象<mapper>
Element e = document.getRootElement();
//获取命名空间namespace
String namespace = e.attribute("namespace").getData().toString();
//获取select,insert,update,delete子节点列表
List<Element> selects = e.elements("select");
List<Element> inserts = e.elements("select");
List<Element> updates = e.elements("select");
List<Element> deletes = e.elements("select");
List<Element> all = new ArrayList<>();
all.addAll(selects);
all.addAll(inserts);
all.addAll(updates);
all.addAll(deletes);
//遍历节点,组装成 MappedStatement 然后放入到configuration 对象中
for(Element ele:all){
MappedStatement mappedStatement = new MappedStatement();
String id = ele.attribute("id").getData().toString();
String resultType = ele.attribute("resultType").getData().toString();
String sql = ele.getData().toString();
mappedStatement.setId(namespace+"."+id);
mappedStatement.setResultType(resultType);
mappedStatement.setNamespace(namespace);
mappedStatement.setSql(sql);
configuration.getMappedStatement().put(namespace+"."+id,mappedStatement);
}
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
在 SqlSessionFactory 里创建了 DefaultSqlSession,我们看看它的具体实现。SqlSession里面应该封装了所有数据库的具体操作和一些获取 mapper 实现类的方法。使用动态代理生成一个加强类。这里面最终还是把数据库的相关操作转给 SqlSession,使用 mapper 能使编程更加优雅。
SqlSession 接口,定义模版方法
Default 的 SqlSession 实现类。里面需要传入 Executor,这个 Executor 里面封装了 JDBC 操作数据库的流程。我们重点关注 getMapper 方法。
动态代理的 InvocationHandler
最后来看我们的测试类
整个项目的源码在项目源码,如果你觉得或多或少对你有些帮助。