跳转到内容

查询模型(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();
}
}

pageorder 是常用查询参数,这里不详细展开了。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;
}
}

QLongQString 等类型都是基于该类型的衍生,确定了值的类型,暂时没有添加其他特殊 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 itemCode
Select select = new Select();
select.addField(ItemBO.itemCode_field);
params.setSelect(select);
// order by createdAt desc
params.setOrder(new Order(ItemBO.createdAt_field, false));
// limit 0,20
params.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);
}