写点什么

如何为 Spring 和 Mybatis 增加可逆计算支持

作者:canonical
  • 2023-07-31
    北京
  • 本文字数:3017 字

    阅读完需:约 10 分钟

Mybatis 所管理的 SQL 语句存放在 XML 配置文件中,号称是可以在不修改源码的情况下通过配置调整来定制数据库访问逻辑,比如适配不同的数据库方言等。但在实际使用中,如果 XML 文件已经被打包到 Jar 包中,那么即使是进行单个 SQL 语句的定制也必须要复制整个配置文件,这明显是设计上的一种缺陷。可逆计算理论为所有的 DSL 语言提供了统一的差量化定制语法。借助于 Nop 平台的基础设施,我们只需要补充少量代码,拦截 Mybatis 的配置文件加载过程,就可以为 Mybatis 框架引入可逆计算支持,实现细粒度的差量化定制。如法炮制,同样的方法还可以被应用于 Spring 框架的改造。

一. Mybatis 的 Delta 定制

Mybatis 内置了一个简易的分解、聚合机制:多个 XML 文件可以具有同样的 namespace,从而聚合为一个统一的 Mapper 接口。


Mybatis 中 namespace 配置对应于 Mapper 接口的 Java 类名


在模型驱动的开发模式下,我们一般会根据模型自动生成一组增删改查的 SQL 语句,把它们存放在单独的 Mapper 文件中,通过一个标准的 BaseMapper 接口来映射这些 SQL 语句。然后再生成一个 BaseMapper 的派生接口用于映射手工编写的 SQL 语句。例如:


  1. 代码生成 _SysUser.mapper.xml文件

  2. 手工编写 SysUser.mapper.xml,它和自动生成的 Mapper 具有同样的 namespace。

  3. 增加 Java 接口SysUserMapper,它从 BaseMapper 接口继承,从而避免重复定义标准的增删改查函数。


如果我们希望实现增量式的模型驱动开发,那么每次代码生成时都需要直接覆盖_SysUser.mapper.xml 文件,这样可以保证代码和模型始终保持一致。如果我们觉得自动生成的 SQL 语句不满足要求怎么办?一种做法是修改代码生成器,但这样会影响到所有使用此代码生成器的模块。另一种做法是指定 SysUser.mapper.xml 从_SysUer.mapper.xml 继承,然后在 SysUser.mapper.xml 中实现同名的 SQL 语句,希望能够像对象继承机制一样覆盖自动生成的 SQL 语句。但是很可惜,Mybatis 不支持文件继承,多个 XML 文件中包含同名的 SQL 语句会报错

Mapper 文件扫描及注册

基于 Nop 平台可以对 Mybatis 进行如下改造:


  1. 增加mapper.xdef元模型定义,定义两个节点如何进行差量合并

  2. 增加 NopMybatisSessionFactoryCustomizer,在其中利用 Nop 平台的 DSL 文件加载器去加载 mapper 文件


@Service@ConditionalOnProperty(name = "nop.spring.delta.mybatis.enabled", matchIfMissing = true)public class NopMybatisSessionFactoryCustomizer implements SqlSessionFactoryBeanCustomizer {    @Override    public void customize(SqlSessionFactoryBean factoryBean) {
List<IResource> resources = ModuleManager.instance().findModuleResources("/mapper", ".mapper.xml");
if (!resources.isEmpty()) { List<Resource> locations = new ArrayList<>(resources.size()); for (IResource resource : resources) { // 忽略自动生成的mapper文件,它们只能作为基类存在 if (resource.getName().startsWith("_")) continue;
XDslExtendResult result = DslNodeLoader.INSTANCE.loadFromResource(resource); XNode node = result.getNode(); node.removeAttr("xmlns:x");
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<!DOCTYPE mapper\n" + " PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\"\n" + " \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n" + node.xml(); locations.add(new ByteArrayResource(xml.getBytes(StandardCharsets.UTF_8), resource.getPath())); } factoryBean.addMapperLocations(locations.toArray(new Resource[0])); } }}
复制代码


  • 利用 ModuleManager.findModuleResources 扫描所有模块的 mapper 目录,并收集所有后缀名为 mapper.xml 的资源文件

  • DslNodeLoader.loadFromResource 会解析 XML 文件,执行 Delta 差量合并算法,返回合成后的 XNode 节点。

  • 将 XNode 的内容序列化为字节数据后包装为 Spring 内置的 Resource 接口,然后注册到 Mybatis 的 SqlSessionFactoryBean 中


DslNodeLoader 加载 DSL 文件时会自动识别 delta 目录,如果发现/_vfs/_delta/default/目录下存在同名的文件,则会优先加载 delta 目录下的文件。例如 SysUser.mapper.xml


<mapper x:extends="super" x:schema="/nop/spring/schema/mapper.xdef" xmlns:x="/nop/schema/xdsl.xdef">
<!-- 定制使用nop_auth_user表 --> <sql id="selectUserVo"> select u.user_id, u.dept_id, u.user_name, u.nick_name from nop_auth_user u </sql></mapper>
复制代码


在 delta 目录下的定制文件中,我们可以通过x:extends="super"来表示继承原路径下的 DSL 文件。

二. Spring 的 Delta 定制

基于 Nop 平台可以对 Mybatis 进行如下改造:


  1. 增加beans.xdef元模型定义,定义两个节点如何进行差量合并

  2. 增加 NopBeansRegistrar,在其中利用 Nop 平台的 DSL 文件加载器去加载 mapper 文件


@Import(NopBeansAutoConfiguration.NopBeansRegistrar.class)@Configurationpublic class NopBeansAutoConfiguration {
public static class NopBeansRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { List<IResource> resources = ModuleManager.instance().findModuleResources("/beans", "beans.xml"); if (resources.isEmpty()) return;
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); for (IResource resource : resources) { if (!resource.getName().startsWith("spring-")) continue;
XDslExtendResult result = DslNodeLoader.INSTANCE.loadFromResource(resource); XNode node = result.getNode(); node.removeAttr("xmlns:x");
Resource springResource = toResource(node); reader.loadBeanDefinitions(springResource); } } }}

复制代码


具体处理过程与 NopMybatisSessionFactoryCustomizer 类似。


利用 beans.xml 文件的差量定制能力,我们还可以实现对 Mybatis 的 Mapper 接口的扩展。


  1. spring-demo.beans.xml文件中定义 sysUserMapper。NopBeansRegistrar 会自动扫描所有模块的 beans 目录下前缀为 spring 的 beans 配置文件。(采用这种注册方式就不要再使用 MapperScan 注解)。

  2. 从 SysUserMapper 接口继承,实现一个扩展接口 SysUserMapperEx 接口,在其中可以增加扩展的 SQL 调用方法。

  3. 在 delta 目录下可以定制spring-demo.beans.xml文件,设置 mapperTypeEx 属性为扩展接口类型。


    <bean id="sysUserMapper" parent="nopBaseMapper">        <property name="mapperInterface" value="io.nop.demo.spring.SysUserMapper"/>    </bean>
复制代码


通过这种方法,我们通过增加单独的 delta 模块实现对原有 mapper 文件和 beans 文件的定制,并实现 Mapper 接口的扩展。


发布于: 刚刚阅读数: 3
用户头像

canonical

关注

还未添加个人签名 2022-08-30 加入

还未添加个人简介

评论

发布
暂无评论
如何为Spring和Mybatis增加可逆计算支持_Spring Boot_canonical_InfoQ写作社区