当前位置 博文首页 > 这么优雅的Java ORM没见过吧!

    这么优雅的Java ORM没见过吧!

    作者:白菜园 时间:2021-01-13 18:04

    ??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
    

    边码代码边码文实属不易,作者需要您的支持请您多多点赞推荐!另欢迎感兴趣的小伙伴加入我们!

    下一篇:没有了