首页 程序笔记 简单优雅的Java ORM

简单优雅的Java ORM

Java的ORM框架有很多,但由于Java语言的限制大部分都不够优雅也不够简单,所以作者只能另辟蹊径造轮子了。照旧先看示例代码了解个大概,然后再解释实现原理。

一、ORM示例

Insert

public CompletableFuture<Void> insert() {
    var obj = new sys.entities.Demo("MyName"); //构造参数为主键
    obj.Age = 100; //设置实体属性的值
    return obj.saveAsync();
}

Update

  • 更新单个实体(必须具备主键)
public CompletableFuture<Void> update(sys.entities.Demo obj) {
    obj.Age = 200;
    return obj.saveAsync();
}
  • 根据条件更新(必须指定条件以防误操作)
public CompletableFuture<?> update() {
    var cmd = new SqlUpdateCommand<sys.entities.Demo>();
    cmd.update(e -> e.City = "Wuxi");   //更新字段
    cmd.update(e -> e.Age = e.Age + 1); //更新累加字段
    cmd.where(e -> e.Name == "Johne");  //更新的条件
    var outs = cmd.output(e -> e.Age);  //更新的同时返回指定字段
    return cmd.execAsync().thenApply(rows -> {
        System.out.println("更新记录数: " + rows);
        System.out.println("返回的值: " + outs.get(0));
        return "Done.";
    });
}

Delete

  • 删除单个实体(必须具备主键)
public CompletableFuture<Void> update(sys.entities.Demo obj) {
    obj.markDeleted(); //先标记为删除状态
    return obj.saveAsync(); //再调用保存方法
}
  • 根据条件删除(必须指定条件以防误操作)
public CompletableFuture<?> delete() {
    var cmd = new SqlDeleteCommand<sys.entities.Demo>();
    cmd.where(e -> e.Age < 0 || e.Age > 200);
    return cmd.execAsync();
}

Transaction

  由于作者讨厌隐式事务,所以事务命令必须显式指定。

public CompletableFuture<?> transaction() {
    var obj1 = new sys.entities.Demo("Demo1");
    obj1.Age = 11;

    var obj2 = new sys.entities.Demo("Demo2");
    obj2.Age = 22;

    return DataStore.DemoDB.beginTransaction().thenCompose(txn -> { //开始事务
        return obj1.saveAsync(txn)                 //事务保存obj1
            .thenCompose(r -> obj2.saveAsync(txn)) //事务保存obj2
            .thenCompose(r -> txn.commitAsync());  //递交事务
    }).thenApply(r -> "Done");
}

Sql查询

  • Where条件
public CompletableFuture<?> query(String key) {
    var q = new SqlQuery<sys.entities.Demo>();
    q.where(e -> e.Age > 10 && e.Age < 80);
    if (key != null)
        q.andWhere(e -> e.Name.contains(key)); //拼接条件
    return q.toListAsync(); //返回List<sys.entities.Demo>
}
  • 分页查询
public CompletableFuture<?> query(int pageSize, int pageIndex) {
    var q = new SqlQuery<sys.entities.Demo>();
    return q.skip(pageSize * pageIndex)
        .take(pageSize)
        .toListAsync();
}
  • 结果映射至匿名类
public CompletableFuture<?> query() {
    var q = new SqlQuery<sys.entities.Demo>();
    return q.toListAsync(e -> new Object() { //返回List<匿名类>
        public final String Name = e.Name; //匿名类属性 = 实体属性表达式
        public final int    Age = e.Age + 10;
        public final String Father = e.Parent.Name;
    }).thenApply(appbox.data.JsonResult::new);
}
  • 结果映射至继承的匿名类
public CompletableFuture<?> query() {
    var q = new SqlQuery<sys.entities.Demo>();
    q.where(e -> e.Parent.Name == "Rick");
    return q.toListAsync(e -> new sys.entities.Demo() { //返回List<? extens Demo>
        public final String Father = e.Parent.Name;
    });
}
  • 结果映射至树状结构列表
public CompletableFuture<?> tree() {
    var q = new SqlQuery<sys.entities.Demo>();
    q.where(t -> t.Name == "Rick");
    return q.toTreeAsync(t -> t.Childs); //参数指向EntitySet(一对多成员)
}
  • EntityRef(一对一引用的实体成员)自动Join
public CompletableFuture<?> query() {
    var q = new SqlQuery<sys.entities.Customer>();
    q.where(cus -> cus.City.Name == "Wuxi");
    return q.toListAsync();
}

生成的Sql:
Select t.* From "Customer" t Left Join "City" j1 On j1."Code"=t."CityCode"
  • 手工指定Join
public CompletableFuture<?> join() {
    var q = new SqlQuery<sys.entities.Customer>();
    var j = new SqlQueryJoin<sys.entities.City>();

    q.leftJoin(j, (cus, city) -> cus.CityCode == city.Code);
    q.where(j, (cus, city) -> city.Name == "Wuxi");
    return q.toListAsync();
}
  • 子查询
public CompletableFuture<?> subQuery() {
    var sq = new SqlQuery<sys.entities.Demo>();
    sq.where(s -> s.ParentName == "Rick");
    
    var q = new SqlQuery<sys.entities.Demo>();
    q.where(t -> DbFunc.in(t.Name, sq.toSubQuery(s -> s.Name)));
    return q.toListAsync();
}
  • GroupBy
public CompletableFuture<?> groupBy() {
    var q = new SqlQuery<sys.entities.Demo>();
    q.groupBy(t -> t.ParentName) //多个可重复
        .having(t -> DbFunc.sum(t.Age) > 10);
    return q.toListAsync(t -> new Object() {
        public final String group = t.ParentName == null ? "可怜的孩子" : t.ParentName;
        public final int totals = DbFunc.sum(t.Age);
    }).thenApply(appbox.data.JsonResult::new);
}

二、实现原理

  其实以上的示例代码并非最终运行的代码,作者利用Eclipse jdt将上述代码在编译发布服务模型时分析转换为最终的运行代码,具体过程如下:

1. jdt分析服务虚拟代码生成AST抽象语法树;

2. 遍历AST树,将实体对象的读写属性改写为getXXX(), setXXX();

var name = obj.Name; //读实体属性
obj.Name = "Rick";   //写实体属性

改写为:

var name = obj.getName();
obj.setName("Rick");

3. 遍历AST树,将查询相关方法的参数转换为运行时表达式;

public CompletableFuture<?> query(String key) {
    var q = new SqlQuery<sys.entities.Employee>();
    q.where(e -> e.Manager.Name + "a" == key + "b");
    return q.toListAsync();
}

转换为:

public CompletableFuture<?> query(String key) {
    var q = new appbox.store.query.SqlQuery<>(-7018111290459553788L, SYS_Employee.class);
    q.where(e -> e.m("Manager").m("Name").plus("a").eq(key + "b"));
    return q.toListAsync();
}

4. 根据服务模型使用到的实体模型生成相应实体的运行时代码;

5. 最后编译打包服务模型的字节码。

以上请参考源码的ServiceCodeGenerator及EntityCodeGenerator类。

三、性能与小结

  作者写了个简单查询的服务,测试配置为MacBook主机(wrk压测 + 数据库)->4核I7虚拟机(服务端),测试结果如下所示qps可达1万,已包括实体映射转换及序列化传输等所有开销。这里顺便提一下,由于框架是全异步的,所以没有使用传统的JDBC驱动,而是使用了jasync-sql(底层为Netty)来驱动数据库。

wrk -c200 -t2 -d20s -s post_bin.lua http://10.211.55.8:8000/api
Running 20s test @ http://10.211.55.8:8000/api
  2 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    18.97ms    5.84ms  89.15ms   81.55%
    Req/Sec     5.32k   581.92     6.48k    65.00%
  211812 requests in 20.02s, 36.76MB read
Requests/sec:  10578.90
Transfer/sec:      1.84MB

 

4

声明 本站内容部分来源于网络,仅供参考学习交流并不代表本站观念,如无意中侵犯您的权益( 包括/图片/视频/个人隐私等信息 )请来信告知,本站收到信息会尽快处理并回访,联系邮箱:laodilailiao@foxmail.com

站心网

Java的ORM框架有很多,但由于Java语言的限制大部分都不够优雅也不够简单,所以作者只能另辟蹊径造轮子了。..

为您推荐

mysql分表简单介绍

一、Mysql分表的原因1、当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会停在那儿了。分表的目的就在于此,减小数据库的负担,缩短查询时间。2、mysql中有一种机制是表锁定..

写给那些想要自学成才的java程序员

自学java没那么难一:个人经历我的大学:第一年泡在图书馆看杂七杂八的书,跟学习毫无关系。第二年疯狂打LOL,从白银打到黄铜(黄铜守门员)。第三年上半年,被某人点醒,学习了整套C#知识体系,某马。下半年又决定..

跳槽!Java面试经验总结

0.前言笔者在不足两年经验的时候从成都一家金融科技中厂跳槽到杭州阿里淘天集团,又于今年5月份从杭州淘天跳槽到成都字节。自认为自己在面试这方面有一点心得,处于记录和分享的目的便有了此文,此文纯主观,也许对3..

一个简单的 C# 异步日志记录器

Clearcove.Logging是一个非常简单的日志库,旨在通过直接许可条款满足大多数日志记录需求。介绍我知道你在想什么——代码世界真的需要另一个日志库吗?如果你在.NET中寻找一个日志库,那么你有很多选择。有NLog,Log..

Java中String类常见的方法

以下介绍字符串常见的几个方法。介绍String类在 Java 中,String类是一个代表字符串的类,具有以下特性:不可变性:String对象一旦被创建就是不可变的,即它们的值在创建后不能被更改。任何对String对象的修改操作实..

一个简单的大转盘抽奖程序(附.NetCore Demo源码)

最近闲下来在做一些demo,现在讲一下做的一个简单的大转盘抽奖demo,前端lottery,layui,后端.net core,sqlsugar,数据库用的mysql1.前端实现:前端用的是基于开源的lottery,其中有些改动的,使得前端可以自适应pc端..

轻量级 JavaScript 动画库 mo.js使用教程

mo.js 是一个强大的 JavaScript 动画库,专为在网页项目中创建复杂动画和运动图形而设计。它注重提供平滑、动态的动画效果,并通过简单、模块化和灵活的组件让开发更加便捷。mo.js官网地址:https://mojs.github.io/..

文件上传JavaScript库FilePond使用教程

传统的文件上传控件往往显得笨拙且不够用户友好。FilePond的出现,为Web文件上传带来了革命性的改变。本文将详细介绍FilePond这一JavaScript库,探讨它如何优化文件上传流程,并提供无与伦比的用户体验。什么是FileP..

Swapy - 开源JavaScript js拖拽插件

Swapy是一个简单易用的JavaScript工具,能够将任何布局转换为拖拽交换布局。本文将详细介绍Swapy的功能、如何使用它,以及它在实际项目中的应用。什么是Swapy?Swapy是由TahaSh开发的一款开源JavaScript工具。它的核..

JavaScript 的 sessionStorage 能否加锁?

直接给 sessionStorage 加锁是不可能的。sessionStorage 的本质: sessionStorage 是浏览器提供的一种用于在当前浏览器会话中存储数据的机制。它存储在客户端,数据仅在当前浏览器窗口或标签页中有效。加锁的必要性..

Dapper使用教程

假如你喜欢原生的Sql语句,又喜欢ORM的简单,那你一定会喜欢上Dapper这款ROM。Dapper的优势: 1、Dapper是一个轻型的ORM类。代码就一个SqlMapper.cs文件,编译后就40K的一个很小的Dll. 2、Dapper很快。Dapper的速度..

c#实现与Java无差异的GZip压缩和GZip解压缩

c#实现与Java无差异的GZip压缩和GZip解压缩,其中有个坑就是GZip压缩的时候,只有在GZipStream在Dispose后调应对应MemoryStream.ToArray()所得到的结果才是正确的压缩数据。如果在zipStream.Write(bytes, 0, bytes.L..

介绍Js简单的递归排列组合

最近在开发SKU模块的时候,遇到这样一个需求,某种商品有N(用未知数N来表示是因为规格的数组由用户制定且随时可以编辑的,所以对程序来说,它是一个未知数)类规格,每一类规格又有M个规格值,各种规格值的组合便是..

JavaScript设计模式学习网站Patterns

今天分享一个Javascript学习网站patterns。patterns官网:https://www.patterns.dev/patterns学习书籍下载地址:https://www.patterns.dev/book改进构建 Web 应用程序的方式Patterns.dev 是一本关于设计模式和组件模..

.NET中运行Java代码

在.NET中使用Java代码可以通过不同的方法和工具来实现,尤其是通过一些桥接工具或者中间件来进行跨语言的操作。以下是一些常见的方法:1. Java和.NET的互操作性:通过跨语言框架:IKVM.NET: 这是一个开源项目,允许..

使用IKVM.NET在.NET中运行Java代码

IKVM.NET是一个开源项目,它允许Java字节码在.NET平台上运行。通过IKVM.NET,可以在.NET环境中直接调用Java类和库。以下是使用IKVM.NET的基本步骤:步骤一:安装和配置下载和安装: 下载并安装IKVM.NET。你可以在官..

使用C#的Socket实现最简单的TCP通信示例代码

使用C#实现TCP通信有多个类可以使用,最简单的是直接使用TcpListener+TcpClient类,但有时候这两个类并不方便做更底层的一些配置。而使用Socket类可以做一些比较底层的TCP配置。这里给出使用Socket实现最简单的TCP通..

简单了解Docker的概念和作用

什么是Docker?Docker是一个基于轻量级虚拟化技术的容器,整个项目基于Go语言开发,并采用了Apache 2.0协议。Docker可以将我们的应用程序打包封装到一个容器中,该容器包含了应用程序的代码、运行环境、依赖库、配置..

在C#中通过Windows API读写INI文件的简单实现代码

摘要INI文件是比较常用的配置文件格式,在C#中,没有提供对INI文件直接读写的类库,要想实现INI文件的读写,需要借助Windows API GetPrivateProfileString和WritePrivateProfileString两个方法来实现。INI写入字符串..

2023年学.NET还是Java好?

2023年学.NET还是Java,取决于您未来的职业规划和个人兴趣。.NET 是微软推出的开源软件开发平台,它包括了 .NET Core、.NET Framework 等框架。.NET 主要用于构建 Windows 平台应用程序,但也支持跨平台开发。Java ..

发表回复

返回顶部