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 @QueryField
to 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 onlyParent
field is assigned true
as , the corresponding query statement can be obtained from the annotation as
id IN (SELECT parent_id FROM menu)
复制代码
Concatenate SELECT
the 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 @SubQuery
annotation to redefine the field:
public class MenuQuery extends PageQuery {
@SubQuery(select = "parent_id", from = "menu")
private boolean onlyParent;
}
复制代码
@SubQuery
The 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 @SubQuery
is usually not enough, and Java annotations do not support self-nesting, so we need to define two annotations @NestedQueries
and @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;
}
复制代码
@NestedQueries
The format of the subquery statement corresponding to the annotation is:
#{column} #{op} (
SELECT #{select} FROM #{from} [#{where} #{op} (
SELECT #{select} FROM #{from} ...)]
)
复制代码
Use instead @NestedQueries
to 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 boolean
type onlyParent
field to String
the 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
类型。