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

开发背景

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

meta元素设计如下:

<!-- 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
>

期望示例如下:

<insert id="insertMap" useGeneratedKeys="true" generator="meta">
 <meta table="USER" type="insert"/>
</insert>

<update id="updateMap">
 <meta table="USER" type="update"/>
</update>

<select id="selectOneByPk" resultType="java.util.HashMap">
 select
 <meta table="USER" type="columns"/>
 from USER
 where <meta table="USER" type="pk-col"/> = #{__PK_VALUE}
</select>

开发准备

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

<!-- mybatis -->
<dependency>
 <groupId>org.mybatis</groupId>
 <artifactId>mybatis</artifactId>
</dependency>
<!-- mybatis-spring -->
<dependency>
 <groupId>org.mybatis</groupId>
 <artifactId>mybatis-spring</artifactId>
</dependency>

增加自定义元素

创建 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<SqlNode> 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 来处理。示例如下:

<insert id="insertMap" useGeneratedKeys="true" generator="meta">
 <![CDATA[[
 <meta table="USER" type="insert"/>
 ]]>
</insert>

 如果不想要 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";

 ······

}
<!-- 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
>

CustomXMLLanguageDriver

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

/**
 * Mapper动态语句注解调用
 * <p>
 * "<script>select * from user <if test=\"id !=null \">where id = #{id} </if></script>"
 *
 * @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("<script>")) {
  //将动态语句字符串转换为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<String, XNode> 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 <T> void addMapper(Class<T> 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中使用

<!-- Mybatis SessionFactory-->
<bean id="sqlSessionFactory" class="com.my.ibatis.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="configurationProperties" >
    <bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
      <property name="locations" value="classpath*:mybatis.properties"/>
    </bean>
  </property>
</bean>
@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)

相关推荐

  • MyBatis动态SQL标签用法实例详解

    1.动态SQL片段 通过SQL片段达到代码复用 <!-- 动态条件分页查询 --> <sql id="sql_count"> select count(*) </sql> <sql id="sql_select"> select * </sql> <sql id="sql_where"> from icp <dynamic prepend="where&quo

  • Mybatis传单个参数和<if>标签同时使用的问题及解决方法

    // Mapper.java EmerEvent selectByAlarmId(Integer alarmId); // Mapper.xml <select id="selectByAlarmId" resultMap="BaseResultMap" parameterType="java.lang.Integer"> select <include refid="Base_Column_List" /&

  • MyBatis动态SQL中的trim标签的使用方法

    trim标记是一个格式化的标记,可以完成set或者是where标记的功能,如下代码: 1. select * from user <trim prefix="WHERE" prefixoverride="AND |OR"> <if test="name != null and name.length()>0"> AND name=#{name}</if> <if test="gender

  • mybatis 映射文件中if标签判断字符串相等的两种方式

    mybatis 映射文件中,if标签判断字符串相等,两种方式: 因为mybatis映射文件,是使用的ognl表达式,所以在判断字符串sex变量是否是字符串Y的时候, <if test="sex=='Y'.toString()"> <if test = 'sex== "Y"'> 注意: 不能使用 <if test="sex=='Y'"> and 1=1 </if> 因为mybatis会把'Y'解析为字

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

    开发背景  现有系统中维护了一套业务表相关列.键的元数据,希望通过读取元数据实现自动封装 SQL 语句.自定义主键策略.实现方案为入侵式修改 MyBatis,增加元素标签meta,支持业务开发中可以在XML映射文件中使用. meta元素设计如下: <!-- meta标签 可根据参数获取到对应的表名 动态生成语句 --> <!ELEMENT meta EMPTY> <!ATTLIST meta test CDATA #IMPLIED type (update|insert|se

  • mybatis插件实现自定义改写表名实例代码

    代码如下: @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.cl

  • MyBatis typeAliases元素标签(含注解方式)及其属性、设置方式

    目录 typeAliases元素标签及其属性.设置 简介 通过单个定义别名的方式 通过包扫描的方式 采用注解的方式 常见的 Java 类型内建的相应的类型别名 typeAliases和package标签的用法 typeAliases元素标签及其属性.设置 个人建议还是采用全类名的方式,这样可以很轻松的看到该类的所有方法等,比较方便直观:这样不过也有缺点,不利于维护等. 简介 typeAliases:别名处理器,可以为java类型(resultType)起别名.类型别名是为 Java 类型设置一个

  • 如何使用Spring自定义Xml标签

    目录 前言 正文 自定义NameSpaceHandler 自定义schema Parser Decorator 总结 前言 在早期基于Xml配置的Spring Mvc项目中,我们往往会使用<context:component-scan basePackage="">这种自定义标签来扫描我们在basePackae配置里的包名下的类,并且会判断这个类是否要注入到Spring容器中(比如这个类上标记了@Component注解就代表需要被Spring注入),如果需要那么它会帮助我们

  • MyBatis映射文件resultMap元素中使用多个association的方法

    现在有一张订单表t_stockorder,其拥有id.code.client_id.merchandise_id.merchandise_number.order_date.operator_id这些字段,其中client_id关联t_client表中code字段,merchandise_id关联t_merchandise表的code字段,operator_id关联t_employee表的code字段. 现在要通过SQL语句将订单表中t_stockorder的数据全部查询出来,SQL语句如下所示

  • MyBatis的9种动态标签详解

    目录 前言 动态标签用法 1.if 2.choose.when.otherwise 3.where 4.set 5.trim 6.foreach 7.bind 前言 MyBatis提供了9种动态SQL标签:trim.where.set.foreach.if.choose.when.otherwise.bind: 其执行原理为,使用OGNL从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能. 动态标签用法 1.if If : 当参数满足条件才会执行某个条件

  • 微前端之Web组件自定义元素示例详解

    目录 我们知道的 Web组件使用 名称规范 组件传参数并可以写模板包括js和css Shadow Dom 影子节点 类中的构造函数和钩子函数 getter/setter属性和属性反射 扩展原生 HTML 我们知道的 第一:我们熟知的HTML标签有 a, p, div, section, ul, li, h2, article, head, body, strong, video, audio 等等 第二:我们知道,a标签是链接,p标签是段落,div是块级,h2是字体,strong 是粗体,vid

  • javascript实现的动态添加表单元素input,button等(appendChild)

    写一个小系统时,需要动态添加表单元素,按自己的实现方法写了这篇教程! 我想各位在很多网站上都看到过类似的效果! 1.先用document.createElement方法创建一个input元素! 复制代码 代码如下: var newInput = document.createElement("input"); 2.设定相关属性,如name,type等  复制代码 代码如下: newInput.type=mytype;   newInput.name="input1"

  • Java开发框架spring实现自定义缓存标签

    自从spring3.1之后,spring引入了抽象缓存,可以通过在方法上添加@Cacheable等标签对方法返回的数据进行缓存.但是它到底是怎么实现的呢,我们通过一个例子来看一下.首先我们定义一个@MyCacheable package caching.springaop; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.

  • 浅谈Django自定义模板标签template_tags的用处

    自定义模板标签,过滤器.英文翻译是Customtemplatetagsandfilters.customfilter自定义过滤器今天不在我的记录范围之内,以后用到再看官方文档也不迟. **问题1:**customtemplatetags到底长啥样? customtemplatetags-github Manytemplatetagstakeanumberofarguments–stringsortemplatevariables–andreturnaresultafterdoingsomepro

随机推荐