

作者:小傅哥 博客:https://bugstack.cn
❝沉淀、分享、成长,让自己和他人都能有所收获!😜❞
你是怎么面对功能迭代的?
其实很多程序员在刚开始做编程或者新加入一家公司时,都没有多少机会可以做一个新项目,大部分时候都是在老项目上不断的迭代更新。在这个过程你可能要学习N个前人留下的各式各样的风格迥异的代码片段,在这些纵横交错的流程中,找到一席之地,把自己的ifelse加进去。
虽然这样胡乱的加ifelse,刚上手就“摆烂”的心态,让人很难受。但要想在已经被压缩的工期下,还能交付出高质量的代码其实也很难完成,所以一部分研发被逼到能用就行,能跑就可以。
但说回来,其实不能逐步清理一片屎山,让代码在你的手上逐步清晰、整洁、干净,很多时候也是作为码农自身经验的不足,不懂得系统重构、不了解设计原则、不熟悉业务背景、不清楚产品走向等等原因造成的。所以最好的办法是提升自身的能力,每接到一次需求都有一些技术上的改变,既然它是屎山,那就当做打怪升级了,修一点、改一块、补一片,总会在你手上越来越易于维护和扩展的。
在我们渐进式的逐步实现 Mybatis 框架过程中,首先我们要有一个目标导向的思路,也就是说 Mybatis 的核心逻辑怎么实现。
其实我们可以把这样一个 ORM 框架的目标,简单的描述成是为了给一个接口提供代理类,类中包括了对 Mapper 也就是 xml 文件中的 SQL 信息(类型、入参、出参、条件)进行解析和处理,这个处理过程就是对数据库的操作以及返回对应的结果给到接口。如图 4-1

图 4-1 ORM 框架核心流程
那么按照 ORM 核心流程的执行过程,我们本章节就需要在上一章节的基础上,继续扩展对 Mapper 文件的解析以及提取出对应的 SQL 文件。并在当前这个阶段,可以满足我们调用 DAO 接口方法的时候,可以返回 Mapper 中对应的待执行 SQL 语句。为了不至于把整个工程撑大,小傅哥会带着大家逐步完成这些内容,所以本章节暂时不会对数据库进行操作,待后续逐步实现
结合上一章节我们使用了 MapperRegistry 对包路径进行扫描注册映射器,并在 DefaultSqlSession 中进行使用。那么在我们可以把这些命名空间、SQL描述、映射信息统一维护到每一个 DAO 对应的 Mapper XML 的文件以后,其实 XML 就是我们的源头了。通过对 XML 文件的解析和处理就可以完成 Mapper 映射器的注册和 SQL 管理。这样也就更加我们操作和使用了。如图 4-2

图 4-2 XML 文件解析注册处理
SqlSessionFactoryBuilder 工厂建造者模式类,通过入口 IO 的方式对 XML 文件进行解析。当前我们主要以解析 SQL 部分为主,并注册映射器,串联出整个核心流程的脉络。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
工程源码:https://t.zsxq.com/bmqNFQ7
XML 解析和注册类实现关系,如图 4-2

图 4-2 XML 解析和注册类实现关系
源码详见: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);
}
}
源码详见: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));
}
}
}
**源码详见(配置项)**: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);
}
}
在配置类中添加映射器注册机和映射语句的存放;
源码详见: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);
}
}
MapperRegistry mapperRegistry 替换为 Configuration configuration,这样才能传递更丰富的信息内容,而不只是注册器操作。提供 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>
@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);
}
测试结果
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
- END -
你好,我是小傅哥。一线互联网java 工程师、架构师,开发过交易&营销、写过运营&活动、设计过中间件也倒腾过中继器、IO板卡。不只是写Java语言,也搞过C#、PHP,是一个技术活跃的折腾者。
2022年在知识星球【码农会锁】开发完成基于 DDD 四层架构设计的,《分布式实战项目抽奖系统》。此项目以互联网开发常用技术为主,包括:SpringBoot、Mybatis、Dubbo、MQ、Redis、分库分表、ELK、Docker等,以及大量的真实场景案例和对应的设计模式实战,解决每一个细节问题,非常适合学习实践。