這麼優雅的Java ORM沒見過吧!

白菜園發表於2021-01-13

  Java的ORM框架有很多,但由於Java語言的限制大部分都不夠優雅也不夠簡單,所以作者只能另闢蹊徑造輪子了。照舊先看示例程式碼瞭解個大概,然後再解釋實現原理。

一、ORM示例

1. Insert

public CompletableFuture<Void> insert() {
    var obj = new sys.entities.Demo("MyName"); //構造引數為主鍵
    obj.Age = 100; //設定實體屬性的值
    return obj.saveAsync();
}

2. 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.";
    });
}

3. 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();
}

4. 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");
}

5. 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

邊碼程式碼邊碼文實屬不易,作者需要您的支援請您多多點贊推薦!另歡迎感興趣的小夥伴加入我們!

相關文章