模型定义
@TModel
/** * 用于注解模型声明 */@Documented@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface TModel { /** * 模型 key ,需保证模块内唯一; * 留空时默认为 Class.simpleName */ String key() default "";
/** * 模型名称,选填 */ String name() default "";
/** * 模型主字段,主字段是在部分内建场景中,需要展示模型记录时展示的字段(例如在下拉搜索框中搜索到的记录) */ String mainField() default RootModel.id_field;
/** * 描述信息 */ String desc() default "";
/** * id 生成策略,当 isTransient 为 true 时设置无效 * 当 BaseModel 的 ID 泛型为 Long 时,支持自定义或自增; * 当 BaseModel 的 ID 泛型为 String 时,支持自定义或自增模板或 UUID * * @see IDRule */ IDRule idRule() default @IDRule();
/** * 索引,当 isTransient 为 true 时设置无效 * * @see Index */ Index[] indexes() default {};
/** * 字段组配置 * * @see FieldGroup */ FieldGroup[] fieldGroups() default {};
/** * 删除策略 */ DeleteStrategy deleteStrategy() default DeleteStrategy.Physical;
/** * 分表策略 */ Partition partition() default @Partition(enabled = false);
/** * 是否为非持久化模型 * 非持久化模型数据不会持久化,不会生成默认 Server Action ,但默认 View Action 还是会存在 */ boolean isTransient() default false;
/** * 如果为True, 则表示该类不是一个完整的实体, 只是一个模型基类 */ boolean superClass() default false;
/** * 是否支持搜索 */ boolean enableSearch() default false;
/** * 是否支持导入 */ boolean enableImport() default false;
/** * 是否支持导出 */ boolean enableExport() default false;
/** * 索引别名,默认为该模型的key取值 */ String searchIndexAlias() default "";
/** * 是否开启搜索,默认为false表示不开启 */ boolean searchIndexEnabled() default false;
/** * 模型对应表名,默认为 moduleKey_javaClassSimpleName */ String dbTableName() default "";}@IDRule
public @interface IDRule { String UUID = "UUID"; String AUTO_INCREMENT = "AUTO_INCREMENT"; String CUSTOM = "CUSTOM"; /** * 除了 UUID 、AUTO_INCREMENT 、CUSTOM 三个静态项外,还支持模板格式化 * 当 ID type 为 Long 时,支持 CUSTOM 和 AUTO_INCREMENT,默认为 AUTO_INCREMENT * 当 ID type 为 String 时,支持 CUSTOM 、UUID 和模板格式化,默认为 UUID * 模板格式化可以是 "A" 或 "A[00123]" 这样的类似格式 * "A" 意思是从将 "A" 作为前缀连上从 1 开始的自增数字 * "A[00123]" 意思是将 [00123] 部分替换为从 123 开始的自增数字,并补 0 到指定位数 * * 当设为 CUSTOM 时,Trantor 和 DS 不会再为业务生成 ID ,需要业务方自己 set ID 到模型实例中 */ String rule() default "";}@Index
public @interface Index { /** * 索引 key ,不填时会自动按 columns 生成; * 例如 columns = {"id", "name"} ,那么就会变成 idx_id_name(或者 uk_id_name ,如果 unique = true); * 建议不填 */ String name() default "";
/** * 被索引的列,值为模型字段名 */ String[] columns();
/** * 是否惟一索引 */ boolean unique() default false;}@FieldGroup
Trantor 中部分页面和机制是内置的,并不能通过编辑视图 DSL 文件来改变。例如模型对象 hover 时弹出的浮层;全局搜索时展示的数据列表;模型选择器的弹出框等。字段组便是用来在这些场景下指定模型应该展示哪些字段的。
目前有三种字段组,每种仅可定义一个,分别为:
- FieldGroupType.SEARCH_SHOW 搜索展示字段组,用于全局搜索时,模型数据 Table 的展示列。
- FieldGroupType.DEFAULT_SHOW 默认展示字段组,用于模型对象 hover 浮层,模型选择器弹出 Table 列。
- FieldGroupType.SEARCHABLE 可搜字段组,用于指定在模糊搜索框(全局搜索、Table)搜索时会检索的字段。
public @interface FieldGroup { /** * 字段组类型 */ FieldGroupType type() default FieldGroupType.DEFAULT_SHOW;
/** * 字段组包含的字段名称 */ String[] fieldName();}模型字段定义
@TModelField
/** * 模型字段注解,未注解的字段不会被当做模型字段,而会被直接忽略 */@Documented@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public @interface TModelField { /** * 字段显示名称,用于前端界面展示 */ String name() default "";
/** * 字段描述 */ String desc() default "";
/** * 字段类型,默认为根据字段 Java 类型或 Meta 信息注解自动推断; * 部分字段类型无法推断,需要显示声明 * * @see TModelFieldType */ TModelFieldType type() default TModelFieldType.Inference;
/** * 是否存储,当为 false 时不会在存储中建立字段,也不会持久化,但可用于和前端交互 */ boolean store() default true;
/** * 是否可空 */ boolean nullable() default true;
/** * 默认值 */ String defaultValue() default "";
/** * 模型表中的字段名,默认与 java field name 相同 */ String dbColumnName() default "";}TModelFieldType
public enum TModelFieldType { /** * 普通字符串(单行文本)适用 Java 类型:String * * @see io.terminus.trantor.api.annotation.typemeta.TextMeta */ Text { @Override public boolean isSearchable() { return true; }
@Override public boolean isString() { return true; } }, /** * 普通字符串(多行文本)适用 Java 类型:String * @see io.terminus.trantor.api.annotation.typemeta.TextMeta */ MultiText { @Override public boolean isSearchable() { return true; }
@Override public boolean isString() { return true; } }, /** * 富文本 */ RichText { @Override public boolean isSearchable() { return true; }
@Override public boolean isString() { return true; } }, /** * 日期, 仅有日期, 如 2019-01-01 适用 Java 类型:Date 、LocalDate */ Date { @Override public boolean isDate() { return true; } }, /** * DateTime:具体时间, 如 2019-01-01 12:10:00 适用 Java 类型:Date 、LocalDateTime */ DateTime { @Override public boolean isDate() { return true; } }, /** * Time:时间, 如 13:20:50 (可以使用 LocalTime) 适用 Java 类型:Date 、LocalTime */ Time { @Override public boolean isDate() { return true; } }, /** * 整形数字 适用 Java 类型:int 、Integer 、short 、Short 、long 、Long */ Int { @Override public boolean isNumber() { return true; } }, /** * 浮点类型 */ Float { @Override public boolean isNumber() { return true; } }, /** * 布尔 适用 Java 类型:boolean 、Boolean * @see io.terminus.trantor.api.annotation.typemeta.BooleanMeta */ Boolean, /** * 枚举值,不可扩展(建议优先使用 Dictionary)适用 Java 类型:对应的枚举 Class * @see io.terminus.trantor.api.annotation.typemeta.EnumMeta */ Enum, /** * 字典类型,可扩展的枚举类型 * * @see io.terminus.trantor.api.annotation.typemeta.DictionaryMeta */ Dictionary { @Override public boolean isString() { return true; } }, /** * @see io.terminus.trantor.api.annotation.typemeta.DictionaryMeta */ MultiDictionary { @Override public boolean isJson() { return true; } }, /** * 单张图片 适用 Java 类型:String(图片 url) */ Image { @Override public boolean isString() { return true; } }, /** * 密码 适用 Java 类型:String */ Password { @Override public boolean isString() { return true; } }, /** * 邮箱 适用 Java 类型:String */ Email { @Override public boolean isSearchable() { return true; }
@Override public boolean isString() { return true; } }, /** * 手机 适用 Java 类型:String */ Phone { @Override public boolean isSearchable() { return true; }
@Override public boolean isString() { return true; } }, /** * Json 类型,支持使用非持久化模型或 Map 、List 适用 Java 类型:任何 Java 对象 */ Json { @Override public boolean isJson() { return true; } }, /** * 对一关联到其它模型 适用 Java 类型:对应的模型 Class * @see io.terminus.trantor.api.annotation.typemeta.RelationMeta */ ToOne { @Override public boolean isSingleObject() { return true; } }, /** * 对多关联到其它模型 适用 Java 类型:对应的模型 Class List ,即 List * * @see io.terminus.trantor.api.annotation.typemeta.RelationMeta */ ToMany { @Override public boolean isMultiObject() { return true; } }, Link { @Override public boolean isSingleObject() { return true; } }, LinkMany { @Override public boolean isMultiObject() { return true; } }, LookupOne { @Override public boolean isSingleObject() { return true; } }, LookupMany { @Override public boolean isMultiObject() { return true; } }, /** * 附件 * * @see io.terminus.trantor.api.annotation.typemeta.AttachmentMeta */ Attachment { @Override public boolean isJson() { return true; } }, /** * @see io.terminus.trantor.api.annotation.typemeta.AddressMeta */ Address { @Override public boolean isJson() { return true; } };Meta Annotation
Meta Annotation 用于为特定字段类型声明补充信息,例如字符串长度限制、数字精度、附件允许格式等。
@TextMeta - Text, MultiText, RichText
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface TextMeta { int DEFAULT_LENGTH = 256;
/** * 字符串长度 */ int length() default DEFAULT_LENGTH;
/** * 仅对 Text 类型字段有效 * STRING(Test)+TIMES(yyyy-MM-dd HH:mm:ss.SSS)+INCRE(1,4,0,100)+RANDOM(4,1) * * @see <a href="https://yuque.antfin.com/docs/share/65baa6b9-d03a-41b7-98d4-68a8a76ba2cb#85a838f4">规则字段文档</a> */ String rule() default "";}@NumberMeta - Number
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface NumberMeta { int DEFAULT_DIGITS = 0;
/** * 数值展示的单位,例如 % 、天等等 */ String unit() default "";
/** * 格式化表达式 */ String format() default "";}@BooleanMeta - Boolean
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface BooleanMeta { /** * 当值为 true 时的前端显示标签 */ Label trueLabel() default @Label("是");
/** * 当值为 false 时的前端显示标签 */ Label falseLabel() default @Label("否");}@EnumMeta - Enum
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface EnumMeta { /** * 每一个枚举值的前端显示标签; * 也可以不通过 @EnumMeta 指定标签信息,而是在对应的枚举类上使用 @Label 注解声明; * 此时可以不使用 EnumMeta 进行注解 */ NamedLabel[] labels() default {};
/** * 显示枚举 name * 若不配置 则默认显示全部labels选项 * 若配置 则只显示配置的枚举name */ String[] values() default {};
/** * 排除枚举name */ String[] excludes() default {};}@DictionaryMeta - Dictionary
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface DictionaryMeta { /** * 字典值来源类,不同字典值的展示信息通过在字典类中的值上注解 {@link Label} 来实现 */ Class<?> clazz() default Object.class;
/** * 字典 Key, 当 dictionaryKey 和 Class 都填写时, 以 dictionaryKey 为准 */ String dictionaryKey() default "";
/** * 字典类型, 当 dictionaryKey 和 Class 都填写时, 以 dictionaryType 为准 * 反之以字典类 {@Link io.terminus.trantor.api.annotation.TDict#type()} 的注解为准 */ TDictType dictionaryType() default TDictType.String;}@DateTimeMeta - DateTime
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface DateTimeMeta { /** * 时间的最小单位,例如 小时、分钟等等 */ TimeUnit unit() default TimeUnit.MINUTES;
/** * 格式化表达式,用于前端展示格式所用。 * 需要注意这里目前使用的并不是 Java 的 SimpleDateFormat 的格式,而是前端采用的 moment 的 format 格式。 * 参考 https://momentjs.com/ */ String format() default "";}@TimeMeta - Time
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface TimeMeta { /** * 时间的最小单位,例如 小时、分钟等等 */ TimeUnit unit() default TimeUnit.MINUTES;
/** * 格式化表达式 * @return */ String format() default "";}@ImageMeta - Image
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ImageMeta { /** * 文件最大允许大小,单位 KB ,默认 Integer.MAX_VALUE ,大约 2T */ int maxSize() default Integer.MAX_VALUE;
/** * 附件的读取策略, 默认为私有读 */ ReadPolicy readPolicy() default ReadPolicy.PRIVATE;}@JsonMeta - Json
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface JsonMeta { /** * 当对应字段被声明为 List<T> 时,声明 List 中的泛型类型(因为泛型被擦除了); * 可选类型为一系列基本类型或 extends BaseModel 的非持久化模型类型 */ Class<?> arrayClass() default Object.class;}@RelationMeta - ToOne, ToMany
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface RelationMeta { /** * 关系名称,用于惟一标识一个模型间关系; * 当关系被声明为双向时(即在两个模型中都有对彼此的同业务含义的关联字段声明),需要两边的关系名称相同; * 为了保证单向关系在后面可以视需要转变为双向关系,目前该名称必填 */ String name();
/** * 关联模型 class ,当字段 Java 类型非 List 时,可以不填,由 Trantor 进行推断; * 当 Java 类型为 List 时(即代表 ToMany 关系),因为类型擦除的原因,必填 */ Class<? extends RootModel> modelClass() default RootModel.class;
/** * 关联字段更新策略, 默认只解除关系,不级联删除 */ OnDeleteType onDelete() default OnDeleteType.SET_NULL;
/** * 关联表名 * 仅适用与 ManyToMany */ String relationTableName() default "";
/** * 反向关系,默认自动生成,也可以强制指定反向关系 */ RelationType opposite() default RelationType.AUTO;
/** * 反向关系字段名 * 适用于 ManyToMany 仅在单边使用 {@link RelationMeta#opposite()} 申明时,指定在关系表中对面关联的字段名 */ String oppositeColumnName() default "";
/** * 关系字段在哪张表中,仅适用于 OneToOne 关系 */ RelationSide side() default RelationSide.AUTO;}@AttachmentMeta - Attachment
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface AttachmentMeta { enum ShowType { PICTURE, AVATAR, VIDEO, DOCUMENT, ALL }
/** * 展示类型, 默认不限制 */ ShowType showType() default ShowType.ALL;
/** * 允许的文件类型,后缀名,不区分大小写,留空则为不限制,默认不限制 * 可以使用内置常量来简化常见文件类型的声明 * * @see io.terminus.trantor.api.annotation.typemeta.AllowedTypes */ String[] allowedTypes() default {};
/** * 单文件最大允许大小,单位 KB ,默认 Integer.MAX_VALUE ,大约 2T */ int maxSize() default Integer.MAX_VALUE;
/** * 允许上传文件数量,默认 1 */ int countLimit() default 1;
/** * 附件的读取策略, 默认为私有读 */ ReadPolicy readPolicy() default ReadPolicy.PRIVATE;}@AddressMeta - Address
@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface AddressMeta { /** * 是否包含详细地址 * * @return */ boolean containsDetail() default true;
/** * 是否包含邮政编码 * * @return */ boolean containsPostcode() default true;}@Label
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface Label { /** * 标签展示名 */ String value();
/** * 展示在标签名前的图标 */ Icon icon() default Icon.None;
/** * 图标颜色 */ Icon.Color iconColor() default Icon.Color.Black;}@NamedLabel
public @interface NamedLabel { /** * 枚举值 Name */ String name();
/** * 该枚举值在前端的展示名 */ String value();
/** * 展示图标 */ Icon icon() default Icon.None;
/** * 图标颜色 */ Icon.Color iconColor() default Icon.Color.Black;}@Reference
冗余字段注解,可以在模型中冗余关联模型的字段。只能冗余 ToOne 关系模型中的字段。
public @interface Reference { /** * 关联的 ToOne 字段名 */ String fromField();
/** * 关联到的模型内字段名 */ String mappingField();
/** * 数据同步策略 */ Refresh onChange() default Refresh.SYNC;
enum Refresh { NEVER, SYNC, ASYNC }}举例:
@TModelField(name = "地址")@RelationMeta(name = "OneAddressRel")private Address singleAddress;
@TModelField(name = "省")@Reference( fromField = User.singleAddress_field, mappingField = Address.province_field)private String province;@TModelFieldTemplate
以 java class 的形式管理模型内字段,示例如下:
@Data@EqualsAndHashCode(callSuper = true)@TModelpublic class User extends BaseModel<Long> { private static final long serialVersionUID = 4034704005484102406L;
@TModelFieldTemplate private Customer customer;}@Datapublic class Customer implements Serializable { private static final long serialVersionUID = 36860781092262196L; @TModelField private String name; @TModelField @RelationMeta(name = "UserToAddressRel") private Address address;}其中 name 和 address 都是直接属于模型 User 的字段。可以在代码和 View DSL 中用 customer_xxx 使用
@TAction(modelClass = User.class)public User create(User user) { user = userDAO.create(user, select -> { select.get(User.customer_name_field); select.getObject(User.customer_address_field, Selectable::selectAll); }); return user;}定义模型间关系
Trantor 通过在模型上定义关联关系字段来描述模型间关系,支持所有 OneToOne 、OneToMany 、ManyToOne 和 ManyToMany 四种关系。
在单边定义关系
关系可以只在单边模型上定义。Trantor 会识别字段声明从而推断出是且关系是什么(字段类型为模型类或 List ,需要注意的是当字段类型为 List 时,因为泛型擦除,需要使用 @RelationMeta(modelClass = Class) 来指定对应的关联模型)。
当在单边模型定义 ToOne 关系时,默认双边是 ManyToOne 关系;当定义 ToMany 关系时,默认双边是 OneToMany 关系。如果此时想定义为其余两种关系,需要使用 @RelationMeta(opposite = RelationType) 来指定反向关系。
见下面的例子:
@TModelclass ModelA extends BaseModel<Long> { @TModelField @RelationMeta(name = "modelB") private ModelB modelB; // ManyToOne @TModelField @RelationMeta(name = "modelC", modelClass = ModelC.class) private List<ModelC> modelC; // OneToMany @TModelField @RelationMeta(name = "modelD", opposite = RelationType.ToOne) private ModelD modelD; // OneToOne @TModelField @RelationMeta(name = "modelE", modelClass = ModelE.class, opposite = RelationType.ToMany) private List<ModelE> modelE; // ManyToMany}在双边定义关系
关系也可以在双边模型上都进行定义,Trantor 通过匹配 @RelationMeta(name = RelationName) 相同来确认两边模型上描述的是同一个关系。
以下是例子:
@TModelclass ModelA extends BaseModel<Long> { @TModelField @RelationMeta(name = "relation1") private ModelB modelB; // OneToOne @TModelField @RelationMeta(name = "relation2", modelClass = ModelB.class) private List<ModelB> modelB2; // ManyToMany}
@TModelclass ModelB extends BaseModel<Long> { @TModelField @RelationMeta(name = "relation1") private ModelA modelA; // OneToOne @TModelField @RelationMeta(name = "relation2", modelClass = ModelA.class) private List<ModelA> modelA2; // ManyToMany}决定在哪一边定义关系
这取决于开发者希望以何种方式组合访问模型数据。只当当前模型拥有关联模型字段(即定义了关系)时,Trantor 才支持使用 <Field name="modelB.field"/> 级联字段声明、lookupFrom 数据来源等特性;手写 GQL 或使用 TrantorDAO 访问数据时,可直接访问关联模型数据。
不落库的非持久化模型
模型分为持久化模型与非持久化模型。没有特殊指定的情况下,所有的模型都是持久化模型,在模块启动时,Trantor 生命周期会解析持久化模型的结构,并向 DataStore 注册。DataStore 在收到注册请求后,会按需进行 DDL(创建表或修改表结构)产生真实物理表。同时 Trantor 会为每个持久化模型生成一组系统默认 Action ,包含基础 CUD 和默认视图跳转。
非持久化模型则不同,通过 @TModel(isTransient = true) 将模型置为非持久化,会有以下区别:
- 不会向 DataStore 注册,因此不会有真实物理表对应
- 这也意味着无法作为其它持久化模型的 ToOne、ToMany 关联模型
- 可以作为 Json 字段类型的目标模型使用
- 有系统默认的 ViewAction ,但不会有系统默认 ServerAction ,可以作为自定义 ServerAction 的入参类型
- 可以通过选择性的实现 StandardActions 抽象类中的部分方法,赋予非持久化模型和持久化模型相同的行为
- 可以自己实现 DataAction interface(MultiDataAction 、SingleDataAction),结合 ViewAction 来自定义非持久化模型的数据获取行为
非持久化模型往往用于以下场景:
- 用于承载和底层存储模型不适配的交互
- 用于 Json 字段的结构描述
- 用于将外部数据源接入 Trantor 体系
分库分表
分库
分表
- 分表概述
自 Trantor 0.4.6-SNAPSHOT 版本开始,可以在@TModel 注解中声明 partition 参数(比如:partition = @Partition(enabled=true, field = model.field))进行分表定义,以满足大数据量业务场景需求。
分表字段
- 必填字段:取值不能为空
- 字段类型:Long/Integer 型,或者 String 类型但可以截取子串并转为 Long/Integer
- 注意事项
将分表字段作为查询条件,以避免全表扫描