Why

为什么设计这个注解?

本来前端构建一下的事情,为什么要绕这么大弯子呢?

主要我一直想设计一个通用的列表 Controller(提供数据及页面跳转),结合 gridTemplate.ftl(提供页面结构),直接成为一个列表页面。而不是重复去写这些代码。

现在设想只是初期,本篇正是对于这种构想的初步实践。

而且,个人认为,作为前端展示,每一列对应的中文标签完全可以由后端提供,完成前后端解耦。

当然也可以退一步,设计成前端可以自由选择的。可以自己指定,也可以使用后台提供的。

  • 从前端来看

如果你曾经写过前面的列表(table)展示。因为不同的列,中文的label显示是不同的。不同实体之间也不尽相同。

所以每次都要重写构建表头(tHead)。这很繁琐。

  • 从后端来看

假设有一张角色表。DDL如下:

DROP TABLE IF EXISTS `role`;
CREATE TABLE role (
  id          BIGINT(20) PRIMARY KEY AUTO_INCREMENT NOT NULL
  COMMENT '主键,自增',
  name        VARCHAR(64)                           NOT NULL
  COMMENT '角色名称',
  code        VARCHAR(64)                           NOT NULL
  COMMENT '角色代码',
  description VARCHAR(128)                          NULL DEFAULT ''
  COMMENT '角色说明',
  `created_time` timestamp NULL,
  `updated_time` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  INDEX `name`(`name`),
  UNIQUE INDEX `code_UNIQUE`(`code`)
)
  COMMENT '角色表';

对应实体类(model)。为了简洁,将所有的注释,Getter、Setter、toString() 等移除。

public class Role {

    private Long id;

    private String name;

    private String code;

    private String description;

    private Date createdTime;

    private Date updatedTime;
}
  • 后端与前端的衔接

当前端指定要显示哪些列后,会将对应的实体(model)对应的字段传过来。因为数据库的原因,我们没有一个地方去保存字段对应的显示名称。

当前端告诉后端需要显示的列后(比如显示所有列),真的有必要前端再重新构建一遍表头吗?

How

每一列对应的中文标签应该放在哪里呢?

最初的实体源于建表语句。所以中文标签可以追溯到DDL。这样,比如使用 mybatis, 代码自动生成时可以自己指定生成插件。直接生成在Java代码里。【见后续】

如下:

代码中的 @Label 就是我们接下来需要讲解的重点。

public class Role {
    @Label("主键,自增")
    private Long id;

    @Label("角色名称")
    private String name;

    @Label("角色代码")
    private String code;

    @Label("角色说明")
    private String description;

    private Date createdTime;

    private Date updatedTime;
}

@Label

定义及简单解释

注解定义如下:

/**
 * 用于保存数据库的显示LABEL
 * 规定:
 * 1) 数据库录入时注释需要符合以下规范:
 * Label:COMMENT
 * 标签和注释用【:】分开
 * 2) 没有注释的字段
 * Label 默认为字段名称
 * COMMENT 不设置
 * 3) 注释是否规范 直接根据 【:】 然后设置。
 *
 * 备注:
 * 1) 字段LABEL在利用 mybatis 生成MODEL时自动生成对应 信息。
 * 后期如果修改数据库内容,需保证model备注相应更新。
 * 同样的,如果想修改标签/备注内容,只需要直接修改@Label 的内容即可。
 * Created by houbinbin on 2017/2/13.
 * @version 1.7
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Label {

  /**
   * 数据库字段的显示标签
   * @return 默认返回空字符串
   */
  String value() default "";

  /**
   * 数据库字段的注释说明
   * @return 默认返回空字符串
   */
  String comment() default "";
}

解释如下:

感觉自己有些啰嗦,但总比冷冰冰的别人看不懂的好。就在废话下去:

  • 这个标签只是个人设计,可以根据这种设计自己拓展修改。并规定自己的DDL注释方式也可(解析方式肯定也要对应修改)。

  • 多数情况下DDL注释什么的是不规范的。可以手动直接添加注解,甚至生成注解后手动修改也可。

如何解析

可以根据自己需求,自行构建。

  /**
   * 解析 Role 字段上@Label 标签。如果不存在@Label 标签,默认返回字段名称。
   * @return 返回 字段标签列表
   */
  public static List<String> getFieldLabels() {
    List<String> labelList = new LinkedList<>();
    Field[] fields = Role.class.getDeclaredFields();

    for(Field field : fields) {
      Label label = field.getAnnotation(Label.class);

      if(label != null) {
        labelList.add(label.value());
      } else {
        labelList.add(field.getName());
      }
    }

    return labelList;
  }

直接打印内容如下:

[主键,自增, 角色名称, 角色代码, 角色说明, createdTime, updatedTime]

With mybatis-generator

mybatis-generator 如果你不知道如何使用,可查看Mybatis-Generator

  • LabelPlugin.java

备注:

1) com.ryo.mybatis.demo.label.annotation.Label 请自行替换为对应的路径

2) 数据库注释如果不是按照【Label:COMMENT】写法,请自行修改。

/**
 * Created by houbinbin on 16/7/28.
 * - 供 mybatis.generator 与 @Label 结合使用
 * @see com.ryo.mybatis.demo.label.annotation.Label
 */
public class LabelPlugin extends PluginAdapter {
    private FullyQualifiedJavaType dataAnnotation = new FullyQualifiedJavaType("com.ryo.mybatis.demo.label.annotation.Label");

    public LabelPlugin() {
    }

    public boolean modelFieldGenerated(Field field,
                                       TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn,
                                       IntrospectedTable introspectedTable,
                                       ModelClassType modelClassType) {
        this.addLabelAnnotation(field, topLevelClass, introspectedColumn);
        return true;
    }

    /**
     * 添加@Label注解
     * @see com.ryo.mybatis.demo.label.annotation.Label
     * @param field
     * @param topLevelClass
     * @param introspectedColumn 数据库列信息
     */
    protected void addLabelAnnotation(Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn) {
        topLevelClass.addImportedType(this.dataAnnotation);
        String annotation = buildLabelAnnotation(introspectedColumn);
        if(StringUtil.isNotEmpty(annotation)) {
            field.addAnnotation(annotation);
        }
    }

    private String buildLabelAnnotation(IntrospectedColumn introspectedColumn){
        String remark = introspectedColumn.getRemarks();    //取得注释
        if(StringUtil.isEmpty(remark)) {
            return StringUtil.EMPTY;
        }

        String[] remarks = remark.split(":");
        if(remarks.length == 1) {
            return String.format("@Label(\"%s\")", remarks[0]);
        } else if(remarks.length == 2) {
            return String.format("@Label(value = \"%s\"), comment = \"%s\"", remarks[0], remarks[1]);
        }
        throw new IllegalArgumentException("数据库注释描述有误introspectedColumn: "+introspectedColumn);
    }

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }
}
  • generatorConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC
        "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <context id="MySQLTables" targetRuntime="MyBatis3">
    <!--<context id="sqlContext" targetRuntime="MyBatis3Simple" defaultModelType="flat">-->

        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!--label plugin-->
        <plugin type="com.ryo.mybatis.demo.label.plugin.LabelPlugin"/>

        <commentGenerator>
            <!-- 是否取消注释 -->
            <property name="suppressAllComments" value="true"/>
            <!--取消时间注释-->
            <property name="suppressDate" value="true"/>
        </commentGenerator>

        <!--jdbc driver-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root" password="123456"/>

        <!--Xxx.java-->
        <javaModelGenerator targetPackage="com.ryo.mybatis.demo.label.domain" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!--XxxMapper.xml-->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>

        <!--XxxMapper.java-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.ryo.mybatis.demo.label.mapper" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <table tableName="role" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false"
               enableSelectByExample="false" selectByExampleQueryId="false">
            <!--<property name="useActualColumnNames" value="true" />-->
        </table>

    </context>
</generatorConfiguration>