MyBatis 元数据<meta/>标签生成SQL

1,018 阅读4分钟

上一篇:MyBatis 添加元数据自定义元素标签

创建元数据实体

 按需创建元数据实体类,实现 Serializable 接口,用来存储元数据并放入缓存。

元数据解析器 MetaDataParse

 定义元数据解析接口,获取资源表元数据实体。依据自身业务创建 DefaultMetaDataParse 实现 MetaDataParse 接口。

public interface MetaDataParse {

    /**
     * 获取所有表元数据
     *
     * @return List<Table>
     */
    List<Table> tables();

    /**
     * 获取指定表元数据
     *
     * @return List<Table>
     */
    List<Table> tables(List<String> tableCodes);

    /**
     * 获取指定表元数据
     *
     * @param tableCode 表名
     * @return Table
     */
    Table table(String tableCode);

    /**
     * 设置解析器数据源
     *
     * @param dataSource 数据源
     */
    void setDataSource(DataSource dataSource);
}

元数据缓存管理 MetaDataCacheManager

 定义元数据缓存管理接口。依据自身业务创建 DefaultMetaDataCacheManager 实现 MetaDataCacheManager 接口。

public interface MetaDataCacheManager {

    /**
     * 资源表数据加入缓存
     *
     * @param table 资源表数据
     */
    void put(Table table);

    /**
     * 删除资源表缓存
     *
     * @param code 资源表编码
     */
    void remove(String code);

    /**
     * 缓存中获取资源表数据
     *
     * @param code 资源表编码
     * @return 资源表数据
     */
    Table get(String code);

    /**
     * 清空全部缓存
     */
    void clear();

    /**
     * 设置 configuration
     *
     * @param configuration mybatis配置
     */
    void setConfiguration(CustomConfiguration configuration);
}

元数据构造器 MetaStatementBuilder

 创建 MetaStatementBuilder 实现对元数据对象的构建及缓存,使用 CustomConfiguration、MetaDataParse、MetaDataCacheManager 创建 元数据构造器对象。如果未传入MetaDataParse和MetaDataCacheManager时,使用默认的 DefaultMetaDataParse 和 DefaultMetaDataCacheManager。

public class MetaStatementBuilder {
    private static final Log LOGGER = LogFactory.getLog(MetaStatementBuilder.class);

    protected final CustomConfiguration configuration;
    protected final MetaDataParse parse;
    private final MetaDataCacheManager cacheManager;

    public static MetaStatementBuilder getInstance(CustomConfiguration configuration, MetaDataParse parse, MetaDataCacheManager cacheManager) {
        return new MetaStatementBuilder(configuration,
                Optional.ofNullable(parse).orElse(new DefaultMetaDataParse()),
                Optional.ofNullable(cacheManager).orElse(new DefaultMetaDataCacheManager())
        );
    }

    public MetaStatementBuilder(CustomConfiguration configuration, MetaDataParse parse, MetaDataCacheManager cacheManager) {

        this.configuration = configuration;
        //元数据解析器
        this.parse = parse;
        //元数据缓存管理器
        this.cacheManager = cacheManager;
        //设置解析器数据源
        this.parse.setDataSource(configuration.getEnvironment().getDataSource());
        //设置解析器数据源
        this.cacheManager.setConfiguration(configuration);
    }

    /**
     * 重新获取 table 元数据
     *
     * @param tableCode 表名
     * @return Table
     */
    public Table builderTable(String tableCode) {
        cacheManager.removeTable(tableCode);
        return table(tableCode);
    }

    /**
     * 获取 table 元数据
     *
     * @param tableCode 表名
     * @return Table
     */
    public Table table(String tableCode) {
        //校验是否为null
        Optional.ofNullable(tableCode).orElseThrow(() -> new CacheException("Meta tableCode is null."));
        //缓存中获取
        Table table = cacheManager.getTable(tableCode);
        if (table == null) {
            LOGGER.debug("Meta table[" + tableCode + "] cache init.");
            //查询元数据
            table = parse.table(tableCode);
            Optional.ofNullable(table).orElseThrow(() -> new CacheException("Meta table[" + tableCode + "] parse fail."));
            //put缓存
            cacheManager.put(table);
            //生成主键策略
            keyGen(table);
        }
        return table;
    }

    /**
     * 生成主键策略
     *
     * @param table 资源表表元数据
     * @return org.apache.ibatis.executor.keygen.KeyGenerator
     */
    private KeyGenerator keyGen(Table table) {
        // TODO 生成自定义主键策略
    }

    public CustomConfiguration getConfiguration() {
        return configuration;
    }

    public MetaDataCacheManager getCacheManager() {
        return cacheManager;
    }

    public MetaDataParse getParse() {
        return parse;
    }
}

修改 SqlSessionFactoryBean

 在 SqlSessionFactoryBean 中加入元数据解析器、元数据缓存属性。方便配置自定义的元数据解析器和元数据缓存实现。

/**
 * 元数据解析器
 */
private MetaDataParse metaDataParse;

/**
 * 元数据缓存
 */
private MetaDataCacheManager cacheManager;

 在 buildSqlSessionFactory 方法中使用 metaDataParse 和 cacheManager 创建元数据构造器 MetaStatementBuilder,并将 MetaStatementBuilder 设置到 CustomConfiguration 中。

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    ······

    // 元数据操作相关拓展
    if (targetConfiguration instanceof CustomConfiguration) {
        //强制类型转换
        CustomConfiguration customConfiguration = (CustomConfiguration) targetConfiguration;
        //创建 MetaStatementBuilder 并添加到 Configuration
        customConfiguration.setMetaStatementBuilder(MetaStatementBuilder.getInstance(customConfiguration, metaDataParse, cacheManager)
                .builder());
    }
    
    ······

}

MetaSqlNode 使用 MetaStatementBuilder 生成语句

 MetaSqlNode 继承了 MyBatis 的 SqlNode 接口。添加自定义语句不是通过 DynamicContext.appendSql() ,而是拼装动态 XML 脚本,然后通过 CustomXMLScriptBuilder 将 XML转换为SqlNode,最后调用转换获取的 SqlNode.apply(DynamicContext) 将sql添加到 DynamicContext

下面以修改逻辑举例


public class MetaSqlNode implements SqlNode {
    
    ······
    
    @Override
    public boolean apply(DynamicContext context) {

        //支持Test语句
        if (!StringUtils.isEmpty(test) && !evaluator.evaluateBoolean(test, context.getBindings())) {
            return false;
        }

        //获取资源表
        table = configuration.getMetaStatementBuilder().table(table);

        //默认空语句
        SqlNode sqlNode = new TextSqlNode("");
        if (type == TypeEnum.update) {
            //更新语句
            sqlNode = updateScript(table);
        }
        
        return sqlNode.apply(context);
    }
    
    /**
     * 动态更新语句节点,需指定ID
     *
     * @param table    Table对象
     * @param hasWhere 是否包含条件
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode updateScript(Table table, boolean hasWhere) {
        Id id = table.getId();
        if (id == null || id.getCode() == null) {
            throw new RuntimeException(String.format("TableCode[%s] id not find!", table.getCode()));
        }
        //选取需要插入的列,忽略主键字段
        List<Column> columns = table.getColumnList().stream()
                .filter(p -> !table.getId().getCode().equalsIgnoreCase(p.getCode())).collect(Collectors.toList());

        //拼接语句
        StringBuilder sqlScript = new StringBuilder("<script> UPDATE ").append(table.getCode());
        sqlScript.append(" <trim prefix=\"set\" suffixOverrides=\",\"> ");
        columns.forEach(p -> {
            sqlScript.append("<if test=\"").append(String.format(MAP_PARAMETER, p.getCode())).append("!=null\">").append(p.getCode()).append("=#{").append(String.format(MAP_PARAMETER, p.getCode())).append("},</if>");
        });
        sqlScript.append("</trim>");
        sqlScript.append("WHERE ");
        sqlScript.append(id.getCode()).append("=#{").append(String.format(MAP_PARAMETER, id.getCode())).append("}");
        sqlScript.append("</script>");
        return scriptToSqlNode(sqlScript);
    }

    /**
     * 动态sql语句转SqlNode节点
     *
     * @param sqlScript 动态sql语句
     * @return org.apache.ibatis.scripting.xmltags.SqlNode
     */
    private SqlNode scriptToSqlNode(StringBuilder sqlScript) {
        XPathParser parser = new XPathParser(sqlScript.toString(), false, configuration.getVariables(), new CustomXMLMapperEntityResolver());
        CustomXMLScriptBuilder builder = new CustomXMLScriptBuilder(configuration, parser.evalNode("/script"), null);
        return builder.parseDynamicTags();
    }

    
    ······
    
}

实际使用

 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>
    <!-- 元数据解析器 -->
    <property name="metaDataParse">
        <bean class=" Class extends MetaDataParse "/>
    </property>
    <!-- 元数据缓存管理 -->
    <property name="metaDataParse">
        <bean class=" Class extends MetaDataCacheManager "/>
    </property>
</bean>

 元数据解析器 MetaDataParse 可以通过查询数据库中存储的表结构数据来构建 metadata,也可以扫描实体类的注解来构建 metadata。缓存管理 MetaDataCacheManager 可以使用简单的 ConcurrentHashMap 来实现,也可以是 Ehcache等缓存框架实现。