java动态构建数据库复杂查询教程

 更新时间:2021年11月25日 14:10:30   作者:厦门在乎科技  
这篇文章主要介绍了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<Func<int, bool>> 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 = 1;
    var maxValue = 5;
    Expression<Func<int, bool>> 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);
}

可以使用方法创建表达式

那既然这样,我们也可以使用一个方法来创建 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<Func<int, bool>> 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<Func<int, bool>> CreateFilter(Func<int, bool> leftFunc, Func<int, bool> 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<Func<int, bool>> CreateFilter(Expression<Func<int, bool>> leftFunc,
        Expression<Func<int, bool>> 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<Func<int, bool>>(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<Func<int, bool>> CreateFilter(Expression<Func<int, bool>> leftFunc,
        Expression<Func<int, bool>> 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<Func<int, bool>>(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<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,
        params Expression<Func<int, bool>>[] 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<Func<int, bool>>(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<Func<int, bool>> CreateMinValueFilter(int minValue)
    {
        return x => x >= minValue;
    }
    Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,
        params Expression<Func<int, bool>>[] 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<Func<int, bool>>(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<Func<int, bool>> 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<Func<int, bool>>(bodyExp, pExp);
        return result;
    } 
    Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,
        params Expression<Func<int, bool>>[] 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<Func<int, bool>>(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<Func<int, bool>> CreateValueCompareFilter(Func<Expression, Expression, Expression> comparerFunc,
        int rightValue)
    {
        var pExp = Expression.Parameter(typeof(int), "x");
        var rightExp = Expression.Constant(rightValue);
        var bodyExp = comparerFunc(pExp, rightExp);
        var result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);
        return result;
    } 
    Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,
        params Expression<Func<int, bool>>[] 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<Func<int, bool>>(bodyExp, pExp);
        } 
        return result;
    }
}

加入一点点配置,就完成了

最后,我们在把子表达式的创建通过一点点小技巧。通过外部参数来决定。就基本完成了一个多 And 的值比较查询条件的动态构建。

[Test]
public void Expression10()
{
    var config = new Dictionary<string, int>
    {
        { ">=", 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<Expression, Expression, Expression> MapConfig(string op)
    {
        return op switch
        {
            ">=" => Expression.GreaterThanOrEqual,
            "<" => Expression.LessThan,
            _ => throw new ArgumentOutOfRangeException(nameof(op))
        };
    } 
    Expression<Func<int, bool>> CreateValueCompareFilter(Func<Expression, Expression, Expression> comparerFunc,
        int rightValue)
    {
        var pExp = Expression.Parameter(typeof(int), "x");
        var rightExp = Expression.Constant(rightValue);
        var bodyExp = comparerFunc(pExp, rightExp);
        var result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);
        return result;
    }
    Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,
        params Expression<Func<int, bool>>[] 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<Func<int, bool>>(bodyExp, pExp);
        } 
        return result;
    }
}

以上就是java动态构建数据库复杂查询实现示例的详细内容,更多关于动态构建数据库复杂查询的资料请关注脚本之家其它相关文章!

相关文章

  • Jmeter安装及配置教程详解

    Jmeter安装及配置教程详解

    很多朋友私信小编Jmeter安装及配置教程能出一期教程吗?正巧赶上疫情,不是太忙,下面小编把Jmeter安装及配置教程分享到脚本之家平台,感兴趣的朋友跟随小编一起看看吧
    2021-12-12
  • MAC配置java+jmeter环境变量过程解析

    MAC配置java+jmeter环境变量过程解析

    这篇文章主要介绍了MAC配置java+jmeter环境变量过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • java HashMap的keyset实例

    java HashMap的keyset实例

    简单地说,在keyset方法返回的set上做修改会改变原来hashmap,这也许不是你想要的,于是形成一个隐藏的bug
    2013-04-04
  • Java 判断线程池所有任务是否执行完毕的操作

    Java 判断线程池所有任务是否执行完毕的操作

    这篇文章主要介绍了Java 判断线程池所有任务是否执行完毕的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • 如何使用pipeline和jacoco获取自动化测试代码覆盖率

    如何使用pipeline和jacoco获取自动化测试代码覆盖率

    这篇文章主要介绍了如何使用pipeline和jacoco获取自动化测试代码覆盖率,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • JSqlParse完整介绍

    JSqlParse完整介绍

    JSqlParse是一款很精简的sql解析工具,本文主要介绍了JSqlParse完整介绍,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-05-05
  • java利用pdfbox+poi往pdf插入数据

    java利用pdfbox+poi往pdf插入数据

    这篇文章主要给大家介绍了关于java利用pdfbox+poi如何往pdf插入数据的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-02-02
  • springboot使用之多个filter的执行顺序以及配置方式

    springboot使用之多个filter的执行顺序以及配置方式

    这篇文章主要介绍了springboot使用之多个filter的执行顺序以及配置方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java文件批量重命名批量提取特定类型文件

    Java文件批量重命名批量提取特定类型文件

    这篇文章主要介绍了Java文件批量重命名批量提取特定类型文件的相关资料
    2016-08-08

最新评论