这篇文章主要介绍了java动态构建数据库复杂查询的实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
有的时候,你需要动态构建一个比较复杂的查询条件,传入数据库中进行查询。而条件本身可能来自前端请求或者配置文件。那么这个时候,表达式树,就可以帮助到你。
Where当中可以传入固定的条件
以下是一个简单的单元测试用例。接下来,我们将这个测试用例改的面目全非。
[Test]
public void Normal()
{
var re = Enumerable.Range(0, 10).AsQueryable() // 0-9
.Where(x => x >= 1 && x < 5).ToList();
// 1 2 3 4
var expectation = Enumerable.Range(1, 4);
// 1 2 3 4
re.Should().BeEquivalentTo(expectation);
}
Queryable中的Where就是一种表达式树
由于是 Queryable 的关系,所以Where当中的其实是一个表达式,那么我们把它单独定义出来,顺便水一下文章的长度。
[Test]
public void Expression00()
{
Expression> filter = x => x >= 1 && x < 5;
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
}
表达式可以通过Lambda隐式转换
Expression 右侧是一个 Lambda ,所以可以捕获上下文中的变量。
这样你便可以把 minValue 和 maxValue 单独定义出来。
于是乎你可以从其他地方来获取 minValue 和 maxValue 来改变 filter。
[Test]
public void Expression01()
{
var minValue = https://www.it610.com/article/1;
var maxValue = 5;
Expression> filter = x => x >= minValue && x < maxValue;
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
}
【java动态构建数据库复杂查询教程】可以使用方法创建表达式
那既然这样,我们也可以使用一个方法来创建 Expression。
这个方法,实际上就可以认为是这个 Expression 的工厂方法。
[Test]
public void Expression02()
{
var filter = CreateFilter(1, 5);
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
Expression> CreateFilter(int minValue, int maxValue)
{
return x => x >= minValue && x < maxValue;
}
}
通过Func可以更加灵活的组合条件
那可以使用 minValue 和 maxValue 作为参数来制作工厂方法,那么用委托当然也可以。
于是,我们可以把左边和右边分别定义成两个 Func,从而由外部来决定左右具体的比较方式。
[Test]
public void Expression03()
{
var filter = CreateFilter(x => x >= 1, x => x < 5);
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
Expression> CreateFilter(Func leftFunc, Func rightFunc)
{
return x => leftFunc.Invoke(x) && rightFunc.Invoke(x);
}
}
也可以手动构建表达式
实际上,左右两个不仅仅是两个Func,其实也可以直接是两个表达式。
不过稍微有点不同的是,表达式的合并需要用 Expression 类型中的相关方法创建。
我们可以发现,调用的地方这次其实没有任何改变,因为 Lambda 既可以隐式转换为 Func 也可以隐式转换为 Expression。
每个方法的意思可以从注释中看出。
[Test]
public void Expression04()
{
var filter = CreateFilter(x => x >= 1, x => x < 5);
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
Expression> CreateFilter(Expression> leftFunc,
Expression> rightFunc)
{
// x
var pExp = Expression.Parameter(typeof(int), "x");
// (a => leftFunc(a))(x)
var leftExp = Expression.Invoke(leftFunc, pExp);
// (a => rightFunc(a))(x)
var rightExp = Expression.Invoke(rightFunc, pExp);
// (a => leftFunc(a))(x) && (a => rightFunc(a))(x)
var bodyExp = Expression.AndAlso(leftExp, rightExp);
// x => (a => leftFunc(a))(x) && (a => rightFunc(a))(x)
var resultExp = Expression.Lambda>(bodyExp, pExp);
return resultExp;
}
}
使其更加简单
但是,上面的方法,其实可以再优化一下。避免对左右表达式的直接调用。
使用一个叫做 Unwrap 的方法,可以将 Lambda Expression 解构成只包含 Body 部分的表达式。
这是一个自定义的扩展方法,你可以通过 ObjectVisitor 来引入这个方法。
[Test]
public void Expression05()
{
var filter = CreateFilter(x => x >= 1, x => x < 5);
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
Expression> CreateFilter(Expression> leftFunc,
Expression> rightFunc)
{
// x
var pExp = Expression.Parameter(typeof(int), "x");
// leftFunc(x)
var leftExp = leftFunc.Unwrap(pExp);
// rightFunc(x)
var rightExp = rightFunc.Unwrap(pExp);
// leftFunc(x) && rightFunc(x)
var bodyExp = Expression.AndAlso(leftExp, rightExp);
// x => leftFunc(x) && rightFunc(x)
var resultExp = Expression.Lambda>(bodyExp, pExp);
return resultExp;
}
}
可以拼接更多的表达式
我们可以再优化以下,把 CreateFilter 方法扩展为支持多个子表达式和可自定义子表达式的连接方式。
于是,我们就可以得到一个 JoinSubFilters 方法。
[Test]
public void Expression06()
{
var filter = JoinSubFilters(Expression.AndAlso, x => x >= 1, x => x < 5);
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
Expression> JoinSubFilters(Func expJoiner,
params Expression>[] subFilters)
{
// x
var pExp = Expression.Parameter(typeof(int), "x");
var result = subFilters[0];
foreach (var sub in subFilters[1..])
{
var leftExp = result.Unwrap(pExp);
var rightExp = sub.Unwrap(pExp);
var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda>(bodyExp, pExp);
}
return result;
}
}
使用工厂方法来代替固定的子表达式
有了前面的经验,我们知道。其实x => x >= 1这个表达式可以通过一个工厂方法来建。
所以,我们使用一个 CreateMinValueFilter 来创建这个表达式。
[Test]
public void Expression07()
{
var filter = JoinSubFilters(Expression.AndAlso,
CreateMinValueFilter(1),
x => x < 5);
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
Expression> CreateMinValueFilter(int minValue)
{
return x => x >= minValue;
}
Expression> JoinSubFilters(Func expJoiner,
params Expression>[] subFilters)
{
// x
var pExp = Expression.Parameter(typeof(int), "x");
var result = subFilters[0];
foreach (var sub in subFilters[1..])
{
var leftExp = result.Unwrap(pExp);
var rightExp = sub.Unwrap(pExp);
var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda>(bodyExp, pExp);
}
return result;
}
}
工厂方法内部也可以使用Expression手动创建
当然,可以只使用 Expression 相关的方法来创建x => x >= 1。
[Test]
public void Expression08()
{
var filter = JoinSubFilters(Expression.AndAlso,
CreateMinValueFilter(1),
x => x < 5);
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
Expression> CreateMinValueFilter(int minValue)
{
// x
var pExp = Expression.Parameter(typeof(int), "x");
// minValue
var rightExp = Expression.Constant(minValue);
// x >= minValue
var bodyExp = Expression.GreaterThanOrEqual(pExp, rightExp);
var result = Expression.Lambda>(bodyExp, pExp);
return result;
}
Expression> JoinSubFilters(Func expJoiner,
params Expression>[] subFilters)
{
// x
var pExp = Expression.Parameter(typeof(int), "x");
var result = subFilters[0];
foreach (var sub in subFilters[1..])
{
var leftExp = result.Unwrap(pExp);
var rightExp = sub.Unwrap(pExp);
var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda>(bodyExp, pExp);
}
return result;
}
}
同理,子表达式都可以如此创建
那既然都用了 Expression 来创建子表达式了,那就干脆再做一点点改进,把x => x < 5也做成从工厂方法获取。
[Test]
public void Expression09()
{
var filter = JoinSubFilters(Expression.AndAlso,
CreateValueCompareFilter(Expression.GreaterThanOrEqual, 1),
CreateValueCompareFilter(Expression.LessThan, 5));
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
Expression> CreateValueCompareFilter(Func comparerFunc,
int rightValue)
{
var pExp = Expression.Parameter(typeof(int), "x");
var rightExp = Expression.Constant(rightValue);
var bodyExp = comparerFunc(pExp, rightExp);
var result = Expression.Lambda>(bodyExp, pExp);
return result;
}
Expression> JoinSubFilters(Func expJoiner,
params Expression>[] subFilters)
{
// x
var pExp = Expression.Parameter(typeof(int), "x");
var result = subFilters[0];
foreach (var sub in subFilters[1..])
{
var leftExp = result.Unwrap(pExp);
var rightExp = sub.Unwrap(pExp);
var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda>(bodyExp, pExp);
}
return result;
}
}
加入一点点配置,就完成了
最后,我们在把子表达式的创建通过一点点小技巧。通过外部参数来决定。就基本完成了一个多 And 的值比较查询条件的动态构建。
[Test]
public void Expression10()
{
var config = new Dictionary
{
{ ">=", 1 },
{ "<", 5 }
};
var subFilters = config.Select(x => CreateValueCompareFilter(MapConfig(x.Key), x.Value)).ToArray();
var filter = JoinSubFilters(Expression.AndAlso, subFilters);
var re = Enumerable.Range(0, 10).AsQueryable()
.Where(filter).ToList();
var expectation = Enumerable.Range(1, 4);
re.Should().BeEquivalentTo(expectation);
Func MapConfig(string op)
{
return op switch
{
">=" => Expression.GreaterThanOrEqual,
"<" => Expression.LessThan,
_ => throw new ArgumentOutOfRangeException(nameof(op))
};
}
Expression> CreateValueCompareFilter(Func comparerFunc,
int rightValue)
{
var pExp = Expression.Parameter(typeof(int), "x");
var rightExp = Expression.Constant(rightValue);
var bodyExp = comparerFunc(pExp, rightExp);
var result = Expression.Lambda>(bodyExp, pExp);
return result;
}
Expression> JoinSubFilters(Func expJoiner,
params Expression>[] subFilters)
{
// x
var pExp = Expression.Parameter(typeof(int), "x");
var result = subFilters[0];
foreach (var sub in subFilters[1..])
{
var leftExp = result.Unwrap(pExp);
var rightExp = sub.Unwrap(pExp);
var bodyExp = expJoiner(leftExp, rightExp);
result = Expression.Lambda>(bodyExp, pExp);
}
return result;
}
}
以上就是本文全部的内容,希望能帮助到大家!
推荐阅读
- JAVA难点理解|(JAVA): 自定义枚举和enum枚举类型------详解。
- 计算机网络(王道考研)|3.4计算机网络(局域网基本概念和体系结构 以太网 )
- 大数据|前后端分离架构中的接口安全_如何立即确保您的应用架构安全(分离,配置和访问)
- 操作系统(王道考研)|2.7操作系统(读者—写者问题 哲学家进餐问题 管程 )
- 解决IDEA 2022 Translation 翻译文档失败: 未知错误的问题
- 我的碎碎念|真的无语,MSRA连国防七子及北邮学生都不招了
- Java毕业设计项目实战篇|Java项目:宿舍管理系统(java+SpringBoot+Thymeleaf+html+layui+mysql)
- Java毕业设计项目实战篇|Java项目:企业人事系统(java+SpringBoot+Vue+ElementUI+maven+mysql)
- Java毕业设计项目实战篇|Java项目:图书管理系统(java+SpringBoot+jpa+html+swagger+maven+mysql)