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

devtools/2024/10/18 19:18:14/
xmlns="http://www.w3.org/2000/svg" style="display: none;">

前言


1:在流程上,通过 DefaultSqlSession#selectOne 方法调用执行器,并通过预处理语句处理器 PreparedStatementHandler 执行参数设置和结果查询。
2:那么这个流程中我们所处理的参数信息,也就是每个 SQL 执行时,那些?号 需要被替换的地方,目前是通过硬编码的方式进行处理的。而这就是本章节需要解决的问题,如果只是硬编码完成参数设置,那么对于所有哪些不同类型的参数就没法进行操作了。

设计


参数的处理也就是通常我们使用 JDBC 直接操作数据库时,所使用 ps.setXxx(i, parameter); 设置的各类参数。那么在自动化解析 XML 中 SQL 拆分出所有的参数类型后,则应该根据不同的参数进行不同的类型设置,也就;Long 调用 ps.setLong、String 调用 ps.setString 所以这里需要使用策略模式,在解析 SQL 时按照不同的执行策略,封装进去类型处理器(也就是是实现 TypeHandler 接口的过程)
在这里插入图片描述
MapperMethod

public class MapperMethod {public Object execute(SqlSession sqlSession, Object[] args) {Object result = null;switch (command.getType()) {case SELECT:Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);break;default:throw new RuntimeException("Unknown execution method for: " + command.getName());}return result;}/*** 方法签名*/public static class MethodSignature {public Object convertArgsToSqlCommandParam(Object[] args) {final int paramCount = params.size();if (args == null || paramCount == 0) {// 如果没参数return null;} else if (paramCount == 1) {return args[params.keySet().iterator().next().intValue()];} else {// 否则,返回一个ParamMap,修改参数名,参数名就是其位置final Map<String, Object> param = new ParamMap<Object>();int i = 0;for (Map.Entry<Integer, String> entry : params.entrySet()) {// 1.先加一个#{0},#{1},#{2}...参数param.put(entry.getValue(), args[entry.getKey().intValue()]);// ...}return param;}}}
}

在映射器方法中 MapperMethod#execute 将原来的直接将参数 args 传递给 SqlSession#selectOne 方法,调整为转换后再传递对象。
参数策略处理器
在 Mybatis 的源码包中,有一个 type 包,这个包下所提供的就是一套参数的处理策略集合。它通过定义类型处理器接口、由抽象模板实现并定义标准流程,定提取抽象方法交给子类实现,这些子类就是各个类型处理器的具体实现。

public interface TypeHandler<T> {/*** 设置参数*/void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;}

模板模式

public abstract class BaseTypeHandler<T> implements TypeHandler<T> {@Overridepublic void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {// 定义抽象方法,由子类实现不同类型的属性设置setNonNullParameter(ps, i, parameter, jdbcType);}protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;}

子类实现

/*** @description Long类型处理器*/
public class LongTypeHandler extends BaseTypeHandler<Long> {@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {ps.setLong(i, parameter);}}/*** @description String类型处理器*/
public class StringTypeHandler extends BaseTypeHandler<String>{@Overrideprotected void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, parameter);}}

这里的接口实现举了个例子,分别是;LongTypeHandler、StringTypeHandler,在 Mybatis 源码中还有很多其他类型,这里我们暂时不需要实现那么多,只要清楚这个处理过程和编码方式即可。
类型注册机

public final class TypeHandlerRegistry {private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class);private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<>();private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<>();public TypeHandlerRegistry() {register(Long.class, new LongTypeHandler());register(long.class, new LongTypeHandler());register(String.class, new StringTypeHandler());register(String.class, JdbcType.CHAR, new StringTypeHandler());register(String.class, JdbcType.VARCHAR, new StringTypeHandler());}//...
}    

里在构造函数中,新增加了 LongTypeHandler、StringTypeHandler 两种类型的注册器。
参数构建
相对于前面章节所完成的内容,这个章节需要对 SqlSourceBuilder 源码构建器中,创建参数映射 ParameterMapping 需要添加参数处理器的内容。

// 构建参数映射
private ParameterMapping buildParameterMapping(String content) {// 先解析参数映射,就是转化成一个 HashMap | #{favouriteSection,jdbcType=VARCHAR}Map<String, String> propertiesMap = new ParameterExpression(content);String property = propertiesMap.get("property");Class<?> propertyType;if (typeHandlerRegistry.hasTypeHandler(parameterType)) {propertyType = parameterType;} else if (property != null) {MetaClass metaClass = MetaClass.forClass(parameterType);if (metaClass.hasGetter(property)) {propertyType = metaClass.getGetterType(property);} else {propertyType = Object.class;}} else {propertyType = Object.class;}logger.info("构建参数映射 property:{} propertyType:{}", property, propertyType);ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);return builder.build();
}

参数使用
参数构建完成后,就可以在 DefaultSqlSession#selectOne 调用时设置参数使用了。那么这里的链路关系;Executor#query - > SimpleExecutor#doQuery -> StatementHandler#parameterize -> PreparedStatementHandler#parameterize -> ParameterHandler#setParameters 到了 ParameterHandler#setParameters 就可以看到了根据参数的不同处理器循环设置参数。

public class DefaultParameterHandler implements ParameterHandler {@Overridepublic void setParameters(PreparedStatement ps) throws SQLException {List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (null != parameterMappings) {for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);String propertyName = parameterMapping.getProperty();Object value;if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {// 通过 MetaObject.getValue 反射取得值设进去MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}JdbcType jdbcType = parameterMapping.getJdbcType();// 设置参数logger.info("根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:{}", JSON.toJSONString(value));TypeHandler typeHandler = parameterMapping.getTypeHandler();typeHandler.setParameter(ps, i + 1, value, jdbcType);}}}}

测试
配置数据源

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><select id="queryUserInfo" parameterType="cn.bugstack.mybatis.test.po.User" resultType="cn.bugstack.mybatis.test.po.User">SELECT id, userId, userName, userHeadFROM userwhere id = #{id} and userId = #{userId}
</select>

单元测试

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

对象类型参数

@Test
public void test_queryUserInfo() {// 1. 获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2. 测试验证:对象参数User user = userDao.queryUserInfo(new User(1L, "10001"));logger.info("测试结果:{}", JSON.toJSONString(user));
}

总结
们算是把一个 ORM 框架的基本流程串联起来了,不要硬编码也能完成简单 SQL 的处理。读者伙伴可以仔细阅读下当前框架中,所包含的分包结构。比如:构建、绑定、映射、反射、执行、类型、事务、数据源等等,尝试画一画他们的链接关系,这样会让你更加清晰现在的代码解耦结构。

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


http://www.ppmy.cn/devtools/125092.html

相关文章

2024系统架构师---试题四论网络安全体系架构设计及应用

试题四论网络安全体系架构设计及应用 建立信息系统安全体系的目的&#xff0c;就是将普遍性安全原理与信息系统的实际相结合&#xff0c;形成满足信息系统安全需求的安全体系结构&#xff0c;网络安全体系是信息系统体系的核心。OSI(Open System Interconnection)是由国际化标准…

Docker 环境下多节点服务器监控实战:从 Prometheus 到 Grafana 的完整部署指南

Docker 环境下多节点服务器监控实战&#xff1a;从 Prometheus 到 Grafana 的完整部署指南 文章目录 Docker 环境下多节点服务器监控实战&#xff1a;从 Prometheus 到 Grafana 的完整部署指南一 多节点部署1 节点一2 节点二3 节点三 二 监控节点部署三 配置 prometheus.yml四 …

大数据毕业设计选题推荐-招聘信息数据分析系统-Python数据可视化-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

数据处理方式,线程与进程,多任务,Spark与MR的区别

目录 数据处理的方式有哪些 单机数据处理 集群数据处理 分布式计算框架 MapReduce ApplicationMaster Spark分布式计算类别 进程与线程的区别 进程是计算时分配资源的最小单位 线程是执行计算任务的最小任务 多进程的执行效率没有多线程的执行效率高 多任务 Spark和M…

Facebook直播障碍的多重原因及解决方案

在社交媒体迅猛发展的今天&#xff0c;直播已经成为用户与观众互动、分享内容的重要方式。然而&#xff0c;作为全球最大的社交平台之一&#xff0c;Facebook在直播过程中常常遇到各种障碍&#xff0c;使得用户无法顺利进行直播。本文将深入探讨Facebook无法直播的原因&#xf…

OceanBase + DolphinScheduler,搭建分布式大数据调度平台的实践

本文整理自白鲸开源联合创始人&#xff0c;Apache DolphinScheduler PMC Chair&#xff0c;Apache Foundation Member 代立冬的演讲。主要介绍了DolphinScheduler及其架构、DolphinScheduler与OceanBase 的联合大数据方案。 DolphinScheduler是什么&#xff1f; Apache Dolphi…

YOLO11 实例分割模型做行人分割

实例分割是检测和隔离图像中单个对象的关键技术,YOLO11 是执行这项任务的最佳模型之一。在本文中,你将学习如何使用 YOLO11 分割模型有效地在图像中分割人物。我们将涵盖从设置 Python 环境和安装必要的库,到下载测试图像和可视化分割结果的所有内容。通过本教程的学习,你将…

dvwa:xss全系列全难度解析

引号内注入&#xff1a; <select name"default"><option value"English">English</option>^^^^^^^<option value"" disabled"disabled">----</option><option valueEnglish>English</option&g…