Несколько методов сопоставления подзапросов

Спросите Конга Иджи, сколько способов написать китайский иероглиф, и он скажет вам, что их четыре.

Если вы хотите спросить меня, сколько методов сопоставления для операторов подзапроса, я перечислю для вас три или пять.

бизнес фон

Например, у нас есть такая таблица меню, каждая запись меню имеет parentId, указывающий на идентификатор ее родительского меню, а объект Entity определяется следующим образом:

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

Теперь мы хотим запросить все родительские меню, используя этот SQL:

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

Так как же этот подзапрос сопоставляется по полям?

Способ 1: @QueryField

Непосредственно используйте общую аннотацию @QueryField, чтобы определить оператор запроса в аннотации как есть.

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

Когда onlyParentполе назначено trueкак , соответствующий оператор запроса может быть получен из аннотации как

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

Соедините SELECTпредложения, чтобы получить:

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

Способ 2: @SubQuery

Далее давайте проведем некоторую оптимизацию, извлечем некоторые переменные оператора подзапроса и определим новую аннотацию @SubQuery:

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

Затем используйте @SubQueryаннотацию, чтобы переопределить поле:

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

@SubQueryФормат оператора подзапроса, соответствующего аннотации:

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

Подставьте значение, присвоенное при определении аннотации id, IN, parent_id, чтобы получить menu:

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

Способ 3: @NestedQueries/@NestedQuery

Подзапросы часто требуют нескольких уровней вложенности, одного @SubQueryобычно недостаточно, а аннотации Java не поддерживают самовложенность, поэтому нам нужно определить две аннотации @NestedQueriesи @NestedQuery, а для выражения логики вложенности использовать массивы.


@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;
}
复制代码

@NestedQueriesФормат оператора подзапроса, соответствующего аннотации:

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

Используйте вместо этого @NestedQueriesдля определения подзапросов

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

Так же можно получить:

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

Расширение 1: добавьте условия запроса для последнего подзапроса

Предположим, я хочу запросить его родительское меню по имени меню, оператор SQL может быть таким:

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

Нам просто нужно изменить поле booleantype на type .onlyParentStringmenuName

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类型。

рекомендация

отjuejin.im/post/7079638411891441677