手写mybatis之细化XML语句构建器,完善静态SQL解析

ops/2024/10/18 10:17:37/
xmlns="http://www.w3.org/2000/svg" style="display: none;">

前言


实现到本章节前,关于 Mybatis ORM 框架的大部分核心结构已经逐步体现出来了,包括;解析、绑定、映射、事务、执行、数据源等。但随着更多功能的逐步完善,我们需要对模块内的实现进行细化处理,而不单单只是完成功能逻辑。这就有点像把 CRUD 使用设计原则进行拆分解耦,满足代码的易维护和可扩展性。而这里我们首先着手要处理的就是关于 XML 解析的问题,把之前粗糙的实现进行细化,满足我们对解析时一些参数的整合和处理。
在这里插入图片描述
对于 XML 信息的读取,各个功能模块的流程上应该符合单一职责,而每一个具体的实现又得具备迪米特法则,这样实现出来的功能才能具有良好的扩展性。通常这类代码也会看着很干净 那么基于这样的诉求,我们则需要给解析过程中,所属解析的不同内容,按照各自的职责类进行拆解和串联调用。
在这里插入图片描述
解耦映射解析
提供单独的 XML 映射构建器 XMLMapperBuilder 类,把关于 Mapper 内的 SQL 进行解析处理。提供了这个类以后,就可以把这个类的操作放到 XML 配置构建器,XMLConfigBuilder#mapperElement 中进行使用了。

public class XMLMapperBuilder extends BaseBuilder {/*** 解析*/public void parse() throws Exception {// 如果当前资源没有加载过再加载,防止重复加载if (!configuration.isResourceLoaded(resource)) {configurationElement(element);// 标记一下,已经加载过了configuration.addLoadedResource(resource);// 绑定映射器到namespaceconfiguration.addMapper(Resources.classForName(currentNamespace));}}// 配置mapper元素// <mapper namespace="org.mybatis.example.BlogMapper">//   <select id="selectBlog" parameterType="int" resultType="Blog">//    select * from Blog where id = #{id}//   </select>// </mapper>private void configurationElement(Element element) {// 1.配置namespacecurrentNamespace = element.attributeValue("namespace");if (currentNamespace.equals("")) {throw new RuntimeException("Mapper's namespace cannot be empty");}// 2.配置select|insert|update|deletebuildStatementFromContext(element.elements("select"));}// 配置select|insert|update|deleteprivate void buildStatementFromContext(List<Element> list) {for (Element element : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, element, currentNamespace);statementParser.parseStatementNode();}}}

在 XMLMapperBuilder#parse 的解析中,主要体现在资源解析判断、Mapper解析和绑定映射器到;

1:configuration.isResourceLoaded 资源判断避免重复解析,做了个记录。
2:configuration.addMapper 绑定映射器主要是把 namespace 3:cn.bugstack.mybatis.test.dao.IUserDao 绑定到 Mapper 上。也就是注册到映射器注册机里。
4:configurationElement 方法调用的 buildStatementFromContext,重在处理 XML 语句构建器,下文中单独讲解。

public class XMLConfigBuilder extends BaseBuilder {/** <mappers>*	 <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>*	 <mapper resource="org/mybatis/builder/BlogMapper.xml"/>*	 <mapper resource="org/mybatis/builder/PostMapper.xml"/>* </mappers>*/private void mapperElement(Element mappers) throws Exception {List<Element> mapperList = mappers.elements("mapper");for (Element e : mapperList) {String resource = e.attributeValue("resource");InputStream inputStream = Resources.getResourceAsStream(resource);// 在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource);mapperParser.parse();}}}

语句构建器
XMLStatementBuilder 语句构建器主要解析 XML 中 select|insert|update|delete 中的语句,当前我们先以 select 解析为案例。

public class XMLStatementBuilder extends BaseBuilder {//解析语句(select|insert|update|delete)//<select//  id="selectPerson"//  parameterType="int"//  parameterMap="deprecated"//  resultType="hashmap"//  resultMap="personResultMap"//  flushCache="false"//  useCache="true"//  timeout="10000"//  fetchSize="256"//  statementType="PREPARED"//  resultSetType="FORWARD_ONLY">//  SELECT * FROM PERSON WHERE ID = #{id}//</select>public void parseStatementNode() {String id = element.attributeValue("id");// 参数类型String parameterType = element.attributeValue("parameterType");Class<?> parameterTypeClass = resolveAlias(parameterType);// 结果类型String resultType = element.attributeValue("resultType");Class<?> resultTypeClass = resolveAlias(resultType);// 获取命令类型(select|insert|update|delete)String nodeName = element.getName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));// 获取默认语言驱动器Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();LanguageDriver langDriver = configuration.getLanguageRegistry().getDriver(langClass);SqlSource sqlSource = langDriver.createSqlSource(configuration, element, parameterTypeClass);MappedStatement mappedStatement = new MappedStatement.Builder(configuration, currentNamespace + "." + id, sqlCommandType, sqlSource, resultTypeClass).build();// 添加解析 SQLconfiguration.addMappedStatement(mappedStatement);}}

脚本语言驱动
在 XMLStatementBuilder#parseStatementNode 语句构建器的解析中,可以看到这么一块,获取默认语言驱动器并解析SQL的操作。其实这部分就是 XML 脚步语言驱动器所实现的功能,在 XMLScriptBuilder 中处理静态SQL和动态SQL。
定义接口

public interface LanguageDriver {SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType);}

XML语言驱动器实现

public class XMLLanguageDriver implements LanguageDriver {@Overridepublic SqlSource createSqlSource(Configuration configuration, Element script, Class<?> parameterType) {XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();}}

XML脚本构建器解析

public class XMLScriptBuilder extends BaseBuilder {public SqlSource parseScriptNode() {List<SqlNode> contents = parseDynamicTags(element);MixedSqlNode rootSqlNode = new MixedSqlNode(contents);return new RawSqlSource(configuration, rootSqlNode, parameterType);}List<SqlNode> parseDynamicTags(Element element) {List<SqlNode> contents = new ArrayList<>();// element.getText 拿到 SQLString data = element.getText();contents.add(new StaticTextSqlNode(data));return contents;}}

SQL源码构建器

public class SqlSourceBuilder extends BaseBuilder {private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";public SqlSourceBuilder(Configuration configuration) {super(configuration);}public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);String sql = parser.parse(originalSql);// 返回静态 SQLreturn new StaticSqlSource(configuration, sql, handler.getParameterMappings());}private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {@Overridepublic String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));return "?";}// 构建参数映射private ParameterMapping buildParameterMapping(String content) {// 先解析参数映射,就是转化成一个 HashMap | #{favouriteSection,jdbcType=VARCHAR}Map<String, String> propertiesMap = new ParameterExpression(content);String property = propertiesMap.get("property");Class<?> propertyType = parameterType;ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);return builder.build();}}}

DefaultSqlSession 调用调整
因为以上整个设计和实现,调整了解析过程,以及细化了 SQL 的创建。那么在 MappedStatement 映射语句中,则使用 SqlSource 替换了 BoundSql,所以在 DefaultSqlSession 中也会有相应的调整。

public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;@Overridepublic <T> T selectOne(String statement, Object parameter) {MappedStatement ms = configuration.getMappedStatement(statement);List<T> list = executor.query(ms, parameter, Executor.NO_RESULT_HANDLER, ms.getSqlSource().getBoundSql(parameter));return list.get(0);}}

测试
配置数据源

xml"><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment>
</environments>

1:通过 mybatis-config-datasource.xml 配置数据源信息,包括:driver、url、username、password
2:在这里 dataSource 可以按需配置成 DRUID、UNPOOLED 和 POOLED 进行测试验证。
配置Mapper

xml"><select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.bugstack.mybatis.test.po.User">SELECT id, userId, userName, userHeadFROM userwhere id = #{id}
</select>

单元测试

@Test
public void test() throws IOException {// 1. 从SqlSessionFactory中获取SqlSessionSqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));SqlSession sqlSession = sqlSessionFactory.openSession();// 2. 获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 3. 测试验证User user = userDao.queryUserInfoById(1L);logger.info("测试结果:{}", JSON.toJSONString(user));
}

总结
1:我们就像是去把原本 CRUD 的代码,通过设计原则进行拆分和解耦,运用不用的类来承担不同的职责,完整整个功能的实现。这包括;映射构建器、语句构建器、源码构建器的综合使用,以及对应的引用;脚本语言驱动和脚本构建器解析,处理我们的 XML 中的 SQL 语句。
2:通过这样的重构代码,也能让我们对平常的业务开发中的大片面向过程的流程代码有所感悟,当你可以细分拆解职责功能到不同的类中去以后,你的代码会更加的清晰并易于维护。

好了到这里就结束了手写mybatis之细化XML语句构建器,完善静态SQL解析的学习,大家一定要跟着动手操作起来。需要源码的 可si我获取;


http://www.ppmy.cn/ops/125626.html

相关文章

Midjourney中文版:创意无限,艺术之旅由此启程

Midjourney中文版——一个将你的文字想象转化为视觉艺术的神奇平台。无需繁琐的绘画技巧&#xff0c;只需简单的文字描述&#xff0c;你就能开启一场前所未有的艺术之旅。 Midjourney AI超强绘画 (原生态系统&#xff09;用户端&#xff1a;Ai Loadinghttps://www.mjdiscord.c…

2014年国赛高教杯数学建模A题嫦娥三号软着陆轨道设计与控制策略解题全过程文档及程序

2014年国赛高教杯数学建模 A题 嫦娥三号软着陆轨道设计与控制策略 嫦娥三号于2013年12月2日1时30分成功发射&#xff0c;12月6日抵达月球轨道。嫦娥三号在着陆准备轨道上的运行质量为2.4t&#xff0c;其安装在下部的主减速发动机能够产生1500N到7500N的可调节推力&#xff0c;…

feature fusion和feature aggregation的区别

在语义分割任务中&#xff0c;feature fusion 和 feature aggregation 是相关但不完全相同的概念&#xff0c;它们指的是不同的特征处理方式。 1. Feature Fusion&#xff08;特征融合&#xff09;&#xff1a; Feature fusion 通常指的是将来自不同来源或不同尺度的特征进行…

【Linux】————进程控制

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;Linux专栏 创作时间 &#xff1a;2024年10月10日 ​ ​ 一、程序地址空间&#xff1a; 1、C/C中的程序地址空间&#xff1a; ​ 在c中我们了解了这样的空间分布图。 我们应如何去创建和访问变量呢&#xff1f;…

【隐私计算】隐语HEU同态加密算法解读

HEU: 一个高性能的同态加密算法库&#xff0c;提供了多种 PHE 算法&#xff0c; 包括ZPaillier、FPaillier、IPCL、Damgard Jurik、DGK、OU、EC ElGamal 以及基于FPGA和GPU硬件加速版本的Paillier版本。 本文我们会基于GPU运行HEU Docker容器&#xff0c;编译打包GPaillier并测…

python程序操作pdf

python代码进行多个图片合并为pdf&#xff1a; #python代码进行多个图片合并为pdf&#xff1a; from PIL import Image from fpdf import FPDF import osdef images_to_pdf(image_paths, output_pdf, quality85):"""将多个图片合并为一个PDF文件&#xff0c;并…

Springboot 阿里云OSS对象存储

Springboot 阿里云OSS对象存储 OSSController package com.wzb.OSSController20241009;import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import com.aliyun.oss.model.PutO…

【docker】要将容器中的 livox_to_pointcloud2 文件夹复制到宿主机上

复制文件夹 使用 docker cp 命令从容器复制文件夹到宿主机&#xff1a; docker cp <container_id_or_name>:/ws_livox/src/livox_to_pointcloud2 /path/to/host/folder sudo docker cp dandong_orin_docker:/ws_livox/src/livox_to_pointcloud2 /home