MyBatis adds metadata custom element tags

Create metadata entities

Create metadata entity classes as needed to implement the Serializable interface for storing metadata and caching.

The metadata parser MetaDataParse

Define metadata parsing interfaces to get resource table metadata entities. Create DefaultMetaDataParse to implement MetaDataParse interface according to its own business.

public interface MetaDataParse {

    /** * get all table metadata **@return List<Table>
     */
    List<Table> tables(a);

    /** * get the specified table metadata **@return List<Table>
     */
    List<Table> tables(List<String> tableCodes);

    /** * get the specified table metadata **@paramTableCode table name *@return Table
     */
    Table table(String tableCode);

    /** * Sets the parser data source **@paramDataSource */
    void setDataSource(DataSource dataSource);
}
Copy the code

MetaDataCacheManager MetaDataCacheManager

Define the metadata cache management interface. Based on its own to create DefaultMetaDataCacheManager MetaDataCacheManager interface.

public interface MetaDataCacheManager {

    /** * Add resource table data to cache **@paramTable Resource table data */
    void put(Table table);

    /** * Delete the resource table cache **@paramCode Resource table code */
    void remove(String code);

    /** * Get resource table data from cache **@paramCode Resource table code *@returnResource table data */
    Table get(String code);

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

    /**
     * 设置 configuration
     *
     * @paramConfiguration Mybatis Configuration */
    void setConfiguration(CustomConfiguration configuration);
}
Copy the code

Metadata constructor MetaStatementBuilder

Create MetaStatementBuilder to achieve the construction and cache of metadata objects, using CustomConfiguration, MetaDataParse, MetaDataCacheManager to create metadata constructor objects. If it is not the incoming MetaDataParse and MetaDataCacheManager, use the default DefaultMetaDataParse and 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;
        // Metadata parser
        this.parse = parse;
        // Metadata cache manager
        this.cacheManager = cacheManager;
        // Set the parser data source
        this.parse.setDataSource(configuration.getEnvironment().getDataSource());
        // Set the parser data source
        this.cacheManager.setConfiguration(configuration);
    }

    /** * Retrieve the table metadata **@paramTableCode table name *@return Table
     */
    public Table builderTable(String tableCode) {
        cacheManager.removeTable(tableCode);
        return table(tableCode);
    }

    /** * obtain table metadata **@paramTableCode table name *@return Table
     */
    public Table table(String tableCode) {
        // Check whether the value is null
        Optional.ofNullable(tableCode).orElseThrow(() -> new CacheException("Meta tableCode is null."));
        // From the cache
        Table table = cacheManager.getTable(tableCode);
        if (table == null) {
            LOGGER.debug("Meta table[" + tableCode + "] cache init.");
            // Query metadata
            table = parse.table(tableCode);
            Optional.ofNullable(table).orElseThrow(() -> new CacheException("Meta table[" + tableCode + "] parse fail."));
            / / put the cache
            cacheManager.put(table);
            // Generate a primary key policy
            keyGen(table);
        }
        return table;
    }

    /** * Generate primary key policy **@paramTable Resource table Table metadata *@return org.apache.ibatis.executor.keygen.KeyGenerator
     */
    private KeyGenerator keyGen(Table table) {
        // TODO generates a custom primary key policy
    }

    public CustomConfiguration getConfiguration(a) {
        return configuration;
    }

    public MetaDataCacheManager getCacheManager(a) {
        return cacheManager;
    }

    public MetaDataParse getParse(a) {
        returnparse; }}Copy the code

Modify SqlSessionFactoryBean

Add metadata parser, metadata cache properties to SqlSessionFactoryBean. Easy to configure custom metadata parser and metadata cache implementation.

/** * metadata parser */
private MetaDataParse metaDataParse;

/** * Metadata cache */
private MetaDataCacheManager cacheManager;
Copy the code

MetaStatementBuilder is created using metaDataParse and cacheManager in the buildSqlSessionFactory method. And set MetaStatementBuilder to CustomConfiguration.

protected SqlSessionFactory buildSqlSessionFactory(a) throws Exception {· · · · · ·// Metadata manipulation related extensions
    if (targetConfiguration instanceof CustomConfiguration) {
        // Cast
        CustomConfiguration customConfiguration = (CustomConfiguration) targetConfiguration;
        // Create MetaStatementBuilder and add it to ConfigurationcustomConfiguration.setMetaStatementBuilder(MetaStatementBuilder.getInstance(customConfiguration, metaDataParse, cacheManager) .builder()); }......}Copy the code

MetaSqlNode uses MetaStatementBuilder to generate statements

MetaSqlNode inherits MyBatis SqlNode interface. Instead of adding custom statements via DynamicContext.appendSQL (), assemble dynamic XML scripts and convert XML to SqlNode via CustomXMLScriptBuilder, Finally, call sqlNode. apply(DynamicContext) to add SQL to DynamicContext.

The following is an example of modifying logic


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

        // Support the Test statement
        if(! StringUtils.isEmpty(test) && ! evaluator.evaluateBoolean(test, context.getBindings())) {return false;
        }

        // Get the resource table
        table = configuration.getMetaStatementBuilder().table(table);

        // Default null statement
        SqlNode sqlNode = new TextSqlNode("");
        if (type == TypeEnum.update) {
            // Update statement
            sqlNode = updateScript(table);
        }
        
        return sqlNode.apply(context);
    }
    
    /** * dynamic update statement node, specify ID **@paramTable Table object *@paramHasWhere contains conditions *@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()));
        }
        // Select the column to be inserted, ignoring the primary key fieldList<Column> columns = table.getColumnList().stream() .filter(p -> ! table.getId().getCode().equalsIgnoreCase(p.getCode())).collect(Collectors.toList());// Concatenate statements
        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);
    }

    /** * Dynamic SQL statement to SqlNode **@paramSqlScript dynamic SQL statement *@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);
        returnbuilder.parseDynamicTags(); }......}Copy the code

The actual use

Spring customizes the metadata parser and metadata cache management implementation as follows:

<! -- 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 name="metaDataParse"> <bean class=" class extends MetaDataCacheManager"Copy the code

The metadata parser MetaDataParse can build metadata by querying table structure data stored in the database, or by scanning annotations of entity classes to build metadata. Cache management MetaDataCacheManager can be implemented using a simple ConcurrentHashMap or a cache framework such as Ehcache.