你可能喜欢他,也有可能讨厌他,就像 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`
1
PopRain 2020-11-30 11:32:31 +08:00
生成的 SQL 最好是参数化查询,这样效率高且可以避免 SQL 注入
|
2
Braisdom OP 是的,SQL 注入是 ObjectiveSQL 下一阶段的重点特性
|
3
GM 2020-11-30 11:58:58 +08:00
删除使用 destroy 挺反直觉的,毕竟 sql 里删除就是 delete,大部分人平时用的也都是 delete,建议使用 delete
替换 destroy 。 |
5
beginor 2020-11-30 12:28:35 +08:00 1
持续关注楼主的这个项目, 昨天试用了了一下,确实挺不错的。 不过和 c# 的 linq 比起来, 还是差那么点意思, 主要还是受限于 java 的语法。
|
6
Braisdom OP |
7
Braisdom OP @beginor 感谢支持,下一阶段 ObjectiveSQL 项目的主要工作是:数据迁移(Migration) 和 SQL 注入(SQL Injection)
|
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):开放、高效、易用 |
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,写业务逻辑最优选择 |
11
Braisdom OP @chinvo 我之前没有接触过.NET 的 entity framework,也是最近才看到,
我设计 ObjectiveSQL 的主要目的是为了解决复杂 SQL 的处理,现有的框架维护动态 SQL 太痛苦,时间一长,项目根本无法维护。 简单 SQL 现有的框架已经做的很不错了,只是顺带把它做了而已 |
12
chinvo 2020-11-30 12:59:10 +08:00 via iPhone
@Braisdom #11 其实我比较好奇,很多语言都有类似 EF/LinQ 的 Orm,Java 生态圈竟然没有么 😂
我有了解过 Java 的几种 Orm,有一些是 xml 描述( Java 生态圈好像挺依赖 xml 的),有一些甚至还要写“类 SQL”,给我的直观感受就是难用、反人类 |
14
GM 2020-11-30 13:05:27 +08:00
|
15
Braisdom OP @chinvo 我也有同感,我之前写了很多年的 Java,中途写 Ruby 和 Python,也是最近两年才又写 Java,ORM 又是一个系统无法避免的问题,用起来太痛苦,所以才会写 ObjectiveSQL 这个项目的。
Java LINQ 也有,但很不好用,只能处理一些相对简单的查询,join 子查询,union,复杂表达式等(多层 case when 或者窗口函数等),这些处理起来很不舒服 |
18
renyijiu 2020-11-30 13:19:17 +08:00
有个不太相关的问题,是否支持 grpc protobuf ?
|
19
chinvo 2020-11-30 13:21:32 +08:00 via iPhone
@GM #17 分布式(Actor 模型)用 Orleans,多租户用 MapEndpoint,UoW 其实用不着,因为 EF 本身就是 UoW 模型
|
20
Braisdom OP @renyijiu protobuf 只是一个协议封装,用在数据传输的,现在比较流行,相比很早的 TLV 灵活很多。rpc 只是远程调用,现在微服务里比较流行
|
21
renyijiu 2020-11-30 13:39:01 +08:00
@Braisdom #20 可能理解不一致,可以直接映射使用 protobuf 生成的类,而不是再自己定一个 model 层的 class
|
22
DoctorCat 2020-11-30 14:06:22 +08:00
想起 10 年前用 SSH 的时候,jdbc template 的封装过程
|
24
Braisdom OP @renyijiu OK,之前是理解有对,这个问题我之前遇到过,ObjectiveSQL 已经兼容了,
之前我的一个项目是通过 ProtoBuffer 定义的模型,传输的数据极大,但需要直接存储进数据,如果中间再经过一层转换,性能太差,所以我就在 ObjectiveSQL 中设计了 DomainModelDescriptor,用于描述存储数据类型相关的信息,可以直接通过 ObjectiveSQL 进行数据库操作。 具体你可以参考: https://github.com/braisdom/ObjectiveSql/blob/master/core/src/main/java/com/github/braisdom/objsql/DomainModelDescriptor.java |
25
gowk 2020-11-30 16:24:14 +08:00
@GM
@chinvo ABP 确实对技术能力要求比较高,一般的团队感觉 hold 不住。我也在考虑,一般的中小型项目,怎么流畅的使用.NET 开发应用,专注于业务,有封装好的 boilerplate 推荐吗(不需要前后端分离) |
26
chinvo 2020-11-30 18:05:05 +08:00 via iPhone
|
28
beginor 2020-11-30 19:21:10 +08:00
@Braisdom 请问 objsql 支持动态查询么? 类似这样的 https://blog.jooq.org/tag/dynamic-sql/
|
29
Braisdom OP JOOQ 称为动态查询,在 ObjectiveSQL 里称为复杂查询,详细请查询:2 复杂 SQL 查询
|
30
Braisdom OP @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 中如果实现 |
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() |
32
beginor 2020-11-30 22:20:53 +08:00 via Android
是的,Java 还真没有好用 linq/lambda 框架
|
33
Braisdom OP @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); |
35
beginor 2020-11-30 22:37:47 +08:00 via Android
复杂查询和动态查询,是两个不同的概念,我不质疑 objsql 的复杂查询能力,只是想了解下是否支持动态查询
|
36
Braisdom OP @beginor 所谓动态查询也就是根据不同的参数,join 不同的表,或者选择不同的条件,因为参与拼接的对象都是变量,本身就是动态的。
|
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# 优越感的意思, 我只是好奇是否支持这种动态查询。 |
38
beginor 2020-12-01 08:42:41 +08:00
回复的格式有点儿乱, 可以看这个 gist https://gist.github.com/beginor/4bc9bfd25dfd9f488156cf4975b707f6
|
39
Braisdom OP 我也在 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); } |
40
zhangysh1995 2020-12-01 14:55:15 +08:00
性能咋样?看到过论文专门研究 ORM 性能问题的,有些会导致产生的 SQL 很慢?我对这个方向挺有兴趣的,我发个邮件聊聊呗。
|
41
Braisdom OP @zhangysh1995 [email protected]
ORM 性能问题不是慢 SQL,而是在大规模写和读时的性能,这块我还在优化,和 MyBatis 的性能差不多,但离 JDBC 原始 SQL 还是有差距。 |
42
zhangysh1995 2020-12-01 15:01:25 +08:00
@Braisdom 邮件已发,坐等回复。
|
43
xiaohuya 2022-01-20 22:56:01 +08:00
太像 rails 了,赞
|