查询模型(Query Model)
如需了解查询模型详细描述请查看:查询模型开发文档
特性
- 查询模型是在编译期与 Trantor 模型
1:1自动生成出来,无需手动编写。也因此不可修改或增加其中的字段或方法。 - 查询模型是一种在现有机制下的衍生产物,只为查询行为服务。虽然查询模型不是 Trantor 中的模型(特指持久化模型和瞬时模型),但他也可以作为 Function 和 Extension 入参和出参。
- 查询模型会跟随 Trantor 一起被拓展字段,由机制保证。
- 查询模型包含查询字段、查询参数和查询条件的信息,可以手动使用该对象组装 SQL,也可以使用 Trantor 提供的 API 根据查询模型对象值推断查询 SQL(使用方便,性能会稍差,按需使用)。
核心特性
以商品模型 ItemBO 为例
@Data@Model(name = "Item business model", mainField = ItemBO.itemName_field)public class ItemBO extends BaseModel<Long> {
@Field(name = "Item code", nullable = false) private String itemCode;
@Field(name = "Item name", nullable = false) private String itemName;
@Field(name = "Item type", type = FieldType.MultiDictionary) @DictionaryMeta(ItemTypeDict.class) private List<String> itemType;
@Field(name = "Item description") private String itemDesc;
@ImageMeta @Field(name = "Item image", type = FieldType.Image) private String itemImage;
@Field(name = "Item price", nullable = false, type = FieldType.Currency) @CurrencyMeta(intDigits = 12, decimalDigits = 4) private Currency itemPrice;
// 通过 @LinkMeta 声明与其他模型实体的关联关系 @Field(name = "item category", nullable = false) @LinkMeta private ItemCategoryBO categoryBO;}在程序编译后生成对应的查询模型:
/** * Generated for {@link io.terminus.trantor.example.model.business.ItemBO} by Trantor. * Don't modify it. */@Generated("io.terminus.trantorframework")public class QItemBO extends QModel<ItemBO> {
private QLongId id; private QInteger _version; private QDate createdAt; private QDate updatedAt; private QUser createdBy; private QUser updatedBy; private QString itemCode; private QString itemName; private QType<List> itemType; private QString itemDesc; private QString itemImage; private QType<Currency> itemPrice; private QItemCategoryBO categoryBO;
public QLongId getId() { return this.id; } public void setId(QLongId id) { this.id = id; __hasFields.add("id"); } public QInteger get_version() { return this._version; } public void set_version(QInteger _version) { this._version = _version; __hasFields.add("_version"); } public QDate getCreatedAt() { return this.createdAt; } public void setCreatedAt(QDate createdAt) { this.createdAt = createdAt; __hasFields.add("createdAt"); } public QDate getUpdatedAt() { return this.updatedAt; } public void setUpdatedAt(QDate updatedAt) { this.updatedAt = updatedAt; __hasFields.add("updatedAt"); } public QUser getCreatedBy() { return this.createdBy; } public void setCreatedBy(QUser createdBy) { this.createdBy = createdBy; __hasFields.add("createdBy"); } public QUser getUpdatedBy() { return this.updatedBy; } public void setUpdatedBy(QUser updatedBy) { this.updatedBy = updatedBy; __hasFields.add("updatedBy"); } public QString getItemCode() { return this.itemCode; } public void setItemCode(QString itemCode) { this.itemCode = itemCode; __hasFields.add("itemCode"); } public QString getItemName() { return this.itemName; } public void setItemName(QString itemName) { this.itemName = itemName; __hasFields.add("itemName"); } public QType<List> getItemType() { return this.itemType; } public void setItemType(QType<List> itemType) { this.itemType = itemType; __hasFields.add("itemType"); } public QString getItemDesc() { return this.itemDesc; } public void setItemDesc(QString itemDesc) { this.itemDesc = itemDesc; __hasFields.add("itemDesc"); } public QString getItemImage() { return this.itemImage; } public void setItemImage(QString itemImage) { this.itemImage = itemImage; __hasFields.add("itemImage"); } public QType<Currency> getItemPrice() { return this.itemPrice; } public void setItemPrice(QType<Currency> itemPrice) { this.itemPrice = itemPrice; __hasFields.add("itemPrice"); } public QItemCategoryBO getCategoryBO() { return this.categoryBO; } public void setCategoryBO(QItemCategoryBO categoryBO) { this.categoryBO = categoryBO; __hasFields.add("categoryBO"); }}首先,生成的查询模型会继承自 QModel:
public abstract class QModel<T> implements QElement { private static final long serialVersionUID = 7982423966576450161L; protected Set<String> __hasFields = new HashSet<>(); protected QParams queryParams = new QParams(); protected Map<String, QElement> __extendFields = new LinkedHashMap<>(); @JsonIgnore protected Class<?> __modelClass;
public QModel() { Type superClass = this.getClass().getGenericSuperclass(); if (!(superClass instanceof Class)) { Type[] types = ((ParameterizedType) superClass).getActualTypeArguments(); this.__modelClass = (Class<?>) types[0]; } }
@SuppressWarnings("unchecked") public <V extends QElement> V get(String fieldName) { if (__extendFields.isEmpty() || !__hasFields.contains(fieldName)) return null; return (V) __extendFields.get(fieldName); }
@JsonAnySetter public void set(String fieldName, Object value) { QModelFieldTypeConvertor convertor = QModelFieldTypeConvertor.instance(); if (convertor != null) { value = convertor.convert(this, fieldName, value); } if (EMPTY_RES.equals(value)) { return; } __extendFields.put(fieldName, (QElement) value); __hasFields.add(fieldName); }
@Override public boolean valueIsNull() { return searchIsNull() && queryParamsConditionIsNull(); }
public boolean searchIsNull() { if (__hasFields == null) return true; return __hasFields.isEmpty(); }
public boolean queryParamsConditionIsNull() { if (queryParams == null) return true; return queryParams.conditionValueIsEmpty(); }
public boolean hasField(String name) { if (__hasFields == null) return false; return __hasFields.contains(name); }
public QParams getQueryParams() { return queryParams; }
public void setQueryParams(QParams queryParams) { this.queryParams = queryParams; }
@JsonAnyGetter public Map<String, QElement> get__extendFields() { return __extendFields; }
public void set__extendFields(Map<String, QElement> __extendFields) { this.__extendFields = __extendFields; }
public Set<String> get__hasFields() { return __hasFields; }
public Class<?> get__modelClass() { return __modelClass; }
public void set__modelClass(Class<?> __modelClass) { this.__modelClass = __modelClass; }}其中 queryParams 字段包含了查询相关的参数:
public class QParams implements Serializable { public static final QParams EMPTY = new QParams(); private static final long serialVersionUID = -5548303957573982561L; private Select select; private Page page = new Page(); private Order order; private String fuzzyValue;
public Select getSelect() { return select; }
public void setSelect(Select select) { this.select = select; }
public Page getPage() { return page; }
public void setPage(Page page) { this.page = page; }
public Order getOrder() { return order; }
public void setOrder(Order order) { this.order = order; }
public String getFuzzyValue() { return fuzzyValue; }
public void setFuzzyValue(String fuzzyValue) { this.fuzzyValue = fuzzyValue; }
public boolean conditionValueIsEmpty() { return this.fuzzyValue == null || this.fuzzyValue.isEmpty(); }}page 和 order 是常用查询参数,这里不详细展开了。select 字段描述了本次需要查询的目标字段,在调用 Trantor 的查询模型 API 时,如果该字段值为 null,则自动会查询源模型所有一级字段,类似 select *。
另外,查询模型中的字段也会与源模型的字段一一对应,其中字段的基本类型为 QType,包装了可能的查询类型,比如单值,多值和区间:
public class QType<T> extends QValue implements OneValueProvider<T>, CollectionValueProvider<T>, RangeValueProvider<T> { private static final long serialVersionUID = -9136159894524085139L;
private T value; private Collection<T> values; private RangeValue<T> rangeValue;
public QType() { }
public QType(T value) { this(value, false); }
public QType(T value, boolean fullMatch) { this.type = Type.One; this.value = value; this.fullMatch = fullMatch; }
public QType(Collection<T> values) { this.type = Type.Collection; this.values = values; }
public QType(T startValue, T endValue) { this.type = Type.Range; this.rangeValue = new RangeValue<>(startValue, endValue); }
@Override public boolean valueIsNull() { return value == null && values == null && (rangeValue == null || rangeValue.valueIsNull()); }
@SuppressWarnings("unchecked") @Override public <V> V get() { switch (this.type) { case One: return (V) this.value; case Collection: return (V) this.values; case Range: return (V) this.rangeValue; default: throw new IllegalStateException("value type not found."); } }
@Override public T getValue() { return value; }
public void setValue(T value) { if (this.type == null) { this.type = Type.One; } this.value = value; }
@Override public Collection<T> getValues() { return values; }
public void setValues(Collection<T> values) { if (this.type == null) { this.type = Type.Collection; } this.values = values; }
@Override public RangeValue<T> getRangeValue() { return rangeValue; }
public void setRangeValue(RangeValue<T> rangeValue) { if (this.type == null) { this.type = Type.Range; } this.rangeValue = rangeValue; }}QLong、QString 等类型都是基于该类型的衍生,确定了值的类型,暂时没有添加其他特殊 API。
直接使用查询模型查询数据:
@FunctionImpl(name = "Query item data func implement")public class QueryItemFuncImpl implements QueryItemFunc {
@Override public List<ItemBO> execute(QItemBO qItemBO) { // select Select select = new Select(); select.addField(ItemBO.id_field); select.addField(ItemBO.itemName_field); select.addField(ItemBO.itemCode_field); select.addField(ItemBO.categoryBO_field , new Select.Field(ItemCategoryBO.id_field) , new Select.Field(ItemCategoryBO.name_field)); qItemBO.getQueryParams().setSelect(select); return DS.findAll(qItemBO); }}使用查询模型对象传递查询条件:
@FunctionImpl(name = "Query item data func implement")public class QueryItemFuncImpl implements QueryItemFunc {
@Override public ItemBO execute(QItemBO qItemBO) { Long id = qItemBO.getId().get(); return DS.findById(ItemBO.class, id); }}运行原理
手动使用查询模型构建 SQL 应该比较好理解,这里主要以自动推断查询为例。
当调用 DS.findAll(QModel<Model> queryModel) 时,会根据传入的查询模型中的 QType 字段推断查询条件,推断遵循以下几个原则:
- 如果字段中的 type 是 One,即为单值时:
- 如果字段类型为文本:
- 当
fullMatch = false时,推断为like; - 当
fullMatch = true时,推断为=。
- 当
- 其余类型则为
=。
- 如果字段类型为文本:
- 如果字段中的 type 是 Collection,则推断为
in。 - 如果字段中的 type 是 Range,则推断为
field >= xxx and field <= xxx。
值则根据 type 类型读取对应的字段获取:
- type = One -> value;
- type = Collection -> values;
- type = Range -> rangeValues(对象内部包含 start 和 end 的值)。
按上述的 ItemBO 例子,如果构建一个 QItemBO :
QItemBO qItemBO = new QItemBO();// where id in (?) and itemName like '%?%'QLongId ids = new QLongId(Arrays.asList(1L,2L,3L));qItemBO.setId(ids);QString itemName = new QString("Apple");qItemBO.setItemName(itemName);
QParams params = new QParams();// select itemCodeSelect select = new Select();select.addField(ItemBO.itemCode_field);params.setSelect(select);// order by createdAt descparams.setOrder(new Order(ItemBO.createdAt_field, false));// limit 0,20params.setPage(new Page(1, 20));qItemBO.setQueryParams(params);根据上面构建的查询模型对象执行查询操作:
List<ItemBO> itemBOList = DS.findAll(qItemBO);生成的 SQL :
select itemCode from base_ItemBO where id in (1,2,3) and itemName like '%Apple%' order by createdAt desc limit 0,20;使用原则
使用查询模型应遵循以下几个原则:
-
前端视图获取数据时,优先使用
dataCondition,如果需要手写查询,首先内部评审必要性(前期最好 Trantor 团队参与)。 -
当
dataCondition支持不了的场景,才手写function,使用查询模型作为参数。 -
在后端相互调用时,想复用一些复杂查询的逻辑,可以使用查询模型作为
function的入参。 -
后端内部逻辑,比如按公司名称模糊检索公司,可以用查询模型作为
function的入参,但是类似这种明确的场景,尽量不要使用DS.findAll(QueryModel queryModel)的方法,直接用明确T-SQL查询,比如DS里的下列API:public static <Model extends BaseModel<Long>> Model findById(Class<Model> modelClass, Long id, String select);public static <Model extends BaseModel<Long>> Model findOne(Class<Model> modelClass, String select, String where, Object... params);public static <Model extends BaseModel<Long>> List<Model> findAll(Class<Model> modelClass, String select, String where, Object... params);public static <Model extends BaseModel<Long>> Paging<Model> paging(Class<Model> modelClass, String select, String where, Page page, Object... params); -
如无必要,尽可能少使用查询模型。
-
如无必要,尽可能少使用上述
API的使用,因为性能比较差。
使用示例
以 ItemBO 模型生成的 QItemBO 为例来了解如何使用查询模型。
- 按商品名称模糊查询
QItemBO qItemBO1 = new QItemBO();// where itemName like '%Apple%'qItemBO.setItemName(new QString("Apple"));- 使用构造好的查询模型做查询
public List<ItemBO> queryByItemName(QItemBO qItemBO) { String itemName = qItemBO.getItemName().get(); return DS.findAll(ItemBO.class, "*", "itemName like ?", "%" + itemName + "%");}- 或者(如无必要,不推荐使用)
public List<ItemBO> queryByItemName(QItemBO qItemBO) { return DS.findAll(qItemBO);}