作者:小傅哥

系列:https://bugstack.cn/md/spring/develop-mybatis/2022-03-20-%E7%AC%AC1%E7%AB%A0%EF%BC%9A%E5%BC%80%E7%AF%87%E4%BB%8B%E7%BB%8D%EF%BC%8C%E6%89%8B%E5%86%99Mybatis%E8%83%BD%E7%BB%99%E4%BD%A0%E5%B8%A6%E6%9D%A5%E4%BB%80%E4%B9%88%EF%BC%9F.html

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

你是怎么面对功能迭代的?

其实很多程序员在刚开始做编程或者新加入一家公司时,都没有多少机会可以做一个新项目,大部分时候都是在老项目上不断的迭代更新。在这个过程你可能要学习N个前人留下的各式各样的风格迥异的代码片段,在这些纵横交错的流程中,找到一席之地,把自己的ifelse加进去。

虽然这样胡乱的加ifelse,刚上手就“摆烂”的心态,让人很难受。但要想在已经被压缩的工期下,还能交付出高质量的代码其实也很难完成,所以一部分研发被逼到能用就行,能跑就可以。

但说回来,其实不能逐步清理一片屎山,让代码在你的手上逐步清晰、整洁、干净,很多时候也是作为码农自身经验的不足,不懂得系统重构、不了解设计原则、不熟悉业务背景、不清楚产品走向等等原因造成的。所以最好的办法是提升自身的能力,没接到一次需求都有一些技术上的改变,既然它是屎山,那就当做打怪升级了,修一点、改一块、补一片,总会在你手上越来越易于维护和扩展的。

二、目标

在我们渐进式的逐步实现 Mybatis 框架过程中,首先我们要有一个目标导向的思路,也就是说 Mybatis 的核心逻辑怎么实现。

其实我们可以把这样一个 ORM 框架的目标,简单的描述成是为了给一个接口提供代理类,类中包括了对 Mapper 也就是 xml 文件中的 SQL 信息(类型入参出参条件)进行解析和处理,这个处理过程就是对数据库的操作以及返回对应的结果给到接口。如图 4-1

那么按照 ORM 核心流程的执行过程,我们本章节就需要在上一章节的基础上,继续扩展对 Mapper 文件的解析以及提取出对应的 SQL 文件。并在当前这个阶段,可以满足我们调用 DAO 接口方法的时候,可以返回 Mapper 中对应的待执行 SQL 语句。为了不至于把整个工程撑大,小傅哥会带着大家逐步完成这些内容,所以本章节暂时不会对数据库进行操作,待后续逐步实现

三、设计

结合上一章节我们使用了 MapperRegistry 对包路径进行扫描注册映射器,并在 DefaultSqlSession 中进行使用。那么在我们可以把这些命名空间、SQL描述、映射信息统一维护到每一个 DAO 对应的 Mapper XML 的文件以后,其实 XML 就是我们的源头了。通过对 XML 文件的解析和处理就可以完成 Mapper 映射器的注册和 SQL 管理。这样也就更加我们操作和使用了。如图 4-2

  • 首先需要定义 SqlSessionFactoryBuilder 工厂建造者模式类,通过入口 IO 的方式对 XML 文件进行解析。当前我们主要以解析 SQL 部分为主,并注册映射器,串联出整个核心流程的脉络。
  • 文件解析以后会存放到 Configuration 配置类中,接下来你会看到这个配置类会被串联到整个 Mybatis 流程中,所有内容存放和读取都离不开这个类。如我们在 DefaultSqlSession 中获取 Mapper 和执行 selectOne 也同样是需要在 Configuration 配置类中进行读取操作。

四、实现

1. 工程结构

mybatis-step-03
└── src
├── main
│ └── java
│ └── cn.bugstack.mybatis
│ ├── binding
│ │ ├── MapperMethod.java
│ │ ├── MapperProxy.java
│ │ ├── MapperProxyFactory.java
│ │ └── MapperRegistry.java
│ ├── builder
│ │ ├── xml
│ │ │ └── XMLConfigBuilder.java
│ │ └── BaseBuilder.java
│ ├── io
│ │ └── Resources.java
│ ├── mapping
│ │ ├── MappedStatement.java
│ │ └── SqlCommandType.java
│ └── session
│ ├── defaults
│ │ ├── DefaultSqlSession.java
│ │ └── DefaultSqlSessionFactory.java
│ ├── Configuration.java
│ ├── SqlSession.java
│ ├── SqlSessionFactory.java
│ └── SqlSessionFactoryBuilder.java
└── test
├── java
│ └── cn.bugstack.mybatis.test.dao
│ ├── dao
│ │ └── IUserDao.java
│ ├── po
│ │ └── User.java
│ └── ApiTest.java
└── resources
├── mapper
│ └──User_Mapper.xml
└── mybatis-config-datasource.xml

XML 解析和注册类实现关系,如图 4-2

  • SqlSessionFactoryBuilder 作为整个 Mybatis 的入口,提供建造者工厂,包装 XML 解析处理,并返回对应 SqlSessionFactory 处理类。
  • 通过解析把 XML 信息注册到 Configuration 配置类中,再通过传递 Configuration 配置类到各个逻辑处理类里,包括 DefaultSqlSession 中,这样就可以在获取映射器和执行SQL的时候,从配置类中拿到对应的内容了。

2. 构建SqlSessionFactory建造者工厂

源码详见cn.bugstack.mybatis.session.SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(Reader reader) {
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(reader);
return build(xmlConfigBuilder.parse());
} public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
} }
  • SqlSessionFactoryBuilder 是作为整个 Mybatis 的入口类,通过指定解析XML的IO,引导整个流程的启动。
  • 从这个类开始新增加了 XMLConfigBuilder、Configuration 两个处理类,分别用于解析 XML 和串联整个流程的对象保存操作。接下来我们会分别介绍这些新引入的对象。

3. XML 解析处理

源码详见cn.bugstack.mybatis.builder.xml.XMLConfigBuilder

public class XMLConfigBuilder extends BaseBuilder {

    private Element root;

    public XMLConfigBuilder(Reader reader) {
// 1. 调用父类初始化Configuration
super(new Configuration());
// 2. dom4j 处理 xml
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new InputSource(reader));
root = document.getRootElement();
} catch (DocumentException e) {
e.printStackTrace();
}
} public Configuration parse() {
try {
// 解析映射器
mapperElement(root.element("mappers"));
} catch (Exception e) {
throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
return configuration;
} private void mapperElement(Element mappers) throws Exception {
List<Element> mapperList = mappers.elements("mapper");
for (Element e : mapperList) {
// 解析处理,具体参照源码 // 添加解析 SQL
configuration.addMappedStatement(mappedStatement);
} // 注册Mapper映射器
configuration.addMapper(Resources.classForName(namespace));
}
} }
  • XMLConfigBuilder 核心操作在于初始化 Configuration,因为 Configuration 的使用离解析 XML 和存放是最近的操作,所以放在这里比较适合。
  • 之后就是具体的 parse() 解析操作,并把解析后的信息,通过 Configuration 配置类进行存放,包括:添加解析 SQL、注册Mapper映射器。
  • 解析配置整体包括:类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器,但目前我们还不需要那么多,所以只做一些必要的 SQL 解析处理。

4. 通过配置类包装注册机和SQL语句

源码详见(配置项)cn.bugstack.mybatis.session.Configuration

public class Configuration {

    /**
* 映射注册机
*/
protected MapperRegistry mapperRegistry = new MapperRegistry(this); /**
* 映射的语句,存在Map里
*/
protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(); public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
} public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
}

在配置类中添加映射器注册机和映射语句的存放;

  • 映射器注册机是我们上一章节实现的内容,用于注册 Mapper 映射器锁提供的操作类。
  • 另外一个 MappedStatement 是本章节新添加的 SQL 信息记录对象,包括记录:SQL类型、SQL语句、入参类型、出参类型等。详细可参照源码

5. DefaultSqlSession结合配置项获取信息

源码详见cn.bugstack.mybatis.session.defaults.DefaultSqlSession

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    @Override
public <T> T selectOne(String statement, Object parameter) {
MappedStatement mappedStatement = configuration.getMappedStatement(statement);
return (T) ("你被代理了!" + "\n方法:" + statement + "\n入参:" + parameter + "\n待执行SQL:" + mappedStatement.getSql());
} @Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
} }
  • DefaultSqlSession 相对于上一章节,小傅哥这里把 MapperRegistry mapperRegistry 替换为 Configuration configuration,这样才能传递更丰富的信息内容,而不只是注册器操作。
  • 之后在 DefaultSqlSession#selectOne、DefaultSqlSession#getMapper 两个方法中都使用 configuration 来获取对应的信息。
  • 目前 selectOne 方法中只是把获取的信息进行打印,后续将引入 SQL 执行器进行结果查询并返回。

五、测试

1. 事先准备

提供 DAO 接口和对应的 Mapper xml 配置

public interface IUserDao {

    String queryUserInfoById(String uId);

}
<mapper namespace="cn.bugstack.mybatis.test.dao.IUserDao">

    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.bugstack.mybatis.test.po.User">
SELECT id, userId, userHead, createTime
FROM user
where id = #{id}
</select> </mapper>

2. 单元测试

@Test
public void test_SqlSessionFactory() throws IOException {
// 1. 从SqlSessionFactory中获取SqlSession
Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession(); // 2. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class); // 3. 测试验证
String res = userDao.queryUserInfoById("10001");
logger.info("测试结果:{}", res);
}
  • 目前的使用方式就和 Mybatis 非常像了,通过加载 xml 配置文件,交给 SqlSessionFactoryBuilder 进行构建解析,并获取 SqlSessionFactory 工厂。这样就可以顺利的开启 Session 以及完成后续的操作。

测试结果

07:07:40.519 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试结果:你被代理了!
方法:cn.bugstack.mybatis.test.dao.IUserDao.queryUserInfoById
入参:[Ljava.lang.Object;@23223dd8
待执行SQL:
SELECT id, userId, userHead, createTime
FROM user
where id = ? Process finished with exit code 0
  • 从测试结果我们可以看到,目前的代理操作已经可以把我们从 XML 中解析的 SQL 信息进行打印了,后续我们将结合这部分的处理继续完成数据库的操作。

六、总结

  • 了解 ORM 处理的核心流程,知晓目前我们所处在的步骤和要完成的内容,只有非常清楚的知道这个代理、封装、解析和返回结果的过程才能更好的完成整个框架的实现。
  • SqlSessionFactoryBuilder 的引入包装了整个执行过程,包括:XML 文件的解析、Configuration 配置类的处理,让 DefaultSqlSession 可以更加灵活的拿到对应的信息,获取 Mapper 和 SQL 语句。
  • 另外从整个工程搭建的过程中,可以看到有很多工厂模式、建造者模式、代理模式的使用,也有很多设计原则的运用,这些技巧都可以让整个工程变得易于维护和易于迭代。这也是研发人员在学习源码的过程中,非常值得重点关注的地方。

《手写Mybatis》第4章:Mapper XML的解析和注册使用的更多相关文章

  1. 带码农《手写Mybatis》进度3:实现映射器的注册和使用

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获!

  2. 要想精通Mybatis?从手写Mybatis框架开始吧!

    1.Mybatis组成 动态SQL Config配置 Mapper配置 2.核心源码分析 Configuration源码解析 SqlSessionFactory源码解析 SqlSession源码解析 ...

  3. 手写MyBatis ORM框架实践

    一.实现手写Mybatis三个难点 1.接口既然不能被实例化?那么我们是怎么实现能够调用的? 2.参数如何和sql绑定 3.返回结果 下面是Mybatis接口 二.Demo实现 1.创建Maven工程 ...

  4. 手写mybatis框架笔记

    MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...

  5. 手写MyBatis流程

    MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...

  6. 从零搭建Spring Boot脚手架(4):手写Mybatis通用Mapper

    1. 前言 今天继续搭建我们的kono Spring Boot脚手架,上一文把国内最流行的ORM框架Mybatis也集成了进去.但是很多时候我们希望有一些开箱即用的通用Mapper来简化我们的开发.我 ...

  7. 《手写Mybatis》第5章:数据源的解析、创建和使用

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 管你吃几碗粉,有流量就行! 现在我们每天所接收的信息量越来越多,但很多的个人却没有多 ...

  8. Mybatis热加载Mapper.xml

    开发的时候,写Mybatis Mapper.xml文件的时候,每次修改SQL都需要重启服务,感觉十分麻烦,于是尝试写了一个Mybatis的Mapper.xml热加载. 能在修改Mapper.xml之后 ...

  9. 手写mybatis框架-增加缓存&事务功能

    前言 在学习mybatis源码之余,自己完成了一个简单的ORM框架.已完成基本SQL的执行和对象关系映射.本周在此基础上,又加入了缓存和事务功能.所有代码都没有copy,如果也对此感兴趣,请赏个Sta ...

随机推荐

  1. Mysql数据库索引的使用

    1.索引的使用 查询 表的锁show index from qk_auth_employee 2.走索引 EXPLAIN SELECT * from qk_auth_employee where Da ...

  2. Android 12(S) 图形显示系统 - BufferQueue的工作流程(九)

    题外话 Covid-19疫情的强烈反弹,小区里检测出了无症状感染者.小区封闭管理,我也不得不居家办公了.既然这么大把的时间可以光明正大的宅家里,自然要好好利用,八个字 == 努力工作,好好学习 一.前 ...

  3. Mybatis使用注解开发(未完)

    使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心 注解在接口实现 @Select("SELECT * FROM user") Lis ...

  4. python发送邮件之宝藏库yagmail

    之前使用email模块+smtplib模块发送邮件,虽然可以实现功能,但过程比较繁琐,今天发现一个宝藏库(yagmail),可以说是炒鸡好用啦!!! 贴上实现代码,大家细品 yagmail安装 pip ...

  5. C# WinForm 解决子窗体放大后,子窗体图标放大的问题

    解决子窗体放大后,子窗体的图标占用主窗体的菜单栏的问题. C#子窗体最大化时, 那个图标跑到主窗体的前面去了, 造成界面不统一也不美观. 所以需要进行处理, 只要有主窗体的菜单ItemAdded事件中 ...

  6. BUAA_DS_聊聊链表

    幸福穿着节日的盛装欢迎你. --威廉•莎士比亚<罗密欧与朱丽叶> 1. 说在前面 大家在学数组的时候小脑瓜里有没有这样的疑惑:为什么数组必须是定长的?为什么数组开太长会编译错误?数组越界为 ...

  7. JVM知识(一) 求你了,别再说Java对象都是在堆内存上分配空间的了!

    求你了,别再说Java对象都是在堆内存上分配空间的了! https://baijiahao.baidu.com/s?id=1661296872935371634&wfr=spider& ...

  8. IDEA terminal无法从vim的编辑模式转换为命令模式

    Git 修改最后一次的commit历史记录:https://www.baidu.com/link?url=2WF8yFd0iBuVmXLWfutmSoXa12K9D143e_B0A3PTYYHEP9r ...

  9. JSP有哪些动作?分别是什么?

    外JSP共有以下7种基本动作 jsp:include:在页面被请求的时候引入一个文件. jsp:useBean:寻找或者实例化一个JavaBean. jsp:setProperty:设置JavaBea ...

  10. SpringMvc用什么对象从后台向前台传递数据的?

    通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到.