mybatis generator 支持数据版本号


#Java Mybatis#


mybatis generator 是什么?

MyBatis:自动生成Mapper代码和映射XML

什么是数据版本号?

数据版本号

方式1:使用 select for update 查询数据后修改版本号

参考: mybatis generator 生成 select for update 实现 select for update 。

在事务中通过 select for update 拿到数据的版本号,代码中对版本号加1,然后更新。

方式2:写一个版本号自动加1的插件

参考 mybatis generator 生成 select for update 写一个版本号自动加1的插件,插件会生成新的支持版本号自动更新的方法,例如 updateByExampleSelectiveWithAutoVersion ,其内部会对版本号字段自动加1,类似:

update <table> set version = version + 1 where <condition>;

插件示例:

package tool.plugin;

import org.mybatis.generator.api.FullyQualifiedTable;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.codegen.mybatis3.ListUtilities;
import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;

import java.util.*;

public class AutoAddVersionPlugin extends PluginAdapter {


    private final Map<FullyQualifiedTable, List<XmlElement>> elementsToAdd = new HashMap<>();

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }


    /**
     * 扩展接口中的  UpdateByExampleSelective 方法
     */
    @Override
    public boolean clientUpdateByExampleSelectiveMethodGenerated(Method method,
                                                                 Interface interfaze, IntrospectedTable introspectedTable) {
        if (introspectedTable.getTargetRuntime() == IntrospectedTable.TargetRuntime.MYBATIS3) {
            if (isTableHasVersionFiled(introspectedTable)) {
                copyAndAddMethod(method, interfaze);
            }
        }
        return true;
    }


    /**
     * 扩展 xml 中的  UpdateByExampleSelective 方法
     */
    @Override
    public boolean sqlMapUpdateByExampleSelectiveElementGenerated(
            XmlElement element, IntrospectedTable introspectedTable) {
        if (introspectedTable.getTargetRuntime() == IntrospectedTable.TargetRuntime.MYBATIS3) {
            if (isTableHasVersionFiled(introspectedTable)) {
                copyAndSaveElement(element, introspectedTable);
            }
        }
        return true;
    }


    /**
     * 将 XML 追加的内容放入 XML 根节点中
     * 本方法会在 sqlMapUpdateByExampleSelectiveElementGenerated 之后运行
     */
    @Override
    public boolean sqlMapDocumentGenerated(Document document,
                                           IntrospectedTable introspectedTable) {
        List<XmlElement> elements = elementsToAdd.get(introspectedTable.getFullyQualifiedTable());
        if (elements != null) {
            for (XmlElement element : elements) {
                document.getRootElement().addElement(element);
            }
        }
        return true;
    }


    /**
     * 接口类增加方法
     */
    private void copyAndAddMethod(Method method, Interface interfaze) {
        Method newMethod = new Method(method);
        newMethod.setName(method.getName() + "AndAddVersion");
        interfaze.addMethod(newMethod);
    }

    /**
     * XML 增加内容
     * 参考源码中的 UpdateByExampleSelectiveElementGenerator 类
     */
    private void copyAndSaveElement(XmlElement element, IntrospectedTable introspectedTable) {
        XmlElement answer = new XmlElement("update"); 

        answer.addAttribute(new Attribute(
                "id", introspectedTable.getUpdateByExampleSelectiveStatementId() + "AndAddVersion")); 

        answer.addAttribute(new Attribute("parameterType", "map"));  

        context.getCommentGenerator().addComment(answer);

        StringBuilder sb = new StringBuilder();
        sb.append("update "); 
        sb.append(introspectedTable
                .getAliasedFullyQualifiedTableNameAtRuntime());
        answer.addElement(new TextElement(sb.toString()));

        XmlElement dynamicElement = new XmlElement("set"); 
        answer.addElement(dynamicElement);

        for (IntrospectedColumn introspectedColumn : ListUtilities.removeGeneratedAlwaysColumns(introspectedTable
                .getAllColumns())) {

            // 如果是版本号字段
            if (Objects.equals(introspectedColumn.getProperties().get("isVersionField"), "true")) {
                sb.setLength(0);
                sb.append(MyBatis3FormattingUtilities.getAliasedEscapedColumnName(introspectedColumn));
                sb.append(" = "); 
                sb.append(MyBatis3FormattingUtilities.getAliasedEscapedColumnName(introspectedColumn));
                sb.append(" + 1 ,");
                dynamicElement.addElement(new TextElement(sb.toString()));
            }
            else {
                sb.setLength(0);
                sb.append(introspectedColumn.getJavaProperty("record.")); 
                sb.append(" != null"); 
                XmlElement isNotNullElement = new XmlElement("if"); 
                isNotNullElement.addAttribute(new Attribute("test", sb.toString())); 
                dynamicElement.addElement(isNotNullElement);

                sb.setLength(0);
                sb.append(MyBatis3FormattingUtilities.getAliasedEscapedColumnName(introspectedColumn));
                sb.append(" = "); 
                sb.append(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, "record.")); 
                sb.append(',');

                isNotNullElement.addElement(new TextElement(sb.toString()));
            }


        }

        answer.addElement(getUpdateByExampleIncludeElement(introspectedTable));

        // 追加
        List<XmlElement> elements = elementsToAdd.get(introspectedTable.getFullyQualifiedTable());
        if (elements == null) {
            elements = new ArrayList<XmlElement>();
            elementsToAdd.put(introspectedTable.getFullyQualifiedTable(), elements);
        }
        elements.add(answer);

    }


    private XmlElement getUpdateByExampleIncludeElement(IntrospectedTable introspectedTable) {
        XmlElement ifElement = new XmlElement("if"); 
        ifElement.addAttribute(new Attribute("test", "_parameter != null"));  

        XmlElement includeElement = new XmlElement("include"); 
        includeElement.addAttribute(new Attribute("refid", 
                introspectedTable.getMyBatis3UpdateByExampleWhereClauseId()));
        ifElement.addElement(includeElement);

        return ifElement;
    }

    private boolean isTableHasVersionFiled(IntrospectedTable introspectedTable) {
        for (IntrospectedColumn introspectedColumn : ListUtilities.removeGeneratedAlwaysColumns(introspectedTable.getAllColumns())) {
            if (Objects.equals(introspectedColumn.getProperties().get("isVersionField"), "true")) {
                return true;
            }
        }
        return false;
    }



}

对于版本号字段需要在 generator 用到的 XML 文件中指定 isVersionField 属性为 true,示例:

<table tableName="user_info" domainObjectName="UserInfo" modelType="flat" delimitIdentifiers="true" delimitAllColumns="true">
    <generatedKey column="id" sqlStatement="MySql" identity="true" />
    <columnOverride column="version">
        <property name="isVersionField" value="true"/>
    </columnOverride>
</table>

( 本文完 )