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建模工具负责自动生成与枚举类相关的所有辅助类。
枚举与参照的差异
- 参照的数据项、来源数据的条数往往都比较多,UI一般需要采用(分页)表格的形式进行展现;枚举的数据项大多数情况下都是code/value的键值对,数据项少,记录数也较少,不般不会超过10个,界面使用下拉框(ComboBox)一次性展现全部数据。
- 参照的数据来源为其它业务已经产生的信息,数据相对是动态的。枚举一般都是在设计阶段进行初始化约定,数据相对是静态的。
- 参照信息需要通过sql从数据库中取数,而枚举信息除非是保存到数据库表中的,否则直接通过java代码处理,不需要进行数据库取数。 生成枚举信息并运用到lrEA建模工具中的信息。
以DataStatusEnum相关的类图为例,说明枚举基类、国际化、Json序列化/反序列化的关系。
/**
* <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; } }
在进行UI数据展现、业务逻辑处理、数据持久化到数据库等操作的过程中,如何合理映射并高效处理业务对象、数据对象之间的关系,一直是一个比较困难并存在矛盾的问题。如果要便于界面展现,那么业务数据对象可能会显得臃肿,并且持久化时还会有类型转换、不需要持久化的字段控制等一系列问题;如果要便于持久化,要么数据库表设计有较多的冗余字段,要么界面展现面临诸多问题。
既要展现方便,如枚举、参照等信息,在UI中要显示为业务人员能够看懂的信息,而不是主键、编码等没有业务意义的信息。 又要数据库设计结构合理,满足或基本满足数据库第三范式的要求。如不能在集团基本信息表中,保存“国家地区”的编码、名称等信息。 还要满足效率尽量优化、性能尽量高、资源占用尽量少等性能要求。要尽量减少访问数据库的次数,减少构造复杂结构的对象。
折中之下,我们设计了fxEAP的VO体系,以MyBatis(直接、少量SQL)的性能,达到Hibernate(VO映射)的易用性。
Value Object(VO)体系在模式化开发中具有重要的地位:
- VO是数据库表在JAVA层面的直接映射,用于描述业务数据、参与业务逻辑处理,并在界面中展现;
- VO含有枚举、参照、关联主表、子表等类型的所有业务数据的定义;
- 数据库表中的数据可以查询并返回为VO或List或其它需要的结构(如用于分页显示的结构);
- VO可以直接持久化(包括结构非常复杂的VO);
- fxEAP建模工具将自动生成VO及相关辅助类、配置文件及信息;
- 提供差异VO体系及工具集,减少各组成部分之间的数据传输量,满足大数据量处理等
- 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),发送属性变化事件。
- valueObjectStatus,值对象状态。常规:后台返回数据时,ValueObject的初始状态;新增、修改、删除用于判断对象的修改类型,同时用于判断List Bean中的数据需要进行的持久化操作(新增->保存,修改->更新,删除->删除)。指定了不需要持久化的注解,仅用于业务、界面控制。
- initProperty,初始化属性值,如数据状态、值对象状态。
-
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); }
-
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); }
-
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()); } }
在ValueObject体系之上,建立参照体系(ReferenceDataVO),实现数据库外键在VO层面的映射及处理,ReferenceDataVO体系的主要目标,就是使数据库外键在UI中显示时能够包含具体的业务含义。为数据对象在VO、前台界面的处理和展现方面提供了便利,降低了代码了复杂性、减少了SqlMapper配置文件及Java代码的数量,也降低了对开发人员的要求。
通过参照体系(ReferenceVO),实现数据库外键在VO层面的映射及处理,ReferenceVO体系的主要目标,就是使数据库外键在UI中显示时能够包含具体的业务含义。为数据对象在VO、前台界面的处理和展现方面提供了便利,降低了代码了复杂性、减少了SqlMapper配置文件及Java代码的数量,也降低了对开发人员的要求。
- ReferenceDataVO,实现了IReferenceData接口,定义了三个属性pk(主键)、code(编码)、name(名称)。pk用于持久化,code、name用于界面显示,默认只显示name。
/**
* <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显示,持久化时将忽略。
/**
* <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
提供获取国际化资源的机制,方法示例:
/** * 用于当前用户的区域信息,从类路径上的资源文件中获取国际化信息 * * @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$ }