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

开发背景

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

meta元素设计如下:



期望示例如下:


 



 


开发准备

 新建项目并引入 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");
 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 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,则需要通过修改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";
 
 ······
 
}


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("

你可能感兴趣的:(MyBatis 添加元数据自定义元素标签的实现代码)