ObjectiveSQL 稳定版本发布 1.4.0 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
Braisdom
V2EX    程序员

ObjectiveSQL 稳定版本发布 1.4.0

  •  2
     
  •   Braisdom
    braisdom 2020-11-30 11:15:39 +08:00 3709 次点击
    这是一个创建于 1783 天前的主题,其中的信息可能已经有所发展或是发生改变。

    你可能喜欢他,也有可能讨厌他,就像 Lombok 这种 Java 编程模式一样,也会被很多人拒绝。只不过这次你遇到的不再是处理简单的 setter 和 getter,而是用这种编程风格来解决 ORM 的简单查询和复杂 SQL 编程。

    经过这段时间各位提交的 Bug 和版本的基本特性,发 1.4.0 稳定版本,主要特性如下:

    项目地址: https://github.com/braisdom/ObjectiveSql

    完整特性如下:

    1 简单查询(首先需要定义一个以 DomainModel Annotation 定义的模型)

    @DomainModel public class Member { private String no; @Queryable private String name; private Integer gender; private String mobile; private String otherInfo; @Relation(relatiOnType= RelationType.HAS_MANY) private List<Order> orders; } 

    1.1 数据持久化

    Member.create(newMember); Member.create(newMember, true); // Create a member without validating Member.create(Member.newInstanceFrom(memberHash)); Member.create(new Member[]{newMember1, newMember2, newMember3}, false); Member.update(1L, newMember, true); // Update a member with primary key Member.update("name = 'Smith => Jackson'", "name = 'Alice'"); Member.destroy(1L); // Delete a member with primary key Member.destroy("name = 'Mary'"); // Execute SQL Member.execute(String.format("DELETE FROM %s WHERE name = 'Mary'", Member.TABLE_NAME)); 

    1.2 事务处理

    @Transactional public static void makeOrder(Order order, OrderLine... orderLines) throws SQLException { Order.create(order, false); OrderLine.create(orderLines, false); } 

    1.3 查询与统计

    Member.countAll(); Member.count("id > ?", 1); Member.queryByPrimaryKey(1); Member.queryFirst("id = ?", 1); Member.query("id > ?", 1); Member.queryAll(); 

    1.4 分页查询

    Page page = Page.create(0, 10);// Create a Page instance with current page and page size PagedList<Member> members = Member.pagedQueryAll(page, Member.HAS_MANY_ORDERS); 

    1.5 关联对象查询

    // Querying objects with convenient methods, and it will carry the related objects Member.queryAll(Member.HAS_MANY_ORDERS); Member.queryByPrimary(1, Member.HAS_MANY_ORDERS); Member.queryByName("demo", Member.HAS_MANY_ORDERS); 

    2 复杂 SQL 查询

    Java 代码:

    // SQL programming with Java syntax without losing the features of SQL syntax 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); 

    生成的 SQL 代码:

    -- SQL syntax is the same as Java syntax SELECT ((((SUM(`T0`.`amount` ) / SUM(`T0`.`quantity` ) )) * 100)) FROM `orders` AS `T0` WHERE ((`T0`.`quantity` > 30) AND `T0`.`sales_at` BETWEEN '2020-10-10 00:00:00' AND '2020-10-30 23:59:59') GROUP BY `T0`.`product_id` 
    43 条回复    2022-01-20 22:56:01 +08:00
    PopRain
        1
    PopRain  
       2020-11-30 11:32:31 +08:00
    生成的 SQL 最好是参数化查询,这样效率高且可以避免 SQL 注入
    Braisdom
        2
    Braisdom  
    OP
       2020-11-30 11:35:00 +08:00
    是的,SQL 注入是 ObjectiveSQL 下一阶段的重点特性
    GM
        3
    GM  
       2020-11-30 11:58:58 +08:00
    删除使用 destroy 挺反直觉的,毕竟 sql 里删除就是 delete,大部分人平时用的也都是 delete,建议使用 delete
    替换 destroy 。
    beginor
        4
    beginor  
       2020-11-30 12:25:35 +08:00
    @GM 我也是, 在 destroy 那里愣了一下才明白过来。
    beginor
        5
    beginor  
       2020-11-30 12:28:35 +08:00   1
    持续关注楼主的这个项目, 昨天试用了了一下,确实挺不错的。 不过和 c# 的 linq 比起来, 还是差那么点意思, 主要还是受限于 java 的语法。
    Braisdom
        6
    Braisdom  
    OP
       2020-11-30 12:41:27 +08:00
    @GM
    @beginor
    两位,先陈述一下我的设计思路,问题域分两块:SQL 域和 Java 所解决的业务域,在业务域中存在的是对象行为,创建(create),更新(update),和销毁 /删除(destroy),而 SQL 中通常是 Insert, update, delete 。

    我的设计来自于 Rails 的设计,本质上怎么用问题都不大,只是习惯问题而已
    Braisdom
        7
    Braisdom  
    OP
       2020-11-30 12:42:48 +08:00
    @beginor 感谢支持,下一阶段 ObjectiveSQL 项目的主要工作是:数据迁移(Migration) 和 SQL 注入(SQL Injection)
    chinvo
        8
    chinvo  
       2020-11-30 12:47:41 +08:00 via iPhone   1
    虽然不写 Java,但是还是点个赞

    顺道安利一波 .net

    建议对 .net 还挺留在 framework 2.0 时代认知的人了解一下新时代的 .net (core 、5.0 、6.0):开放、高效、易用
    Braisdom
        9
    Braisdom  
    OP
       2020-11-30 12:48:50 +08:00
    @chinvo .NET 的发展过程值得学习
    chinvo
        10
    chinvo  
       2020-11-30 12:51:33 +08:00 via iPhone
    @Braisdom #9 没拥抱开源之前的 .net 也不能说不好,但是真的用不起,Windows Server 授权、Visual Studio 授权、SQL Server 授权……

    拥抱开源之后和 RHEL 合作,在 RH 系上是商业级的支持力度

    不过在我看来 .Net 最强的地方还是 LinQ 和 EF,写业务逻辑最优选择
    Braisdom
        11
    Braisdom  
    OP
       2020-11-30 12:54:48 +08:00
    @chinvo 我之前没有接触过.NET 的 entity framework,也是最近才看到,

    我设计 ObjectiveSQL 的主要目的是为了解决复杂 SQL 的处理,现有的框架维护动态 SQL 太痛苦,时间一长,项目根本无法维护。

    简单 SQL 现有的框架已经做的很不错了,只是顺带把它做了而已
    chinvo
        12
    chinvo  
       2020-11-30 12:59:10 +08:00 via iPhone
    @Braisdom #11 其实我比较好奇,很多语言都有类似 EF/LinQ 的 Orm,Java 生态圈竟然没有么

    我有了解过 Java 的几种 Orm,有一些是 xml 描述( Java 生态圈好像挺依赖 xml 的),有一些甚至还要写“类 SQL”,给我的直观感受就是难用、反人类
    GM
        13
    GM  
       2020-11-30 13:03:23 +08:00
    @chinvo 推荐个应用框架? ABP 太复杂了,感觉不适合技术能力一般的团队用。
    GM
        14
    GM  
       2020-11-30 13:05:27 +08:00
    @chinvo
    真的没有。
    受制于语言表达能力,很多很好用的功能 Java 里就是无法用正常 Java 代码写出来,只能用代码生成器、动态字节码等别扭的方式来实现。
    Braisdom
        15
    Braisdom  
    OP
       2020-11-30 13:05:36 +08:00   1
    @chinvo 我也有同感,我之前写了很多年的 Java,中途写 Ruby 和 Python,也是最近两年才又写 Java,ORM 又是一个系统无法避免的问题,用起来太痛苦,所以才会写 ObjectiveSQL 这个项目的。

    Java LINQ 也有,但很不好用,只能处理一些相对简单的查询,join 子查询,union,复杂表达式等(多层 case when 或者窗口函数等),这些处理起来很不舒服
    chinvo
        16
    chinvo  
       2020-11-30 13:08:46 +08:00 via iPhone
    @GM #13 个人、小团队,直接裸的 Asp.Net MVC 啊。
    GM
        17
    GM  
       2020-11-30 13:16:31 +08:00
    @chinvo ASP.NET MVC 这个只能做个单体应用,想做个分布式的需要自己手撸各种基础设施功能。
    renyijiu
        18
    renyijiu  
       2020-11-30 13:19:17 +08:00
    有个不太相关的问题,是否支持 grpc protobuf ?
    chinvo
        19
    chinvo  
       2020-11-30 13:21:32 +08:00 via iPhone
    @GM #17 分布式(Actor 模型)用 Orleans,多租户用 MapEndpoint,UoW 其实用不着,因为 EF 本身就是 UoW 模型
    Braisdom
        20
    Braisdom  
    OP
       2020-11-30 13:24:01 +08:00
    @renyijiu protobuf 只是一个协议封装,用在数据传输的,现在比较流行,相比很早的 TLV 灵活很多。rpc 只是远程调用,现在微服务里比较流行
    renyijiu
        21
    renyijiu  
       2020-11-30 13:39:01 +08:00
    @Braisdom #20 可能理解不一致,可以直接映射使用 protobuf 生成的类,而不是再自己定一个 model 层的 class
    DoctorCat
        22
    DoctorCat  
       2020-11-30 14:06:22 +08:00
    想起 10 年前用 SSH 的时候,jdbc template 的封装过程
    GM
        23
    GM  
       2020-11-30 15:00:11 +08:00
    @chinvo Orleans 看起来很不错啊,谢谢。另外,UoW 指的是 Unit of Work 吗?
    Braisdom
        24
    Braisdom  
    OP
       2020-11-30 15:31:34 +08:00
    @renyijiu OK,之前是理解有对,这个问题我之前遇到过,ObjectiveSQL 已经兼容了,

    之前我的一个项目是通过 ProtoBuffer 定义的模型,传输的数据极大,但需要直接存储进数据,如果中间再经过一层转换,性能太差,所以我就在 ObjectiveSQL 中设计了 DomainModelDescriptor,用于描述存储数据类型相关的信息,可以直接通过 ObjectiveSQL 进行数据库操作。

    具体你可以参考: https://github.com/braisdom/ObjectiveSql/blob/master/core/src/main/java/com/github/braisdom/objsql/DomainModelDescriptor.java
    gowk
        25
    gowk  
       2020-11-30 16:24:14 +08:00
    @GM
    @chinvo
    ABP 确实对技术能力要求比较高,一般的团队感觉 hold 不住。我也在考虑,一般的中小型项目,怎么流畅的使用.NET 开发应用,专注于业务,有封装好的 boilerplate 推荐吗(不需要前后端分离)
    chinvo
        26
    chinvo  
       2020-11-30 18:05:05 +08:00 via iPhone
    @GM #23
    @gowk #25

    这毕竟是楼主关于自己作品的帖子,咱们持续在这里这样讨论其他话题有点不礼貌,如果要深入讨论 .net 、abp 等问题咱们可以另开一贴
    renyijiu
        27
    renyijiu  
       2020-11-30 18:59:30 +08:00
    @Braisdom #24 感谢,我看看这块
    beginor
        28
    beginor  
       2020-11-30 19:21:10 +08:00
    @Braisdom 请问 objsql 支持动态查询么? 类似这样的 https://blog.jooq.org/tag/dynamic-sql/
    Braisdom
        29
    Braisdom  
    OP
       2020-11-30 21:37:34 +08:00
    JOOQ 称为动态查询,在 ObjectiveSQL 里称为复杂查询,详细请查询:2 复杂 SQL 查询
    Braisdom
        30
    Braisdom  
    OP
       2020-11-30 21:41:41 +08:00
    @beginor

    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);

    你可以看一下上述代码在 Jooq 中如果实现
    beginor
        31
    beginor  
       2020-11-30 22:19:22 +08:00 via Android
    @Braisdom 我说的是类似这种查询

    DSLContext ctx = ...;

    SelectConditionStep<?> c =
    ctx.select(T.A, T.B)
    .from(T)
    .where(T.C.eq(1));

    if (something)
    c = c.and(T.D.eq(2));

    Result<?> result = c.fetch()
    beginor
        32
    beginor  
       2020-11-30 22:20:53 +08:00 via Android
    是的,Java 还真没有好用 linq/lambda 框架
    Braisdom
        33
    Braisdom  
    OP
       2020-11-30 22:29:51 +08:00
    @beginor 给你一段项目的代码(计算一个商品销售的同环比),比较复杂:

    你也可以把 SpringBoot 项目运行起来看效果:

    https://github.com/braisdom/ObjectiveSql/blob/master/examples/springboot-sample/src/main/java/com/github/braisdom/objsql/sample/model/Product.java#L45


    DateTime begin = DateTime.parse(rawBegin + " 00:00:00", DATE_TIME_FORMATTER);
    DateTime end = DateTime.parse(rawEnd + " 23:59:59", DATE_TIME_FORMATTER);

    // Creating dataset of target, last period and same period last year
    Select target = createPeriodSales(rawBegin, rawEnd);
    Select lp = createPeriodSales(minusMonths(begin, 1), minusMonths(end, 1));
    Select sply = createPeriodSales(minusYears(begin, 1), minusYears(end, 1));

    Select select = new Select();
    select.from(target)
    .leftOuterJoin(lp, createLPJoinCondition(target, lp))
    .leftOuterJoin(sply, createSPLYJoinCondition(target, sply));

    // Create calculation expression of last period
    Expression lpAmount = createLPExpr(target, lp, "total_amount");
    Expression lpOrderCount = createLPExpr(target, lp, "order_count");
    Expression lpQuantity = createLPExpr(target, lp, "total_quantity");

    // Create calculation expression of same period last year
    Expression splyAmount = createSPLYExpr(target, sply, "total_amount");
    Expression splyOrderCount = createSPLYExpr(target, sply, "order_count");
    Expression splyQuantity = createSPLYExpr(target, sply, "total_quantity");

    select.project(target.col("barcode"))
    .project(target.col("sales_year"))
    .project(target.col("sales_month"))
    .project(formatMoney(lpAmount).as("amount_lp"))
    .project(formatMoney(lpOrderCount).as("order_count_lp"))
    .project(formatMoney(lpQuantity).as("quantity_lp"))
    .project(formatMoney(splyAmount).as("amount_sply"))
    .project(formatMoney(splyOrderCount).as("order_count_sply"))
    .project(formatMoney(splyQuantity).as("quantity_sply"));

    select.groupBy(target.col("barcode"),
    target.col("sales_year"),
    target.col("sales_month"));

    return select.execute(Product.class);
    beginor
        34
    beginor  
       2020-11-30 22:35:19 +08:00 via Android
    @Braisdom 这段代码绝对算是复杂查询, 但是不是我说的动态查询
    beginor
        35
    beginor  
       2020-11-30 22:37:47 +08:00 via Android
    复杂查询和动态查询,是两个不同的概念,我不质疑 objsql 的复杂查询能力,只是想了解下是否支持动态查询
    Braisdom
        36
    Braisdom  
    OP
       2020-12-01 07:54:54 +08:00
    @beginor 所谓动态查询也就是根据不同的参数,join 不同的表,或者选择不同的条件,因为参与拼接的对象都是变量,本身就是动态的。
    beginor
        37
    beginor  
       2020-12-01 08:36:16 +08:00
    我也贴一个常用的 NHibernate 动态查询示例吧, 不知道在 objsql 下如何实现, 对 java 不熟悉, 不敢妄语。

    ```c#
    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();
    }
    ```

    PS: 丝毫没有秀 c# 优越感的意思, 我只是好奇是否支持这种动态查询。
    beginor
        38
    beginor  
       2020-12-01 08:42:41 +08:00
    回复的格式有点儿乱, 可以看这个 gist https://gist.github.com/beginor/4bc9bfd25dfd9f488156cf4975b707f6
    Braisdom
        39
    Braisdom  
    OP
       2020-12-01 10:10:01 +08:00
    我也在 gist 里回复了。

    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.orderBy(user.id.asc()).execute(User.class);
    }
    zhangysh1995
        40
    zhangysh1995  
       2020-12-01 14:55:15 +08:00
    性能咋样?看到过论文专门研究 ORM 性能问题的,有些会导致产生的 SQL 很慢?我对这个方向挺有兴趣的,我发个邮件聊聊呗。
    Braisdom
        41
    Braisdom  
    OP
       2020-12-01 15:00:12 +08:00   1
    @zhangysh1995 [email protected]
    ORM 性能问题不是慢 SQL,而是在大规模写和读时的性能,这块我还在优化,和 MyBatis 的性能差不多,但离 JDBC 原始 SQL 还是有差距。
    zhangysh1995
        42
    zhangysh995  
       2020-12-01 15:01:25 +08:00
    @Braisdom 邮件已发,坐等回复。
    xiaohuya
        43
    xiaohuya  
       2022-01-20 22:56:01 +08:00
    太像 rails 了,赞
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     890 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 41ms UTC 20:15 PVG 04:15 LAX 13:15 JFK 16:15
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86