跳转到内容

模型定义

@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)
@TModel
public class User extends BaseModel<Long> {
private static final long serialVersionUID = 4034704005484102406L;
@TModelFieldTemplate
private Customer customer;
}
@Data
public 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) 来指定反向关系。

见下面的例子:

@TModel
class 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) 相同来确认两边模型上描述的是同一个关系。

以下是例子:

@TModel
class ModelA extends BaseModel<Long> {
@TModelField
@RelationMeta(name = "relation1")
private ModelB modelB; // OneToOne
@TModelField
@RelationMeta(name = "relation2", modelClass = ModelB.class)
private List<ModelB> modelB2; // ManyToMany
}
@TModel
class 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 体系

分库分表

分库

分表

  1. 分表概述

自 Trantor 0.4.6-SNAPSHOT 版本开始,可以在@TModel 注解中声明 partition 参数(比如:partition = @Partition(enabled=true, field = model.field))进行分表定义,以满足大数据量业务场景需求。

分表字段

  • 必填字段:取值不能为空
  • 字段类型:Long/Integer 型,或者 String 类型但可以截取子串并转为 Long/Integer
  1. 注意事项

将分表字段作为查询条件,以避免全表扫描