Java/JavaFX企业级应用开发平台(fxEAP)
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

eap-platform-base

实现eap的自定义枚举类、ValueObject值对象、ReferenceData参照、资源国际化、oid等机制。

  • ValueObject,值对象机制,实际上是PO、BO、VO的结合体,用于前端界面显示、持久化等。支持单一、树形、聚合、多对多等多结构,包括持久化、界面展现。
  • ReferenceData,参照对象机制,用于外键在界面上的显示。基类有pk、code、name三个属性。
  • i18n,资源国际化,支持Java直接获取以及基于Spring MessageResource获取国际化资源的机制。
  • oid,主键生成机制,默认使用雪花算法实现。

自定义枚举

对于业务中常用的一些枚举信息,如数据状态、单据状态、账户状态等,在数据库表中保存为varchar(32)类型,以便于业务处理、前台界面展现,提供统一的枚举实现体系。

之所以在数据库层面把枚举定义为varchar类型而不是常见的int类型,主要是希望从数据本身也能够看出枚举值的含义,比如说,“Normal”就比“0”更直观一些。至于多占用的那点存储空间,只能作为实现SQL查询结果保持直观性的必要代价了。

枚举体系适用于code/value格式的数据封装。code用于持久化,value用于界面展现。 所有枚举类均需要提供根据code取枚举值、根据value取枚举值的静态方法。 由lrEA建模工具负责自动生成与枚举类相关的所有辅助类。

枚举与参照的差异

  1. 参照的数据项、来源数据的条数往往都比较多,UI一般需要采用(分页)表格的形式进行展现;枚举的数据项大多数情况下都是code/value的键值对,数据项少,记录数也较少,不般不会超过10个,界面使用下拉框(ComboBox)一次性展现全部数据。
  2. 参照的数据来源为其它业务已经产生的信息,数据相对是动态的。枚举一般都是在设计阶段进行初始化约定,数据相对是静态的。
  3. 参照信息需要通过sql从数据库中取数,而枚举信息除非是保存到数据库表中的,否则直接通过java代码处理,不需要进行数据库取数。 生成枚举信息并运用到lrEA建模工具中的信息。

类图

DataStatus

以DataStatusEnum相关的类图为例,说明枚举基类、国际化、Json序列化/反序列化的关系。

IEnumType接口

/**
 * <p>Title: LiRong Java Enterprise Application Platform</p>
 * <p>Description: 包括code/value属性的枚举类接口 </p>
 * CorpRights: lrJAP.com<br>
 * Company: lrJAP.com<br>
 *
 * @author jianjun.yu
 * @version 6.0.0.RELEASE
 * @date 2021-10-12
 * @see com.lirong.eap.platform.base.pub.enumtype.DataStatusEnum
 * @see com.lirong.eap.platform.base.pub.utils.IEnumTypeUtils
 * @since 1.0.0-SNAPSHOT
 */
public interface IEnumType {

    /**
     * 获取枚举编码
     *
     * @return 编码,用于标识枚举值以及保存到数据库
     */
    String getCode();

    /**
     * 获取枚举描述
     *
     * @return 描述,用于界面显示
     */
    String getValue();
}

示例枚举类

  • DataStatusEnum

    /**
     * <p>Title: LiRong Java Enterprise Application Platform </p>
     * <p>Description: DataStatusEnum(数据状态) 枚举类 </p>
     * <p>CorpRights: lrJAP.com</p>
     * <p>Company: lrJAP.com</p>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2022-04-19
     * @since 1.0.0-SNAPSHOT
     */
    @JsonSerialize(using = BaseEnumJsonSerializer.class)
    @JsonDeserialize(using = DataStatusEnumJsonDeserializer.class)
    public enum DataStatusEnum implements IEnumType {
    
        INIT("Init", getMessageWithJava(getResourceFileInfo(), "DataStatusEnum.Init", "初始化")), // $NON-NLS$
        NORMAL("Normal", getMessageWithJava(getResourceFileInfo(), "DataStatusEnum.Normal", "正常")), // $NON-NLS$
        FROZEN("Frozen", getMessageWithJava(getResourceFileInfo(), "DataStatusEnum.Frozen", "冻结")), // $NON-NLS$
        DISCARD("Discard", getMessageWithJava(getResourceFileInfo(), "DataStatusEnum.Discard", "作废")); // $NON-NLS$
    
        private final String code;
        private final String value;
    
        /* 用于在SQL中进行Decode */
        public static final String DECODE_SQL = "'Init', '初始化', 'Normal', '正常', 'Frozen', '冻结', 'Discard', '作废'"; // $NON-NLS$
    
        DataStatusEnum(String code, String value) {
    
            this.code = code;
            this.value = value;
        }
    
        @Override
        public String getCode() {
    
            return code;
        }
    
        /**
         * 通过国际化机制,获取当前区域需要显示的枚举值
         *
         * @return
         */
        @Override
        public String getValue() {
    
            return getMessageWithJava(getResourceFileInfo(), String.format("DataStatusEnum.%s", getCode()), this.value); // $NON-NLS$
        }
    
        /**
         * 国际化资源信息
         *
         * @return
         */
        public static String getResourceFileInfo() {
    
            return "i18n/platform/base/pub/enumtype/DataStatusEnum"; // $NON-NLS$
        }
    }
    
  • BaseEnumJsonSerializer,Json序列化公共类

    /**
     * <p>Title: LiRong Java Enterprise Application Platform</p>
     * Description: 所有实现IEnumType的枚举类的JSON序列化类 <br>
     * CorpRights: lrJAP.com<br>
     * Company: lrJAP.com<br>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2019-10-21
     */
    public class BaseEnumJsonSerializer extends JsonSerializer<IEnumType> {
    
        @Override
        public void serialize(IEnumType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    
            gen.writeStartObject();
            gen.writeFieldName("code");
            gen.writeString(value.getCode());
            gen.writeFieldName("value");
            gen.writeString(value.getValue());
            gen.writeEndObject();
        }
    }
    
  • DataStatusEnumJsonDeserializer,Json反序列化类

    /**
     * <p>Title: LiRong Java Enterprise Application Platform </p>
     * <p>Description: DataStatusEnum(数据状态) 枚举的Json反序列化类 </p>
     * <p>CorpRights: lrJAP.com</p>
     * <p>Company: lrJAP.com</p>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2022-04-19
     * @since 1.0.0-SNAPSHOT
     */
    public class DataStatusEnumJsonDeserializer extends AbstractEnumJsonDeserializer<DataStatusEnum> {
    
        @Override
        public Class<DataStatusEnum> getEnumClass() {
    
            return DataStatusEnum.class;
        }
    }
    

ValueObject

在进行UI数据展现、业务逻辑处理、数据持久化到数据库等操作的过程中,如何合理映射并高效处理业务对象、数据对象之间的关系,一直是一个比较困难并存在矛盾的问题。如果要便于界面展现,那么业务数据对象可能会显得臃肿,并且持久化时还会有类型转换、不需要持久化的字段控制等一系列问题;如果要便于持久化,要么数据库表设计有较多的冗余字段,要么界面展现面临诸多问题。

要展现方便,如枚举、参照等信息,在UI中要显示为业务人员能够看懂的信息,而不是主键、编码等没有业务意义的信息。 要数据库设计结构合理,满足或基本满足数据库第三范式的要求。如不能在集团基本信息表中,保存“国家地区”的编码、名称等信息。 要满足效率尽量优化、性能尽量高、资源占用尽量少等性能要求。要尽量减少访问数据库的次数,减少构造复杂结构的对象。

折中之下,我们设计了fxEAP的VO体系,以MyBatis(直接、少量SQL)的性能,达到Hibernate(VO映射)的易用性

Value Object(VO)体系在模式化开发中具有重要的地位:

  1. VO是数据库表在JAVA层面的直接映射,用于描述业务数据、参与业务逻辑处理,并在界面中展现;
  2. VO含有枚举、参照、关联主表、子表等类型的所有业务数据的定义;
  3. 数据库表中的数据可以查询并返回为VO或List或其它需要的结构(如用于分页显示的结构);
  4. VO可以直接持久化(包括结构非常复杂的VO);
  5. fxEAP建模工具将自动生成VO及相关辅助类、配置文件及信息;
  6. 提供差异VO体系及工具集,减少各组成部分之间的数据传输量,满足大数据量处理等

类图

ValueObject

ValueObject

  • propertyChangeSupport,属性值变化监听器,用于在JavaFX体系下监听属性值的变化,实现Java Bean和JavaFX Bean属性的双向绑定,以保证FXVO和VO中的属性值的一致性。该监听器的作用非常重要。所有需要使用FX体系进行包装的VO,在setter中,都必须使用本监听器通知FX来感知属性值的变化。
  • getProperty(String propertyName),根据值对象的属性名称,获取对应属性的值。
  • setProperty(String propertyName, Object value),根据属性名称,设置值对象对应属性的值。
  • hasProperty(String propertyName),判断实体中是否有指定的属性。
  • firePropertyChange(String propertyName, Object oldValue, Object newValue),发送属性变化事件。

AbstractValueObject

  • valueObjectStatus,值对象状态。常规:后台返回数据时,ValueObject的初始状态;新增、修改、删除用于判断对象的修改类型,同时用于判断List Bean中的数据需要进行的持久化操作(新增->保存,修改->更新,删除->删除)。指定了不需要持久化的注解,仅用于业务、界面控制。

单一VO(BasicVO)

  • initProperty,初始化属性值,如数据状态、值对象状态。

树形结构(TreeVO)

TreeVO

  • ITreeTableVO

    /**
     * <p>Title: LiRong Java Enterprise Application Platform</p>
     * Description: 树形结构VO对象基类 <br>
     * CorpRights: lrJAP.com<br>
     * Company: lrJAP.com<br>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2018-03-08
     */
    public interface ITreeTableVO extends IBasicTreeVO {
    
        /**
         * @return 子节点
         */
        List<? extends ITreeTableVO> getListChild();
    }
    
  • IBasicTreeVO

    /**
     * <p>Title: LiRong Java Enterprise Application Platform</p>
     * <p>Description: </p>
     * CorpRights: lrJAP.com<br>
     * Company: lrJAP.com<br>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2018-10-15
     */
    public interface IBasicTreeVO extends IBasicVO {
    
        String LEAF_FLAG_PROPERTY_CODE = "leafFlag";
    
        /**
         * 获取上级主键名
         *
         * @return String
         */
        String getTreeParentPrimaryKeyName();
    
        /**
         * 获取上级主键值
         *
         * @return String
         */
        String getParentPrimaryKey();
    
        /**
         * 显示内容,主要用于 TreeView
         *
         * @return String
         */
        String getDisplayValue();
    
        /**
         * 图标文件全路径
         *
         * @return String
         */
        String getLeafIconFileName();
    
        /**
         * 图标文件全路径
         *
         * @return String
         */
        String getNotLeafIconFileName();
    
        /**
         * 是否末级
         *
         * @return Boolean
         */
        Boolean getLeafFlag();
    
        /**
         * 设置是否末级
         *
         * @param leafFlag 是否末级
         */
        void setLeafFlag(Boolean leafFlag);
    }
    
  • ICheckTreeVO

    /**
     * <p>Title: LiRong Java Enterprise Application Platform</p>
     * <p>Description: </p>
     * CorpRights: lrJAP.com<br>
     * Company: lrJAP.com<br>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2019-01-23
     */
    public interface ICheckTreeVO extends ITreeTableVO {
    
        /**
         * @return 部分选中标志
         */
        Boolean getIndeterminateFlag();
    
        Boolean getSelectedFlag();
    
        /**
         * 設置部份選中標誌
         *
         * @param rightsFlag
         */
        void setIndeterminateFlag(final Boolean rightsFlag);
    
        void setSelectedFlag(final Boolean selectedFlag);
    }
    

聚合VO(AggregationVO)

AggregationVO

  • IAggregationMain

    /**
     * <p>Title: LiRong Java Enterprise Application Platform</p>
     * Description: 聚合对象主对象,管理所有子对象 <br>
     * CorpRights: lrJAP.com<br>
     * Company: lrJAP.com<br>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2017-10-20
     */
    public interface IAggregationMain extends IBasicVO {
    
        /**
         * 所有OneToOne对象的属性名称
         *
         * @return 所有1:1子实体的属性名称
         */
        List<String> getAssociationNames();
    
        /**
         * 所有OneToMany对象的属性名称
         *
         * @return 所有1:n子实体的属性名称
         */
        List<String> getCollectionNames();
    
        /**
         * 聚合对象合法性校验
         *
         * @return 校验结果信息
         */
        String aggregationValidate();
    
        /**
         * 根据属性名称获取OneToOne对象
         *
         * @param propertyName 1:1子实体属性名称
         * @return 1:1子实体
         */
        default IAggregationAssociation getAssociation(final String propertyName) {
    
            return (IAggregationAssociation) getProperty(propertyName);
        }
    
        /**
         * 根据属性名称设置OneToOne对象
         *
         * @param propertyName 1:1子实体属性名称
         * @param basicVO      1:1子实体
         */
        default void setAssociation(String propertyName, IAggregationAssociation basicVO) {
    
            setProperty(propertyName, basicVO);
        }
    
        /**
         * 根据属性名称获取OneToMany对象
         *
         * @param propertyName 1:n子实体属性名称
         * @return 1:n子实体列表
         */
        default List<IAggregationDetail> getCollection(String propertyName) {
    
            return (List<IAggregationDetail>) getProperty(propertyName);
        }
    
        /**
         * 根据属性名称设置OneToMany对象
         *
         * @param propertyName 1:n子实体属性名称
         * @param list         1:n子实体列表
         */
        default void setCollection(String propertyName, List<IAggregationDetail> list) {
    
            setProperty(propertyName, list);
        }
    }
    
  • IAggregationAssociation

    /**
     * <p>Title: LiRong Java Enterprise Application Platform</p>
     * Description: 不含Collection的聚合Association,用于实现1:1子实体 <br>
     * CorpRights: lrJAP.com<br>
     * Company: lrJAP.com<br>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2017-10-20
     */
    public interface IAggregationAssociation extends IBasicVO {
    
        /**
         * 获取父实体主键名称
         *
         * @return 父实体主键名称
         */
        String getParentPrimaryKeyName();
    
        /**
         * 设置子实体的上级主键值
         * 子实体中的的主实体,一般有两种数据类型:参照或是字符串。所以此处的参数为Object类型,在为子类生成代码时,已经根据元数据信息处理了该方法。
         *
         * @param parentPrimaryKey 父实体主键值
         */
        void setAssociationPrimaryKey(Object parentPrimaryKey);
    
        /**
         * 获取聚合类型
         *
         * @return {@link AggregationClassTypeEnum}
         */
        AggregationClassTypeEnum getAssociationType();
    }
    
  • IAggregationDetail

    /**
     * <p>Title: LiRong Java Enterprise Application Platform</p>
     * Description: 聚合对象Collection,用于实现1:n子实体 <br>
     * CorpRights: lrJAP.com<br>
     * Company: lrJAP.com<br>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2017-10-20
     */
    public interface IAggregationDetail extends IBasicVO {
    
        /**
         * 获取父实体主键名称
         *
         * @return 父实体主键名称
         */
        String getParentPrimaryPropertyCode();
    
        /**
         * 设置子实体的上级主键值
         * 子实体中的的主实体,一般有两种数据类型:参照或是字符串。所以此处的参数为Object类型,需要在实现类中根据情况自行处理。
         *
         * @param parentPrimaryPropertyCode 父实体主键值
         */
        void setParentPrimaryPropertyCode(Object parentPrimaryPropertyCode);
    }
    

多对多VO(ManyToManyVO)

ManyToManyVO

  • IManyToManyVO

    /**
     * <p>Title: LiRong Java Enterprise Application Platform</p>
     * Description: 多对多实体接口 <br>
     * CorpRights: lrJAP.com<br>
     * Company: lrJAP.com<br>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2018-12-25
     */
    public interface IManyToManyVO {
    
        /**
         * 获取主实体的主键名称
         */
        String getMainEntryPrimaryKeyName();
    
        /**
         * 获取子实体的主键名称
         */
        String getSubEntryPrimaryKeyName();
    
        /**
         * 获取主实体的实体编码
         *
         * @return String
         */
        String getMainEntryClassCode();
    
        /**
         * 获取次实体的实体编码
         *
         * @return String
         */
        String getSubEntryClassCode();
    
        /**
         * 获取对照实体的实体编码
         *
         * @return String
         */
        String getRelationClassCode();
    
        /**
         * 校验信息
         *
         * @return String
         */
        String manyToManyValidate();
    }
    
  • IBaseManyToManyVO

    /**
     * <p>Title: LiRong Java Enterprise Application Platform</p>
     * <p>Description: </p>
     * CorpRights: lrJAP.com<br>
     * Company: lrJAP.com<br>
     *
     * @author jianjun.yu
     * @version 3.0.0-SNAPSHOT
     * @date 2018-10-26
     */
    public interface IBaseManyToManyVO extends IManyToManyVO, IBasicVO {
    
        /**
         * 获取子实体列表
         *
         * @return List
         */
        List<? extends IBasicVO> getListSubEntry();
    
        /**
         * 获取所有子实体的主键值列表
         *
         * @return List
         */
        default List<String> getListSubEntryPrimaryKeyValue() {
    
            return getListSubEntry().stream().map(IBasicVO::getPrimaryKey).collect(Collectors.toList());
        }
    }
    

ReferenceData

在ValueObject体系之上,建立参照体系(ReferenceDataVO),实现数据库外键在VO层面的映射及处理,ReferenceDataVO体系的主要目标,就是使数据库外键在UI中显示时能够包含具体的业务含义。为数据对象在VO、前台界面的处理和展现方面提供了便利,降低了代码了复杂性、减少了SqlMapper配置文件及Java代码的数量,也降低了对开发人员的要求。

类图

ReferenceData

通过参照体系(ReferenceVO),实现数据库外键在VO层面的映射及处理,ReferenceVO体系的主要目标,就是使数据库外键在UI中显示时能够包含具体的业务含义。为数据对象在VO、前台界面的处理和展现方面提供了便利,降低了代码了复杂性、减少了SqlMapper配置文件及Java代码的数量,也降低了对开发人员的要求。

  • ReferenceDataVO,实现了IReferenceData接口,定义了三个属性pk(主键)、code(编码)、name(名称)。pk用于持久化,code、name用于界面显示,默认只显示name。

ReferenceDataVO

/**
 * <p>Title: LiRong Java Enterprise Application Platform</p>
 * <p>Description: 参照基类 </p>
 * <p>Copyright: Copyright (c) 2015</p>
 * <p>Company: lrJAP.com</p>
 *
 * @author jianjun.yu
 * @version 3.0.0-SNAPSHOT
 * @date 2016年7月6日
 */
@Data
public class ReferenceDataVO extends ValueObject implements IReferenceData, Serializable {

    private static final long serialVersionUID = 3358481572495210028L;

    public String pk;

    public String code;

    public String name;
    
    ......
}
  • pk:即外键,字符型。用于持久化,以即进行数据库关联查询。
  • code:编码,字符型。例如,显示pk对应数据的“国家地区编码”的值,用于UI显示,持久化时将忽略。
  • name:名称,字符型。例如,显示pk对应数据的“国家地区名称”的值,用于UI显示,持久化时将忽略。

ClassReferenceVO

/**
 * <p>Title: LiRong Java Enterprise Application Platform</p>
 * Description: ClassRef 的参照类<br>
 * Copyright: <br>
 * Company: lrJAP.com<br>
 *
 * @author jianjun.yu
 * @version 3.0.0-SNAPSHOT
 * @date 2018-06-01
 */
public class ClassReferenceVO extends ReferenceDataVO {

    private static final long serialVersionUID = 2811821844630947215L;

    public ClassReferenceVO() {

        super();
    }

    public ClassReferenceVO(String pk) {

        super(pk);
    }

    public ClassReferenceVO(String pk, String code, String name) {

        super(pk, code, name);
    }
}

资源国际化

  • LRResourceManager

    i18n

    提供获取国际化资源的机制,方法示例:

    /**
     * 用于当前用户的区域信息,从类路径上的资源文件中获取国际化信息
     *
     * @param propertyFile 资源文件类路径上的名称
     * @param code         资源id
     * @param defaultValue 默认值
     * @return 资源国际化信息
     */
    public static String getMessageWithJava(final String propertyFile, final String code, final String defaultValue) {
    
        return getMessageWithJava(propertyFile, ClientEnv.getInstance().getLocale(), code, defaultValue);
    }
    

    使用示例,以枚举类获取国际化资源为例:

    UNCHECK("Uncheck", getMessageWithJava(getResourceFileInfo(), "BillStatusEnum.Uncheck", "未复核")), // $NON-NLS$
    CHECKED("Checked", getMessageWithJava(getResourceFileInfo(), "BillStatusEnum.Checked", "已复核")), // $NON-NLS$
    UNPASS("Unpass", getMessageWithJava(getResourceFileInfo(), "BillStatusEnum.Unpass", "复核未通过")), // $NON-NLS$
    DISCARDED("Discarded", getMessageWithJava(getResourceFileInfo(), "BillStatusEnum.Discarded", "已作废")); // $NON-NLS$
    ...
        @Override
        public String getValue() {
    
            return getMessageWithJava(getResourceFileInfo(), String.format("BillStatusEnum.%s", getCode()), this.value); // $NON-NLS$
        }
    ...
        public static String getResourceFileInfo() {
    
            return "i18n/platform/base/pub/enumtype/BillStatusEnum"; // $NON-NLS$
        }