Several Mapping Methods of Subqueries

Ask Kong Yiji how many ways to write the Chinese character, and he will tell you that there are four.

If you want to ask me how many mapping methods for sub-query statements, I will list three or five for you.

business background

For example, we have such a menu table, each menu record has a parentId that points to the id of its parent menu, and the Entity object is defined as follows:

@Table(name = "menu")
public class MenuEntity {
    @Id
    @GeneratedValue
    private Integer id;
    private Integer parentId;
    private String menuName;
    // getters and setters
}
复制代码

Now we want to query all parent menus using this SQL:

SELECT * FROM menu WHERE id IN (SELECT parent_id FROM menu)
复制代码

So how does this subquery map by fields?

Method 1: @QueryField

Directly use the general annotation @QueryFieldto define the query statement in the annotation as it is.

public class MenuQuery extends PageQuery {
    @QueryField(and = "id IN (SELECT parent_id FROM menu)")
    private boolean onlyParent;
}
复制代码

When the onlyParentfield is assigned trueas , the corresponding query statement can be obtained from the annotation as

id IN (SELECT parent_id FROM menu)
复制代码

Concatenate SELECTthe sentences to get:

SELECT * FROM menu WHERE id IN (SELECT parent_id FROM menu)
复制代码

Method 2: @SubQuery

Let's do some optimization next, extract some variables of the subquery statement, and define a new annotation @SubQuery:

@Target(FIELD)
@Retention(RUNTIME)
public @interface SubQuery {
    String column() default "id";
    String op() default "IN";
    String select();
    String from();
}
复制代码

Then use the @SubQueryannotation to redefine the field:

public class MenuQuery extends PageQuery {
    @SubQuery(select = "parent_id", from = "menu")
    private boolean onlyParent;
}
复制代码

@SubQueryThe format of the subquery statement corresponding to the annotation is:

#{column} #{op} (SELECT #{select} FROM #{from})
复制代码

Substitute the value assigned when defining the annotation id, IN, parent_id, to get menu:

id IN (SELECT parent_id FROM menu)
复制代码

Method 3: @NestedQueries/@NestedQuery

Subqueries often require multiple levels of nesting, one @SubQueryis usually not enough, and Java annotations do not support self-nesting, so we need to define two annotations @NestedQueriesand @NestedQuery, and use arrays to express nesting logic.


@Target({})
public @interface NestedQuery {
    String select();
    String from();
    /**
     * Will use next @NestedQuery.select() as column if empty.
     *
     * @return custom column for next nested query.
     */
    String where() default "";
    String op() default "IN";
}

@Target(FIELD)
@Retention(RUNTIME)
public @interface NestedQueries {
    String column() default "id";
    String op() default "IN";
    NestedQuery[] value();
    boolean appendWhere() default true;
}
复制代码

@NestedQueriesThe format of the subquery statement corresponding to the annotation is:

#{column} #{op} (
  SELECT #{select} FROM #{from} [#{where} #{op} (
     SELECT #{select} FROM #{from} ...)]
)
复制代码

Use instead @NestedQueriesto define subqueries

public class MenuQuery extends PageQuery {
    @NestedQueries({
            @NestedQuery(select = "parent_id", from = "menu")
    })
    private boolean onlyParent;
}
复制代码

The same can be obtained:

id IN (SELECT parent_id FROM menu)
复制代码

Expansion 1: Add query conditions for the last subquery

Suppose I want to query its parent menu by menu name, the SQL statement can be like this:

SELECT * FROM menu WHERE id IN (SELECT parent_id FROM menu WHERE menu_name = ?)
复制代码

We just need to change the booleantype onlyParentfield to Stringthe type menuName.

public class MenuQuery extends PageQuery {
    @NestedQueries({
            @NestedQuery(select = "parent_id", from = "menu")
    })
    private String menuName;
}
复制代码

但是这里用于子查询定义的menuName会和普通查询的menuName字段冲突,于是有:

拓展二:将子查询的字段定义为Query对象

MenuQuery中将@NestedQueries注解到新添加的MenuQuery类型的menu字段上用于子查询的映射,再添加一个String类型的menuName,用于子查询的查询条件。

public class MenuQuery extends PageQuery {
    @NestedQueries({
            @NestedQuery(select = "parent_id", from = "menu")
    })
    private MenuQuery menu;
    
    private String menuName;
}
复制代码

再这样构造一个MenuQuery对象即可:

MenuQuery parentBySubMenu = MenuQuery.builder().menu(MenuQuery.builder().menuName("test").build()).build();
复制代码

解答

现在回到《基于查询对象字段的后缀推导》这篇文章最后提出的问题,这种复杂的嵌套查询如何映射呢?

SELECT * FROM t_perm WHERE id IN (
    SELECT permId FROM t_role_and_perm WHERE roleId IN (
        SELECT roleId FROM t_user_and_role WHERE userId IN (
            SELECT id FROM t_user WHERE username = ?
)))
复制代码

根据上面所讲的注解和方法,对应的查询字段可以定义如下:

public class PermQuery extends PageQuery {
    @NestedQueries({
            @NestedQuery(select = "permId", from = "t_role_and_perm"),
            @NestedQuery(select = "roleId", from = "t_user_and_role", where = "userId"),
            @NestedQuery(select = "id", from = "t_user")
    })
    private String username;
}
复制代码

小结

本篇主要讲解了子查询映射的优化过程,目前的最佳方案为使用@NestedQueries注解以实现子查询的映射,字段的类型可以选择boolean类型,普通类型或者Query类型。

Guess you like

Origin juejin.im/post/7079638411891441677