从树构建 JPA 规范
Build JPA Specification from tree
我创建了一个 API,它允许用户使用树构建查询。该树是从 SearchOperationRequest
class.
构建的
@Data
@ApiModel(value = "SearchOperationRequest", description = "Condition for the query")
public class SearchOperationRequest {
@ApiModelProperty(value = "Conditional statement for the where clause", allowableValues = "EQUALS, NOT_EQUALS, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS")
private SearchConditionOperation condition;
@ApiModelProperty(value = "Column name to be searched on")
private String column;
@ApiModelProperty(value = "Value of column")
private Object value;
@ApiModelProperty(value = "Value of OR / AND")
private SearchComparator comparator;
@ApiModelProperty(value = "Left node of comparator condition")
private SearchOperationRequest left;
@ApiModelProperty(value = "Right node of comparator condition")
private SearchOperationRequest right;
public boolean isTreeLeaf() {
return left == null && right == null;
}
public boolean isComparator() {
return comparator != null;
}
}
所以从这个例子我可以创建一个 SearchOperationRequest
要求所有 WHERE hidden = false AND X = 88
"searchOperation": {
"left": {
"column": "Hidden",
"condition": "EQUALS",
"value": false
},
"comparator": "AND",
"right": {
"left": {
"column": "X",
"condition": "EQUALS",
"value": 88
},
"comparator": "AND"
}
}
此请求是使用通用规范生成器构建到规范中的。
public class GenericSpecificationsBuilder<U> {
public Specification<U> buildSpecificationFromSearchOperationRequest(SearchOperationRequest root, Function<SpecificationSearchCriteria, Specification<U>> converter) {
Stack<SearchOperationRequest> stack = new Stack<>();
Stack<SearchOperationRequest> comparatorStack = new Stack<>();
Deque<Specification<U>> specStack = new LinkedList<>();
SearchOperationRequest pointer = root;
while (pointer != null || !stack.empty()) {
if (pointer.isTreeLeaf()) {
specStack.push(converter.apply(new SpecificationSearchCriteria(pointer.getColumn(), pointer.getCondition(), pointer.getValue())));
}
if (pointer.isComparator()) {
comparatorStack.push(pointer);
}
if (pointer.getRight() != null) {
stack.push(pointer.getRight());
}
if (pointer.getLeft() != null) {
pointer.setRight(pointer.getLeft());
pointer.setLeft(null);
} else if (!stack.empty()) {
SearchOperationRequest temp = stack.pop();
pointer.setRight(temp);
}
pointer = pointer.getRight();
}
while (specStack.size() <= comparatorStack.size()) {
comparatorStack.pop();
}
while (!comparatorStack.empty()) {
SearchOperationRequest searchOperationRequest = comparatorStack.pop();
Specification<U> operand1 = specStack.pop();
Specification<U> operand2 = specStack.pop();
if (searchOperationRequest.getComparator().equals(SearchComparator.AND)) {
specStack.push(Specification.where(operand1)
.and(operand2));
} else if (searchOperationRequest.getComparator().equals(SearchComparator.OR)) {
specStack.push(Specification.where(operand1)
.or(operand2));
}
}
return specStack.pop();
}
}
我的电流非常适合右重树。含义查询,例如:
WHERE X = 6 AND X = 9
WHERE Z = 5 OR T=9
WHERE Z = 5 OR T=9 OR H=6
但它不适用于构建更复杂的树,其中大括号中的条件应优先并首先执行。
WHERE (X = 6 OR Z = 9) AND (T=6 OR H=8)
这个更复杂 SearchOperationRequest
的模型是:
"searchOperation": {
"left": {
"left": {
"column": "X",
"condition": "EQUALS",
"value": 6
},
"comparator": "AND",
"right": {
"column": "Z",
"condition": "EQUALS",
"value": 9
}
},
"comparator": "AND",
"right": {
"left": {
"column": "T",
"condition": "EQUALS",
"value": 6
},
"comparator": "AND",
"right": {
"column": "H",
"condition": "EQUALS",
"value": 8
}
}
}
如何修改我的 GenericSpecificationsBuilder
以便能够处理更复杂的 SearchOperationRequest
树?
让我们使用您的示例树来跟踪执行流程。
AND
/ \
leftOR rightOR
/ \ / \
X=6 Z=9 T=6 H=8
当我们退出第一个 while
循环时,我们的堆栈看起来像:
stack = {}
comparatorStack = { AND, leftOR, rightOR }
specStack = { X=6, Z=9, T=6, H=8 }
同样的状态进入最后的while
循环。
while (!comparatorStack.empty()) {
SearchOperationRequest searchOperationRequest = comparatorStack.pop();
Specification<U> operand1 = specStack.pop();
Specification<U> operand2 = specStack.pop();
if (searchOperationRequest.getComparator().equals(SearchComparator.AND)) {
specStack.push(Specification.where(operand1)
.and(operand2));
} else if (searchOperationRequest.getComparator().equals(SearchComparator.OR)) {
specStack.push(Specification.where(operand1)
.or(operand2));
}
}
这里的问题是您将结果推回到 specStack
。因此,在第二次迭代中,您将弹出第一次迭代的结果(rightOR
)以及 Z=9
,并对它应用 leftOr
逻辑。
分解树
让我们退后一步,看看您是如何分解树的,更具体地说:
if (pointer.getLeft() != null) {
pointer.setRight(pointer.getLeft());
pointer.setLeft(null);
} else if (!stack.empty()) {
SearchOperationRequest temp = stack.pop();
pointer.setRight(temp);
}
此代码的问题在于您正在更改树中的节点。在第一个例子中,有一次我们的指针指向节点:
Z=9
/ \
null rightOR
这看起来不对。而不是使用堆栈 (Depth First Search), you could use a queue (Breadth First Search) 分解树并免费获得您想要的顺序。
这是否解决了将每个逻辑运算符 (comparator
) 应用于正确操作数的问题?不完全是,为了能够解决下面的两种布局,我们可以在不同的工作流程中分解运算符和操作数,而不是将它们全部分解在一起。
AND | rootAND
/ \ | / \
leftOR rightOR | leftOR rightOR
/ \ / \ | / \ / \
X=6 Z=9 T=6 H=8 | X=6 AND Z=9 H=8
| / \
| T=6 Y=3
解决方案
您的 post 中第一个类似于 json 的表示具有不合逻辑的布局,因为逻辑运算符需要对左右操作数进行运算。相反,你有:
"right": {
"left": {
"column": "X",
"condition": "EQUALS",
"value": 88
},
"comparator": "AND"
}
让我们考虑一种对称表示的解决方案,其中每个逻辑运算符都存在左右操作数。
首先我们逐层处理树广度优先。同时,我们将每个 comparator
放入堆栈,因此我们在第二个 while
循环中首先取出最后一个。
在第二个循环中,我们使用新的 Queue
来存储我们的 'in-between-results',因为我们返回根目录。
Queue<SearchOperationRequest> queue = new LinkedList<>();
Deque<SearchOperationRequest> comparatorStack = new LinkedList<>();
if (root == null || !root.isComparator()) return;
queue.add(root);
while(!queue.isEmpty()){
SearchOperationRequest node = queue.poll();
comparatorStack.push(node);
if(node.left != null && node.left.isComparator()) queue.add(node.left);
if(node.right != null && node.right.isComparator()) queue.add(node.right);
}
Queue<Specification> specQueue = new LinkedList<>();
while(!comparatorStack.isEmpty()){
SearchOperationRequest comparator = comparatorStack.pop();
// reverse operand order so already computed values are polled correctly
Specification operand2;
SearchOperationRequest pointer = comparator.getRight();
if(pointer.isTreeLeaf()) {
operand2 = converter.apply(new SpecificationSearchCriteria(pointer.getColumn(), pointer.getCondition(), pointer.getValue()));
} else {
operand2 = specQueue.poll();
}
Specification operand1;
pointer = comparator.getLeft();
if(pointer.isTreeLeaf()) {
operand1 = converter.apply(new SpecificationSearchCriteria(pointer.getColumn(), pointer.getCondition(), pointer.getValue()));
} else {
operand1 = specQueue.poll();
}
if (comparator.equals(SearchComparator.AND)) {
specQueue.add(Specification.where(operand1).and(operand2));
} else if (comparator.equals(SearchComparator.OR)) {
specQueue.add(Specification.where(operand1).or(operand2));
}
}
return specQueue.poll();
我没有测试代码,但您应该能够提取(和重构)工作流。
我创建了一个 API,它允许用户使用树构建查询。该树是从 SearchOperationRequest
class.
@Data
@ApiModel(value = "SearchOperationRequest", description = "Condition for the query")
public class SearchOperationRequest {
@ApiModelProperty(value = "Conditional statement for the where clause", allowableValues = "EQUALS, NOT_EQUALS, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS")
private SearchConditionOperation condition;
@ApiModelProperty(value = "Column name to be searched on")
private String column;
@ApiModelProperty(value = "Value of column")
private Object value;
@ApiModelProperty(value = "Value of OR / AND")
private SearchComparator comparator;
@ApiModelProperty(value = "Left node of comparator condition")
private SearchOperationRequest left;
@ApiModelProperty(value = "Right node of comparator condition")
private SearchOperationRequest right;
public boolean isTreeLeaf() {
return left == null && right == null;
}
public boolean isComparator() {
return comparator != null;
}
}
所以从这个例子我可以创建一个 SearchOperationRequest
要求所有 WHERE hidden = false AND X = 88
"searchOperation": {
"left": {
"column": "Hidden",
"condition": "EQUALS",
"value": false
},
"comparator": "AND",
"right": {
"left": {
"column": "X",
"condition": "EQUALS",
"value": 88
},
"comparator": "AND"
}
}
此请求是使用通用规范生成器构建到规范中的。
public class GenericSpecificationsBuilder<U> {
public Specification<U> buildSpecificationFromSearchOperationRequest(SearchOperationRequest root, Function<SpecificationSearchCriteria, Specification<U>> converter) {
Stack<SearchOperationRequest> stack = new Stack<>();
Stack<SearchOperationRequest> comparatorStack = new Stack<>();
Deque<Specification<U>> specStack = new LinkedList<>();
SearchOperationRequest pointer = root;
while (pointer != null || !stack.empty()) {
if (pointer.isTreeLeaf()) {
specStack.push(converter.apply(new SpecificationSearchCriteria(pointer.getColumn(), pointer.getCondition(), pointer.getValue())));
}
if (pointer.isComparator()) {
comparatorStack.push(pointer);
}
if (pointer.getRight() != null) {
stack.push(pointer.getRight());
}
if (pointer.getLeft() != null) {
pointer.setRight(pointer.getLeft());
pointer.setLeft(null);
} else if (!stack.empty()) {
SearchOperationRequest temp = stack.pop();
pointer.setRight(temp);
}
pointer = pointer.getRight();
}
while (specStack.size() <= comparatorStack.size()) {
comparatorStack.pop();
}
while (!comparatorStack.empty()) {
SearchOperationRequest searchOperationRequest = comparatorStack.pop();
Specification<U> operand1 = specStack.pop();
Specification<U> operand2 = specStack.pop();
if (searchOperationRequest.getComparator().equals(SearchComparator.AND)) {
specStack.push(Specification.where(operand1)
.and(operand2));
} else if (searchOperationRequest.getComparator().equals(SearchComparator.OR)) {
specStack.push(Specification.where(operand1)
.or(operand2));
}
}
return specStack.pop();
}
}
我的电流非常适合右重树。含义查询,例如:
WHERE X = 6 AND X = 9
WHERE Z = 5 OR T=9
WHERE Z = 5 OR T=9 OR H=6
但它不适用于构建更复杂的树,其中大括号中的条件应优先并首先执行。
WHERE (X = 6 OR Z = 9) AND (T=6 OR H=8)
这个更复杂 SearchOperationRequest
的模型是:
"searchOperation": {
"left": {
"left": {
"column": "X",
"condition": "EQUALS",
"value": 6
},
"comparator": "AND",
"right": {
"column": "Z",
"condition": "EQUALS",
"value": 9
}
},
"comparator": "AND",
"right": {
"left": {
"column": "T",
"condition": "EQUALS",
"value": 6
},
"comparator": "AND",
"right": {
"column": "H",
"condition": "EQUALS",
"value": 8
}
}
}
如何修改我的 GenericSpecificationsBuilder
以便能够处理更复杂的 SearchOperationRequest
树?
让我们使用您的示例树来跟踪执行流程。
AND
/ \
leftOR rightOR
/ \ / \
X=6 Z=9 T=6 H=8
当我们退出第一个 while
循环时,我们的堆栈看起来像:
stack = {}
comparatorStack = { AND, leftOR, rightOR }
specStack = { X=6, Z=9, T=6, H=8 }
同样的状态进入最后的while
循环。
while (!comparatorStack.empty()) {
SearchOperationRequest searchOperationRequest = comparatorStack.pop();
Specification<U> operand1 = specStack.pop();
Specification<U> operand2 = specStack.pop();
if (searchOperationRequest.getComparator().equals(SearchComparator.AND)) {
specStack.push(Specification.where(operand1)
.and(operand2));
} else if (searchOperationRequest.getComparator().equals(SearchComparator.OR)) {
specStack.push(Specification.where(operand1)
.or(operand2));
}
}
这里的问题是您将结果推回到 specStack
。因此,在第二次迭代中,您将弹出第一次迭代的结果(rightOR
)以及 Z=9
,并对它应用 leftOr
逻辑。
分解树
让我们退后一步,看看您是如何分解树的,更具体地说:
if (pointer.getLeft() != null) {
pointer.setRight(pointer.getLeft());
pointer.setLeft(null);
} else if (!stack.empty()) {
SearchOperationRequest temp = stack.pop();
pointer.setRight(temp);
}
此代码的问题在于您正在更改树中的节点。在第一个例子中,有一次我们的指针指向节点:
Z=9
/ \
null rightOR
这看起来不对。而不是使用堆栈 (Depth First Search), you could use a queue (Breadth First Search) 分解树并免费获得您想要的顺序。
这是否解决了将每个逻辑运算符 (comparator
) 应用于正确操作数的问题?不完全是,为了能够解决下面的两种布局,我们可以在不同的工作流程中分解运算符和操作数,而不是将它们全部分解在一起。
AND | rootAND
/ \ | / \
leftOR rightOR | leftOR rightOR
/ \ / \ | / \ / \
X=6 Z=9 T=6 H=8 | X=6 AND Z=9 H=8
| / \
| T=6 Y=3
解决方案
您的 post 中第一个类似于 json 的表示具有不合逻辑的布局,因为逻辑运算符需要对左右操作数进行运算。相反,你有:
"right": {
"left": {
"column": "X",
"condition": "EQUALS",
"value": 88
},
"comparator": "AND"
}
让我们考虑一种对称表示的解决方案,其中每个逻辑运算符都存在左右操作数。
首先我们逐层处理树广度优先。同时,我们将每个 comparator
放入堆栈,因此我们在第二个 while
循环中首先取出最后一个。
在第二个循环中,我们使用新的 Queue
来存储我们的 'in-between-results',因为我们返回根目录。
Queue<SearchOperationRequest> queue = new LinkedList<>();
Deque<SearchOperationRequest> comparatorStack = new LinkedList<>();
if (root == null || !root.isComparator()) return;
queue.add(root);
while(!queue.isEmpty()){
SearchOperationRequest node = queue.poll();
comparatorStack.push(node);
if(node.left != null && node.left.isComparator()) queue.add(node.left);
if(node.right != null && node.right.isComparator()) queue.add(node.right);
}
Queue<Specification> specQueue = new LinkedList<>();
while(!comparatorStack.isEmpty()){
SearchOperationRequest comparator = comparatorStack.pop();
// reverse operand order so already computed values are polled correctly
Specification operand2;
SearchOperationRequest pointer = comparator.getRight();
if(pointer.isTreeLeaf()) {
operand2 = converter.apply(new SpecificationSearchCriteria(pointer.getColumn(), pointer.getCondition(), pointer.getValue()));
} else {
operand2 = specQueue.poll();
}
Specification operand1;
pointer = comparator.getLeft();
if(pointer.isTreeLeaf()) {
operand1 = converter.apply(new SpecificationSearchCriteria(pointer.getColumn(), pointer.getCondition(), pointer.getValue()));
} else {
operand1 = specQueue.poll();
}
if (comparator.equals(SearchComparator.AND)) {
specQueue.add(Specification.where(operand1).and(operand2));
} else if (comparator.equals(SearchComparator.OR)) {
specQueue.add(Specification.where(operand1).or(operand2));
}
}
return specQueue.poll();
我没有测试代码,但您应该能够提取(和重构)工作流。