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动态构建数据库复杂查询实现示例的详细内容,更多关于动态构建数据库复杂查询的资料请关注我们其它相关文章!

(0)

相关推荐

  • 实例讲解Java的MyBatis框架对MySQL中数据的关联查询

    mybatis 提供了高级的关联查询功能,可以很方便地将数据库获取的结果集映射到定义的Java Bean 中.下面通过一个实例,来展示一下Mybatis对于常见的一对多和多对一关系复杂映射是怎样处理的. 设计一个简单的博客系统,一个用户可以开多个博客,在博客中可以发表文章,允许发表评论,可以为文章加标签.博客系统主要有以下几张表构成: Author表:作者信息表,记录作者的信息,用户名和密码,邮箱等. Blog表   :  博客表,一个作者可以开多个博客,即Author和Blog的关系是一对多.

  • Java的MyBatis框架中对数据库进行动态SQL查询的教程

    其实MyBatis具有的一个强大的特性之一通常是它的动态 SQL 能力. 如果你有使用 JDBC 或其他 相似框架的经验,你就明白要动态的串联 SQL 字符串在一起是十分纠结的,确保不能忘了空格或在列表的最后省略逗号.Mybatis中的动态 SQL 可以彻底处理这种痛苦.对于动态SQL,最通俗简单的方法就是我们自己在硬编码的时候赋予各种动态行为的判断,而在Mybatis中,用一种强大的动态 SQL 语 言来改进这种情形,这种语言可以被用在任意映射的 SQL 语句中.动态 SQL 元素和使用 JS

  • Spring data jpa的使用与详解(复杂动态查询及分页,排序)

    一. 使用Specification实现复杂查询 (1) 什么是Specification Specification是springDateJpa中的一个接口,他是用于当jpa的一些基本CRUD操作的扩展,可以把他理解成一个spring jpa的复杂查询接口.其次我们需要了解Criteria 查询,这是是一种类型安全和更面向对象的查询.而Spring Data JPA支持JPA2.0的Criteria查询,相应的接口是JpaSpecificationExecutor. 而JpaSpecifica

  • JPA多条件复杂SQL动态分页查询功能

    概述 ORM映射为我们带来便利的同时,也失去了较大灵活性,如果SQL较复杂,要进行动态查询,那必定是一件头疼的事情(也可能是lz还没发现好的方法),记录下自己用的三种复杂查询方式. 环境 springBoot IDEA2017.3.4 JDK8 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0&q

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

    有的时候,你需要动态构建一个比较复杂的查询条件,传入数据库中进行查询.而条件本身可能来自前端请求或者配置文件.那么这个时候,表达式树,就可以帮助到你. Where当中可以传入固定的条件 以下是一个简单的单元测试用例.接下来,我们将这个测试用例改的面目全非. [Test] public void Normal() { var re = Enumerable.Range(0, 10).AsQueryable() // 0-9 .Where(x => x >= 1 && x <

  • Java连接Oracle数据库并查询

    下载ODBC Jar包驱动,网上百度下载或者去官网下载,导入到Eclipse 项目里面 建立连接 public class DbConn { private static String driver = "oracle.jdbc.driver.OracleDriver"; private static String url = "jdbc:oracle:thin:@localhost:1521:orcl"; private static String user =

  • Java连接mysql数据库的详细教程(推荐)

    该教程是面向那些不知道如何通过java连接mysql数据库的新手,所以该教程用的方法不是最优的方法,但求是最好理解的方法. 需要的工具: eclipse.mysql.mysql的驱动 mysql驱动链接:mysql-connector-java-5.1.7.zip 如果该链接下载不了的话,可以私信我 1.在eclipse中创建一个工程:jdbc 2.在工程下创建lib文件夹 鼠标右键点击jdbc工程 --> New -->Folder. 文件夹命名为lib 3.导入mysql驱动 解压上述连接

  • java项目构建Gradle的使用教程

    目录 一.相关介绍 二.安装 三.IED中的使用 1.IDEA 2.Eclipse 四.问题说明 一.相关介绍 Gradle是一个好用的构建工具 ,使用它的原因是: 配置相关依赖代码量少,不会像maven一样xml过多 打包编译测试发布都有,而且使用起来方便 利用自定义的任务可以完成自己想要的功能 二.安装 下载地址http://services.gradle.org/distributions/  ,下载你所需要对应的版本,我这里下载的是gradle-4.7-bin.zip.下载后解压到你想要

  • JAVA代码实现MongoDB动态条件之分页查询

    一.使用QueryByExampleExecutor 1. 继承MongoRepository public interface StudentRepository extends MongoRepository<Student, String> { } 2. 代码实现 使用ExampleMatcher匹配器-----只支持字符串的模糊查询,其他类型是完全匹配 Example封装实体类和匹配器 使用QueryByExampleExecutor接口中的findAll方法 public Page&

  • Java中JFinal框架动态切换数据库的方法

    需求:需要根据企业ID切换对应的数据库,同时,后期可动态增加数据库配置 JFinal框架中对于对于多数据源配置有两种方式: 1.通过配置文件配置,有多少数据库就要配置多少,服务启动时加载所有数据库,缺点:不能动态增加数据库 2.只配置一个主数据库信息就可以了,其他数据库信息保存在表中,通过读取表数据加载数据库连接,优点:在数据表中增加数据库配置即可动态增加数据库连接. 本次主要介绍第2种方法: 一.新建数据表:保存数据库连接信息 配置表对应的实体类 public class DbDto { /*

  • Java连接mysql数据库并进行内容查询的方法

    最近用框架做了几个项目,感觉当初底层的东西有点忘了,写一个JDBC的简单的连接代码来熟悉回顾一下,也希望对刚接触的新手能有所帮助.这也是我的第一篇随笔,废话不多说,直接上代码: public Connection getCon() { //数据库连接名称 String username="root"; //数据库连接密码 String password=""; String driver="com.mysql.jdbc.Driver"; //其中

  • Java对MySQL数据库进行连接、查询和修改操作方法

    0. 一般过程: (1) 调用Class.forName()方法加载驱动程序. (2) 调用DriverManager对象的getConnection()方法,获得一个Connection对象. (3) 创建一个Statement对象,准备一个SQL语句,这个SQL语句可以是Statement对象(立即执行的的语句).PreparedStatement语句(预编译的语句)或CallableStatement对象(存储过程调用的语句). (4) 调用excuteQuery()等方法执行SQL语句,

  • java实现连接mysql数据库单元测试查询数据的实例代码

    1.按照javaweb项目的要求逐步建立搭建起机构,具体的类包有:model .db.dao.test; 具体的架构详见下图: 2.根据搭建的项目架构新建数据库test和数据库表t_userinfo并且添加对应的测试数据; (这里我使用的是绿色版的数据库,具体的下载地址:http://pan.baidu.com/s/1mg88YAc) 具体的建立数据库操作详见下图: 3.编写包中的各种类代码,具体参考代码如下: UserInfo.java /** * FileName: UserInfo.jav

  • Java的idea连接mongodb数据库的详细教程

    最近有一个java实验,要求用java使用数据库,于是本人新手小白,在idea上卡了好半天 希望看到这个博客的人能解决问题,跳过一些坑 首先,我这里用的是 mongodb 数据库(ps:node.js下mongo太好用了,就没有mysql) 1,用idea创建一个maven工程 由于不牵扯太多功能,直接 next 就行了, 很无奈,创建完就直接报错了 找不到 maven 相关的插件 Cannot resolve plugin org.apache.maven.plugins:maven-comp

随机推荐