Skip to content

Instantly share code, notes, and snippets.

@beginor
Last active December 3, 2020 09:38
Show Gist options
  • Save beginor/4bc9bfd25dfd9f488156cf4975b707f6 to your computer and use it in GitHub Desktop.
Save beginor/4bc9bfd25dfd9f488156cf4975b707f6 to your computer and use it in GitHub Desktop.
dynamic query demo
public void SearchUser(
    string userName,
    int? age
) {
    // 以 NHibernate 的动态查询示例
    ISession session = OpenSession();
    IQueryable<User> query = session.Query<User>();
    // 根据参数动态构建表达式树
    if (userName.IsNotNullOrEmpty()) {
        query = query.Where(user => user.UserName.Contains(userName) )
    }
    if (age.HasValue) {
        query = query.Where(user => user.Age >= age);
    }
    // 可以先根据构造好的表达式树进行 Count 查询
    long userCount = query.LongCount();
    // 也可以继续添加其它表达式,并查询结果
    IList<User> users = query.OrderBy(user => user.Id)
                             .Select(user => new User { Id = user.Id, UserName = user.UserName })
                             .ToList();
}
@catchex
Copy link

catchex commented Dec 1, 2020

public List<User> searchUser(String name, Integer age) {
    User.Table user = User.asTable();
    Select select = new Select();
    LogicalExpression predicate = new PolynaryExpression(EQ, $("1"), $("1"));

    if(StringUtils.isNotBland(name)) {
         predicate.and(user.name.eq(name));
    }

    if(age > 0) {
        predicate.and(user.age.eq(age));
    }
    
    return select.where(predicate).orderBy(user.id.asc()).execute(User.class);
}

@catchex
Copy link

catchex commented Dec 1, 2020

后面可以对多元运算符作一下简单的封装。

@beginor
Copy link
Author

beginor commented Dec 2, 2020

现在这种写法就和 hibernate/jpa 的 Typed Criteria Query 差不多了, 建议参照一下 NHibernate 是如何封装成 lambda 的。 NHibernate 起源于 Hibernate , 然后又融入了许多 C# 的特性。

@catchex
Copy link

catchex commented Dec 2, 2020

Lambda 模式的代码太过于复杂,你看到的只是基础的代码,算术运算、比较运算、逻辑运算通过方法实现太复杂:

Order.Table orderTable = Order.asTable();
Select select = new Select();

select.project(sum(orderTable.amount) / sum(orderTable.quantity) * 100)
        .from(orderTable)
        .where(orderTable.quantity > 30 &&
            orderTable.salesAt.between($("2020-10-10 00:00:00"), $("2020-10-30 23:59:59")))
        .groupBy(orderTable.productId);

像这样的代码在Hibernate, 中是无法做到的

@catchex
Copy link

catchex commented Dec 2, 2020

基于表达式的算术运算和逻辑运算,上面的代码可以直接转换为SQL 中的表达式。

@beginor
Copy link
Author

beginor commented Dec 2, 2020

等价的 linq 表达式应该是类似这样:

var query = session.Query<Order>()
    .Where(order => order.SalesAt >= startDate && order.SalesAt < endDate)
    .GroupBy(order => user.ProductId)
    .Select(group => new {
        Result = group.Sum(order => order.Amount) / group.Sum(order => order.Quantity)
    });

var data = query.ToList();

@catchex
Copy link

catchex commented Dec 2, 2020

恩,微软从语法层进行优化是可行的,但Java 里目前是无法做到的,我之前没有接触过LINQ,我主要的目的是处理Java 中的SQL 编程。

过程化,对象化,动态化是我的目的

@beginor
Copy link
Author

beginor commented Dec 2, 2020

这种需求对 hibernate 来说是小意思了, 不管是用 hql 还是 criteria , 都能轻松实现。 不过肯定不如。c# 的 lambda 来的优雅。

毕竟对于一个存在了十几年的框架来说,如果连这么简单的需求都做不到的话, 不可能存活的下去。

不过, 可以使用新技术,比它做的更好

@catchex
Copy link

catchex commented Dec 2, 2020

兄弟,远远不是如此,动态SQL 的处理方式很多,Java 表达式直接转换为SQL 表达目前还没有,还有将不同数据库的函数的封装,目前也没有,最终还有单元测试的集成,这些工作是我需要完成的。

一个项目能够工程化,需要做的工作还是很多的,远远比一些小特性复杂得多

@catchex
Copy link

catchex commented Dec 3, 2020

我改进了一下,这样会更友好一点:

String[] filteredNo = {"202000001", "202000002", "202000003"};
int filteredQuantity = 0;

Order.Table orderTable = Order.asTable();
Select select = new Select();
LogicalExpression eternalExpression = new EternalExpression();

if(filteredNo.length > 0) {
    eternalExpression = eternalExpression.and(orderTable.no.in(filteredNo));
}

if(filteredQuantity != 0) {
    eternalExpression = eternalExpression.and(orderTable > filteredQuantity);
}

select.project((sum(orderTable.amount) / sum(orderTable.quantity) * 100).as("unit_amount"))
        .from(orderTable)
        .where(eternalExpression)
        .groupBy(orderTable.memberId);

List<Order> orders = select.execute(Order.class);

@beginor
Copy link
Author

beginor commented Dec 3, 2020

如果是用 java 版本的 hibernate 的话, 也能实现上面的功能, 不过确实要写的代码会啰嗦一些, 你提供的这种写法会更加简洁。

@catchex
Copy link

catchex commented Dec 3, 2020

其实我的项目主要解决两个问题:
1)自动生成代码,主要是简单的单表查询和关联对象查询
2)Java 的表达式能直接转换为SQL 的表达式(这块目前没有人实现)

@beginor
Copy link
Author

beginor commented Dec 3, 2020

  1. 自动生成代码,很多工具都带了, 自己写一个也不难, 不过建议把自动生成代码的工具集成到编译系统里(gradle/maven), 而不是集成到 ide 里面, 比如 hibernate 自带的生成工具就可以集成到 gradle
dependencies {
  testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0'
  implementation 'com.google.guava:guava:29.0-jre'
  implementation 'org.postgresql:postgresql:42.2.18'
  implementation 'org.hibernate:hibernate-core:5.4.24.Final'
  implementation 'javax.xml.bind:jaxb-api:2.3.1'
  runtimeOnly 'org.glassfish.jaxb:jaxb-runtime:2.3.1'
  // hibernate 代码生成工具
  annotationProcessor('org.hibernate:hibernate-jpamodelgen:5.4.24.Final')
}
  1. 希望你能干翻 JOOQ , 貌似已经挺久的啦, 但是目前还看不出来你的项目相比 jooq 有什么压倒性的优势;

@catchex
Copy link

catchex commented Dec 3, 2020

1)他们那些都是静态代码生成的,每次改动模型后,都需要重新编译后,才能正常使用,而我是动态生成的,结合 IntelliJ IDEA 插件自动实现,相比他们已经方便很多多了,尤其是开发早期,模型经常变。

2)我参考了JOOQ 挺多东西,你看他的Demo 里没有复杂表达式计算,因为那块处理起来特别复杂,而在实际使用时,各种数学计算是很多的,现有的所有框架都是通过函数来封装表达式计算的,plus(), minus(), multip(),如果表达式复杂,这样的函数套在一起根本看不清的。

@catchex
Copy link

catchex commented Dec 3, 2020

我已经参考了流行的所有ORM 框架了,也读了他们的代码,就靠这两点,使开发效率提升了很多,后期将项目稳定后,一定能超越这些框架

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment