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

开发背景

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

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");

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

······

}

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

//将动态语句字符串转换为XNode对象

XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new CustomXMLMapperEntityResolver());

return createSqlSource(configuration, parser.evalNode("/script"), parameterType);

} else {

// issue #127

script = PropertyParser.parse(script, configuration.getVariables());

TextSqlNode textSqlNode = new TextSqlNode(script);

if (textSqlNode.isDynamic()) {

return new CustomDynamicSqlSource(configuration, textSqlNode);

} else {

return new RawSqlSource(configuration, script, parameterType);

}

}

}

创建 CustomXMLMapperBuilder

复制自org.apache.ibatis.builder.xml.XMLMapperBuilder,修改构造函数使用 CustomXMLMapperEntityResolver 解析xml。

public CustomXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map sqlFragments) {

this(new XPathParser(inputStream, true, configuration.getVariables(), new CustomXMLMapperEntityResolver()),

configuration, resource, sqlFragments);

}

SqlSessionFactory

修改 buildSqlSessionFactory 方法,使用 CustomXMLMapperBuilder 来解析xml。

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

······

try {

//使用自定义 XMLMapperBuilder

CustomXMLMapperBuilder xmlMapperBuilder = new CustomXMLMapperBuilder(mapperLocation.getInputStream(),

targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());

xmlMapperBuilder.parse();

} catch (Exception e) {

throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);

} finally {

ErrorContext.instance().reset();

}

······

}

创建 CustomMapperAnnotationBuilder

复制自org.apache.ibatis.builder.annotation.MapperAnnotationBuilder,修改 loadXmlResource 方法使用 CustomXMLMapperBuilder。

private void loadXmlResource() {

if (!configuration.isResourceLoaded("namespace:" + type.getName())) {

······

if (inputStream != null) {

//使用自定义解析器支持自定义标签

CustomXMLMapperBuilder xmlParser = new CustomXMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());

xmlParser.parse();

}

}

}

创建 CustomMapperRegistry

复制自org.apache.ibatis.binding.MapperRegistry,修改 addMapper 方法使用 CustomMapperAnnotationBuilder。

@Override

public void addMapper(Class type) {

if (type.isInterface()) {

······

try {

knownMappers.put(type, new MapperProxyFactory<>(type));

// It's important that the type is added before the parser is run

// otherwise the binding may automatically be attempted by the

// mapper parser. If the type is already known, it won't try.

CustomMapperAnnotationBuilder parser = new CustomMapperAnnotationBuilder(config, type);

parser.parse();

loadCompleted = true;

} finally {

if (!loadCompleted) {

knownMappers.remove(type);

}

}

}

}

CustomConfiguration

修改 mapperRegistry 属性使用 CustomMapperRegistry。

public class CustomConfiguration extends Configuration {

······

protected final MapperRegistry mapperRegistry = new CustomMapperRegistry(this);

······

}

在Spring中使用

@Configuration

public class MybatisConfig {

@Bean

public PropertiesFactoryBean createPropertiesFactoryBean() throws IOException {

PropertiesFactoryBean bean = new PropertiesFactoryBean();

bean.setLocation(new ClassPathResource("mybatis.properties"));

return bean;

}

@Bean("sqlSessionFactory")

public SqlSessionFactoryBean createSqlSessionFactory(DataSource dataSource, PropertiesFactoryBean bean) throws IOException {

SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

factoryBean.setDataSource(dataSource);

factoryBean.setConfigurationProperties(bean.getObject());

return factoryBean;

}

}

到此这篇关于MyBatis 添加元数据自定义元素标签的实现代码的文章就介绍到这了,更多相关MyBatis自定义元素标签内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值