概述
我最初使用HQL在spring-security-ui插件中编写了大多数查询,因为我发现它比标准查询更直观,但是HQL仅适用于Hibernate和关系数据库。 拉取请求将查询更新为使用标准查询,以允许该插件与NoSQL数据存储一起使用,但一个查询与我使用的编程风格不匹配。 没什么大不了的,但是由于许多控制器代码基本上都是CRUD代码,并且与其他代码非常相似,因此我尝试使代码保持通用并将共享逻辑推入基类。
原始的HQL包括此
hql.append " AND e.aclObjectIdentity.aclClass.id=:aclClass"
转换后的标准代码为
aclObjectIdentity {
aclClass {
eq 'id', params.long('aclClass')
}
}
整个查询与此类似:
def results = lookupClass().createCriteria().list(max: max, offset: offset) {
// other standard criteria method calls
if (params.aclClass) {
aclObjectIdentity {
aclClass {
eq 'id', params.long('aclClass')
}
}
}
}
这让我开始思考创建一种通用表示两级投影和标准的方法。
如果我们恢复省略的可选括号,代码将变为
aclObjectIdentity({
aclClass({
eq('id', params.long('aclClass'))
})
})
因此,应该更加清楚这是方法调用的序列; 调用aclObjectIdentity
具有关闭的说法,然后aclClass
具有关闭的说法,最后eq
与String
和long
争论。 将闭包拆分为局部变量使它更清晰,首先是
def aclClassClosure = {
eq('id', params.long('aclClass'))
}
aclObjectIdentity({
aclClass(aclClassClosure)
})
然后
def aclClassClosure = {
eq 'id', params.long('aclClass')
}
def aclObjectIdentityClosure = {
aclClass(aclClassClosure)
}
aclObjectIdentity(aclObjectIdentityClosure)
为了更具体一点,可以说我们有三个域类。
Department
:
class Department {
String name
}
Manager
:
class Manager {
String name
Department department
}
和Employee
:
class Employee {
String name
Manager manager
}
我们创建一些实例:
Department d = new Department(name: 'department1').save()
Manager m = new Manager(name: 'manager1', department: d).save()
Employee e1 = new Employee(name: 'employee1', manager: m).save()
Employee e2 = new Employee(name: 'employee2', manager: m).save()
后来想运行查询:
Employee.createCriteria().list(max: 10, offset: 0) {
eq 'name', 'employee1'
manager {
department {
eq 'name', 'department1'
}
}
}
我的目标是仅使用一些辅助方法来表示此查询,并且不使用任何闭包(或尽可能少的闭包)。 像上面一样拆分
def departmentClosure = {
eq 'name', 'department1'
}
def managerClosure = {
department(departmentClosure)
}
def criteriaClosure = {
eq 'name', 'employee1'
manager(managerClosure)
}
Employee.createCriteria().list([max: 10, offset: 0], criteriaClosure)
运行查询时,使用Hibernate时会把criteriaClosure
的委托设置为HibernateCriteriaBuilder
的实例,或者是MongoDB或您使用的任何其他GORM实现的类似构建器。 构建器为eq
定义了方法, like
, between
等,因此,当您在条件关闭中进行这些调用时,它们将在构建器上运行。
事实证明,如果将闭包拆分为多个闭包并以生成器作为每个闭包的委托进行调用,则其工作方式相同。 因此,这样的方法有效:
def runCriteria(Class clazz, List<Closure> criterias, Map paginateParams) {
clazz.createCriteria().list(paginateParams) {
for (Closure criteria in criterias) {
criteria.delegate = delegate
criteria()
}
}
}
这意味着我们可以拆分
Employee.createCriteria().list(max: 10, offset: 0) {
eq 'name', 'employee1'
manager {
department {
eq 'name', 'department1'
}
}
}
进入
def closure1 = {
eq 'name', 'employee1'
}
def closure2 = {
manager {
department {
eq 'name', 'department1'
}
}
}
并运行为
runCriteria Employee, [closure1, closure2], [max: 10, offset: 0]
但是我们如何才能使该投影成为通用? 这是一个内部方法调用,包装在一个或多个闭包中,这些闭包向下投影到另一个域类。
我最终想要的是能够使用内部条件调用指定一个投影而无需关闭:
def projection = buildProjection('manager.department',
'eq', ['name', 'department1'])
runCriteria Employee, [closure1, projection], [max: 10, offset: 0]
这是执行此操作的buildProjection
方法:
Closure buildProjection(String path, String criterionMethod, List args) {
def invoker = { String projectionName, Closure subcriteria ->
delegate."$projectionName"(subcriteria)
}
def closure = { ->
delegate."$criterionMethod"(args)
}
for (String projectionName in (path.split('\.').reverse())) {
closure = invoker.clone().curry(projectionName, closure)
}
closure
}
要了解其工作原理,请再次查看最里面的闭包:
department {
eq 'name', 'department1'
}
实际上,这将作为对委托的方法调用被调用
delegate.department({
eq 'name', 'department1'
})
Groovy让我们使用GString
动态调用方法,所以这与
String methodName = 'department'
delegate."$methodName"({
eq 'name', 'department1'
})
因此,我们可以将嵌套的闭包表示为内部闭包,作为其包含的闭包的闭包参数调用,并将其作为其包含的闭包的闭包参数调用,依此类推,直到用完所有级别。
我们可以构建一个闭包,它调用eq 'name', 'department1'
(或带有参数的任何标准方法,这只是一个简化的示例),如下所示:
def closure = { ->
delegate."$criterionMethod"(args)
}
因此,为了表示嵌套的闭包,请从“调用者”闭包开始:
def invoker = { String projectionName, Closure subcriteria ->
delegate."$projectionName"(subcriteria)
}
并依次在每个嵌套级别对其进行克隆,并对其进行咖喱处理以嵌入投影名称及其内部闭包,因为条件构建器不希望使用任何闭包参数,从内而外进行工作:
for (String projectionName in (path.split('\.').reverse())) {
closure = invoker.clone().curry(projectionName, closure)
}
因此,最后,我们可以将分解后的查询作为具有标准条件方法调用的一个或多个“核心”条件闭包,以及零个或多个派生的投影闭包来运行:
def criteria = {
eq 'name', 'employee1'
}
def projection = buildProjection('manager.department',
'eq', ['name', 'department1'])
runCriteria Employee, [criteria, projection], [max: 10, offset: 0]
老实说,我怀疑这里有很多重用的潜力,但是通过这一工作帮助我更好地了解了GORM如何运行标准查询。 我将在下个月的Greach上讨论此主题以及其他一些GORM主题,因此,如果您觉得这个有趣,请务必查看该谈话的录音。
翻译自: https://www.javacodegeeks.com/2016/03/building-gorm-criteria-queries-dynamically.html
最后
以上就是活泼黑夜为你收集整理的动态构建GORM标准查询的全部内容,希望文章能够帮你解决动态构建GORM标准查询所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复