Спросите Конга Иджи, сколько способов написать китайский иероглиф, и он скажет вам, что их четыре.
Если вы хотите спросить меня, сколько методов сопоставления для операторов подзапроса, я перечислю для вас три или пять.
бизнес фон
Например, у нас есть такая таблица меню, каждая запись меню имеет 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 = ?)
复制代码
Нам просто нужно изменить поле boolean
type на type .onlyParent
String
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
类型。