ObjectiveSQL 稳定版本发布 1.4.0

2020-11-30 11:15:39 +08:00
 Braisdom

你可能喜欢他,也有可能讨厌他,就像 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`
3366 次点击
所在节点    程序员
43 条回复
renyijiu
2020-11-30 13:39:01 +08:00
@Braisdom #20 可能理解不一致,可以直接映射使用 protobuf 生成的类,而不是再自己定一个 model 层的 class
DoctorCat
2020-11-30 14:06:22 +08:00
想起 10 年前用 SSH 的时候,jdbc template 的封装过程
GM
2020-11-30 15:00:11 +08:00
@chinvo Orleans 看起来很不错啊,谢谢。另外,UoW 指的是 Unit of Work 吗?
Braisdom
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
2020-11-30 16:24:14 +08:00
@GM
@chinvo
ABP 确实对技术能力要求比较高,一般的团队感觉 hold 不住。我也在考虑,一般的中小型项目,怎么流畅的使用.NET 开发应用,专注于业务,有封装好的 boilerplate 推荐吗(不需要前后端分离)
chinvo
2020-11-30 18:05:05 +08:00
@GM #23
@gowk #25

这毕竟是楼主关于自己作品的帖子,咱们持续在这里这样讨论其他话题有点不礼貌,如果要深入讨论 .net 、abp 等问题咱们可以另开一贴
renyijiu
2020-11-30 18:59:30 +08:00
@Braisdom #24 感谢,我看看这块
beginor
2020-11-30 19:21:10 +08:00
@Braisdom 请问 objsql 支持动态查询么? 类似这样的 https://blog.jooq.org/tag/dynamic-sql/
Braisdom
2020-11-30 21:37:34 +08:00
JOOQ 称为动态查询,在 ObjectiveSQL 里称为复杂查询,详细请查询:2 复杂 SQL 查询
Braisdom
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
2020-11-30 22:19:22 +08:00
@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
2020-11-30 22:20:53 +08:00
是的,Java 还真没有好用 linq/lambda 框架
Braisdom
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
2020-11-30 22:35:19 +08:00
@Braisdom 这段代码绝对算是复杂查询, 但是不是我说的动态查询
beginor
2020-11-30 22:37:47 +08:00
复杂查询和动态查询,是两个不同的概念,我不质疑 objsql 的复杂查询能力,只是想了解下是否支持动态查询
Braisdom
2020-12-01 07:54:54 +08:00
@beginor 所谓动态查询也就是根据不同的参数,join 不同的表,或者选择不同的条件,因为参与拼接的对象都是变量,本身就是动态的。
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
2020-12-01 08:42:41 +08:00
回复的格式有点儿乱, 可以看这个 gist https://gist.github.com/beginor/4bc9bfd25dfd9f488156cf4975b707f6
Braisdom
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
2020-12-01 14:55:15 +08:00
性能咋样?看到过论文专门研究 ORM 性能问题的,有些会导致产生的 SQL 很慢?我对这个方向挺有兴趣的,我发个邮件聊聊呗。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/730512

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX