MyBatis 添加元数据自定义元素标签的实现代码

网友投稿 1213 2023-05-09

MyBatis 添加元数据自定义元素标签的实现代码

MyBatis 添加元数据自定义元素标签的实现代码

开发背景

现有系统中维护了一套业务表相关列、键的元数据,希望通过读取元数据实现自动封装 SQL 语句、自定义主键策略。实现方案为入侵式修改 MyBatis,增加元素标签meta,支持业务开发中可以在XML映射文件中使用。

meta元素设计如下:

<!ELEMENT meta EMPTY>

<!ATTLIST meta

test CDATA #IMPLIED

type (update|insert|select|columns|pk-col|load|load-columns) #IMPLIED

ignore CDATA #IMPLIED

table CDATA #IMPLIED

func CDATA #IMPLIED

alias CDATA #IMPLIED

>

期望示例如下:

select

from USER

where = #{__PK_VALUE}

开发准备

新建项目并引入 mybatis 、 mybatis-spring 两个核心依赖。

org.mybatis

mybatis

org.mybatis

mybatis-spring

增加自定义元素

创建 MetaHandler 和 MetaSqlNode

public class MetaHandler implements NodeHandler {

private final CustomConfiguration configuration;

protected MetaHandler(CustomConfiguration configuration) {

this.configuration = configuration;

}

@Override

public void handleNode(XNode nodeToHandle, List targetContents) {

final String test = nodeToHandle.getStringAttribute("test");

final String type = nodeToHandle.getStringAttribute("type");

final String ignore = nodeToHandle.getStringAttribute("ignore");

final String table = nodeToHandle.getStringAttribute("table"http://);

final String func = nodeToHandle.getStringAttribute("func");

String alias = nodeToHandle.getStringAttribute("alias");

if (!StringUtils.isEmpty(alias)) {

alias = alias.trim();

//是否无效 防止注入

boolean invalid = alias.contains(" ") || alias.contains(".");

if (invalid) {

throw new RuntimeException("alias is invalid : " + alias);

}

}

MetaSqlNode metaSqlNode = new MetaSqlNode(configuration, test, type, ignore, table, func, alias);

targetContents.add(metaSqlNode);

}

}

public class MetaSqlNode implements SqlNode {

/**

* mybatis核心数据

*/

private final CustomConfiguration configuration;

/**

* 判断语句校验器

*/

private final ExpressionEvaluator evaluator;

/**

* 判断语句,同if标签

*/

private final String test;

/**

* 生成语句类型 update|insert|select|columns|pk-col|load|load-columns

*/

private final TypeEnum type;

/**

* 忽略的列

*/

private final String ignore;

/**

* 表名,未指定则从调用参数中获取

*/

private final String table;

/**

* 功能,未指定则从调用参数中获取

*/

private final String func;

/**

* 动态列别名

*/

private final String alias;

public MetaSqlNode(CustomConfiguration configuration, String test, String type, String ignore, String table, String func, String alias) {

this.evaluator = new ExpressionEvaluator();

this.configuration = configuration;

this.test = test;

this.type = TypeEnum.parse(type);

this.ignore = ignore;

this.table = table;

this.func = func;

this.alias = alias;

}

@Override

public boolean apply(DynamicContext context) {

// TODO 解析type与table,向context中添加语句

context.appendSql(" insert ");

}

}

创建 CustomXMLScriptBuilder

内容复制自org.apache.ibatis.scripting.xmltags.XMLScriptBuilder,在 initNodeHandlerMap 方法中添加 MetaHandler。

private void initNodeHandlerMap() {

nodeHandlerMap.put("trim", new TrimHandler());

nodeHandlerMap.put("where", new WhereHandler());

nodeHandlerMap.put("set", new SetHandler());

nodeHandlerMap.put("foreach", new ForEachHandler());

nodeHandlerMap.put("if", new IfHandler());

nodeHandlerMap.put("choose", new ChooseHandler());

nodeHandlerMap.put("when", new IfHandler());

nodeHandlerMap.put("otherwise", new OtherwiseHandler());

nodeHandlerMap.put("bind", new BindHandler());

//增加元数据标签解析器

if (configuration instanceof CustomConfiguration) {

nodeHandlerMap.put("meta", new MetaHandler((CustomConfiguration) configuration));

}

}

创建 CustomXMLLanguageDriver

内容复制自org.apache.ibatis.scripting.xmltags.XMLLanguageDriver,在 createSqlSource 方法中使用 CustomXMLScriptBuilder 来解析Xml生成 SqlSource。

@Override

public SqlSource createSqlSource(Configuration configuration, XNode script, Class> parameterType) {

CustomXMLScriptBuilder builder = new CustomXMLScriptBuilder(configuration, script, parameterType);

return builder.parseScriptNode();

}

创建 CustomConfiguration

继承org.apache.ibatis.session.Configuration,内容复制自 Configuration。将构造方法中的 XMLLanguageDriver 修改为 CustomConfiguration。

public CustomConfiguration() {

//默认使用自定义 LanguageDriver

typeAliasRegistry.registerAlias("XML", CustomXMLLanguageDriver.class);

//默认使用自定义 LanguageDriver

languageRegistry.setDefaultDriverClass(CustomXMLLanguageDriver.class);

}

创建 CustomXMLConfigBuilder

内容复制自org.apache.ibatis.builder.xml.XMLConfigBuilder,支持通过 XML 配置来创建 CustomConfiguration。

public class CustomXMLConfigBuilder extends BaseBuilder {

private CustomXMLConfigBuilder(XPathParser parser, String environment, Properties props) {

// 使用 CustomConfiguration

super(new CustomConfiguration());

ErrorContext.instance().resource("SQL Mapper Configuration");

this.configuration.setVariables(props);

this.parsed = false;

this.environment = environment;

this.parser = parser;

}

}

创建 SqlSessionFactory

复制自org.mybatis.spring.SqlSessionFactoryBean,将 buildSqlSessionFactory 方法中的 Configuration 替换为 CustomConfiguration。

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

final Configuration targetConfiguration;

CustomXMLConfigBuilder xmlConfigBuilder = null;

if (this.configuration != null) {

targetConfiguration = this.configuration;

if (targetConfiguration.getVariables() == null) {

targetConfiguration.setVariables(this.configurationProperties);

} else if (this.configurationProperties != null) {

targetConfiguration.getVariables().putAll(this.configurationProperties);

}

} else http://if (this.configLocation != null) {

// 使用 CustomXMLConfigBuilder 创建 CustomConfiguration

xmlConfigBuilder = new CustomXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);

targetConfiguration = xmlConfigBuilder.getConfiguration();

} else {

LOGGER.debug(

() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");

// 使用 CustomConfiguration

targetConfiguration = new CustomConfiguration();

Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);

}

return this.sqlSessionFactoryBuilder.build(targetConfiguration);

}

修改DTD约束

MyBatis 的约束文件并不支持自定义的 meta 元素,需要使用 CDATA 来处理。示例如下:

<![CDATA[[

]]>

如果不想要 CDATA,则需要通过修改DTD约束来完成。

在 classes 下指定位置添加DTD约束文件org/apache/ibatis/builder/xml/mybatis-3-config.dtd达到覆盖 MyBatis DTD的效果。

重写代码来使用指定 DTD 。

创建 CustomXMLMapperEntityResolver

复制自org.apache.ibatis.builder.xml.XMLMapperEntityResolver,将MYBATIS_MAPPER_DTD修改为指向本地 mybatis-3-mapper.dtd 文件,并在DTD文件中添加 meta 元素的约束。

public class CustomXMLMapperEntityResolver implements EntityResolver {

private static final String MYBATIS_MAPPER_DTD = "com/my/ibatis/builder/xml/mybatis-3-mapper.dtd";

}

<!ELEMENT meta EMPTY>

<!ATTLIST meta

test CDATA #IMPLIED

type (update|insert|select|columns|pk-col|load|load-columns) #IMPLIED

ignore CDATA #IMPLIED

table CDATA #IMPLIED

func CDATA #IMPLIED

alias CDATA #IMPLIED

>

CustomXMLLanguageDriver

Mapper动态语句注解处理使用 CustomXMLMapperEntityResolver。

/**

* Mapper动态语句注解调用

*

* ""

*

* @param configuration mybatis配置

* @param script 动态语句字符串

* @param parameterType 参数类型

* @return org.apache.ibatis.mapping.SqlSource

*/

@Override

public SqlSource createSqlSource(Configuration configuration, String script, Class> parameterType) {

// issue #3

if (script.startsWith("

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:IDEA中解决 git pull 冲突的方法
下一篇:HashMap源码中的位运算符&详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~