Springboot 撞上 NebulaGraph——NGbatis 初體驗

NebulaGraph發表於2023-03-24

Springboot 撞上 NebulaGraph——NGbatis 初體驗

本文首發於 NebulaGraph 公眾號 https://mp.weixin.qq.com/s/z56o6AEz1Z4RmS8Zdx6dTA

大家好,我是開源專案 NGbatis 的發起人大葉(CorvusYe@GitHub)。目前 NGbatis 也已成為 NebulaGraph 開源生態專案之一。在過去的 4 個月裡,NGbatis 從提交第一行程式碼以來,已經發布了 3 個版本,正在一步步變得越來越好。感謝一路同行的人們。

這裡給大家貼上倉庫地址:https://github.com/nebula-contrib/ngbatis,歡迎大家在倉庫下方留言提出建議反饋。

一、目前有哪些參與者?

Springboot 撞上 NebulaGraph——NGbatis 初體驗

其中,@Szt-1 做了和 Spring Cloud 和 Nacos 的相容,@liuxiaocs7 完善了文件,@soul-gin 做了 Java 與資料庫之間屬性別名的對映,@Nicole00 做了專案自動化與程式碼規範,@wey-gu 提了很多有利於專案發展的建議並做了國際化。@DawnZzzzz@hejiahuichengxuyuan@yarodai@LiuTianyou 則提了不少 issues,issues 讓人獲得不少靈感。

可以說現階段的 NGbatis 是使用者與開發者想法碰撞後的共同產物

二、什麼是 NGbatis?

NGbatis 是一款針對 NebulaGraph + Spring Boot 的資料庫 ORM 框架。借鑑於 MyBatis 的使用習慣進行開發。包含了一些類似於 mybatis-plus 的單表操作,另外還有一些圖特有的實體-關係基本操作。

如果是 Java 後端服務的開發人員,相信看到這裡,大家對 NGbatis 的用途有了比較清晰的理解。接下來會從幾個問題出發,跟讀者們介紹 NGbatis:

  • 關於 NGbatis 有哪些思考?
  • NGbatis 能做什麼?
  • NGbatis 是怎麼實現的?
  • NGbatis 怎麼使用?

三、關於 NGbatis 有哪些思考?

  • Q: 最原始的訴求是什麼?

    • A: 與 MyBatis 相同,想實現 GQL 與 Java 程式碼的分離 。
  • Q: 為什麼不直接使用 MyBatis 整合?

    • A:

      • MyBatis 遵循 JDBC 規範,而 JDBC 規範更適合於傳統資料庫,圖資料庫存在與傳統資料庫不同的、圖特有的結構,如果採用 JDBC 規範,會受到一定侷限。
      • 想為圖資料庫量身定製一款 ORM,隨著圖資料庫的發展,方便擴充。
  • Q: 是否可以基於 JDBC 擴充出 GJDBC 的規範?

    • A: 個人能力有限,不敢想,或許 NebulaGraph 官方可以考慮下。
  • Q: 為什麼版本號從 v1.1.0 開始,缺失了 v1.0.0 的版本號?

    • A:

      • 最開始的版本是用來適配 Neo4j,後來選用了 NebulaGraph,保留了一個不曾釋出的小版本。
      • 第一次接觸的 NebulaGraph 是 v3.1.0,相容性方面重點放在 v3.1.0+ 的版本

以上,便是開發之初對 NGbatis 的一些方案選擇的思考,做了一些取捨,是好處多一些還是壞處多一些,我自己目前也還在糾結中。比如說放棄 JDBC 的規範後也意味著放棄其背後的生態,比如說優秀的第三方連線池方案。

糾結歸糾結,既然做了決定,路還是要往下走。開胃菜上完了,也該上正餐了。

四、NGbatis 能做什麼?

一個專案誕生最恰當的理由是:想要用它解決一些問題。以解決問題為中心,可以讓專案走得更遠。NGbatis 的任務就是儘可能地減少日常開發中或重複或繁瑣的工作。

  • 在程式碼裡頻繁地做“字串”+”字串”
  • 一遍一遍地重複處理 ResultSet -> 業務物件
  • 重複寫單表基本的增、刪、改、查
  • 在整合時,做過多配置,為什麼萬事就一定是開頭難,簡單點,整合的方式簡單點
  • 需要關注與業務關係不是很密切的 Session 問題

我們生活在一個基礎設施相對完善的時代,好處在於問題產生的同時,答案的模型也同時存在,我們需要做的只是在問題與答案之間做適配,這裡真誠地對作出貢獻的前輩們表示感謝。

以上問題就要求 NGbatis 需要做到以下幾點:

  • 開箱即用,實現與 Springboot、Springcloud 的快速整合
  • 實現 GQL 與 Java 程式碼分離,使用 XML 統一管理
  • 使用模板引擎,解決 GQL 引數拼接繁瑣、容易寫錯的問題
  • 實現 ResultSet 與 Java 物件根據屬性名自動轉換
  • 單表基本增、刪、改、查以及分頁
  • 本地 Session 管理,降低資源消耗

方向有了,剩下的就是工程問題了。

五、NGbatis 是怎麼實現的?

我們最本質的要求就是:把 GQL 語句執行到 NebulaGraph 當中。我們以帶參的 Hello Nebula 為例,即:

Springboot 撞上 NebulaGraph——Ngbatis 初體驗

根據最樸素的 Java 開發方法,可以想到的是:先透過 XML 給 GQL 定義一個座標,再定義一個介面,最後編寫一個實現類按座標讀取 GQL 語句,使用模板引擎替換引數。即:

  • XML
<mapper namespace=
        "com.example.dao.TestDao">

    <select id="greet">
        RETURN 'Hello ${ p0 }'
    </select>

</mapper>
  • DAO 介面
package com.example.dao;

public interface TestDao {
  String greet(String who);
}
  • DAO 實現(虛擬碼)
package com.example.dao;

public class TestDaoImpl implements TestDao {

  @Override
  public String greet(String who) {
    Object[] var2 = new Object[]{who};
    String namespace = "com.example.dao.TestDao";
    String methodName = "greet";
    // 有一個函式,可以完成以下事情:
    // 1. 根據座標讀取 GQL
    // 2. 使用模板引擎完成引數拼接(Beetl)
    // 3. 執行到資料庫
    // 4. 轉換 ResultSet 形成 業務物件
    return foo( namespace, methodName, var2 );
  }
}

做到這裡其實就剩下 foo 怎麼編寫的問題了。到這裡,相信讀者們都有自己的思路。大家有興趣的話可以參考
org.nebula.contrib.ngbatis.proxy.MapperProxy

但這裡引入了另一個問題:每個 dao 的方法,寫法基本是一樣的,又帶來了重複的工作,有悖於 NGbatis 的初衷。因此,使用動態代理,從 XML 與 DAO 資訊中自動生成 TestDao$Proxy,這邊使用的代理方案是基於位元組碼技術 ASM 來生成。上述的例子生成的位元組碼反編譯後的結果如下:

package com.example.dao;

import org.nebula.contrib.ngbatis.proxy.MapperProxy;

public class TestDao$Proxy implements TestDao {

  @Override
  public String greet(String var1) {
    Object[] var2 = new Object[]{var1};
    return (String) MapperProxy.invoke( "com.example.dao.TestDao", "greet", var2 );
  }
}

因此,開發者便不需要再重複編寫諸多 TestDaoImpl,定義好 XML 與 DAO,剩下的工作可以放心地交給 NGbatis。

最後剩下一個問題,引數替換問題:這個問題應該是與開發者關係最為密切的問題。所以,這裡不得不提的模板引擎框架:Beetl 是國內流行模板引擎,也是 NGbatis 一個重要的組成部分,連結是官網的 API。

  • 在呼叫時,將入參 json 化成 nebula-java 可以接收的引數形式(List、Set、Map、字串、基本型別...):
                                                  {
String hello = dao.greet(“Nebula”);       -->       “p0”: “Nebula”
                                                  }
  • 最後以 XML 內容為模板,進行替換:
RETURN 'Hello ${ p0 }'                  -->        RETURN 'Hello Nebula'

六、全域性流程圖

Springboot 撞上 NebulaGraph——Ngbatis 初體驗

七、NGbatis 該如何整合到自己的 Springboot 專案

  1. 新增依賴
    <dependency>
        <groupId>org.nebula-contrib</groupId>
        <artifactId>ngbatis</artifactId>
        <version>1.1.0-rc</version>
    </dependency>
  1. 配置 NebulaGrpah 資料庫
nebula:
  hosts: 127.0.0.1:19669, ip:port, ....
  username: root
  password: nebula
  space: test
  pool-config:
    min-conns-size: 0
    max-conns-size: 10
    timeout: 0
    idle-time: 0
    interval-idle: -1
    wait-time: 0
    min-cluster-health-rate: 1.0
    enable-ssl: false
  1. 新增掃描包以引入 NGbatis bean
@SpringBootApplication(
  exclude={ DataSourceAutoConfiguration.class }, 
  scanBasePackages = {  "org.nebula.contrib", "your.domain" }  )
public class YourSpringbootApplication {

}
  1. 宣告主鍵生成器
import org.nebula.contrib.ngbatis.PkGenerator;
@Component
public class CustomPkGenerator implements PkGenerator {

    @Override
    public <T> T generate(String tagName, Class<T> pkType) {
        Object id = null; // 此處自行對 id 進行設值。
        return (T) id;
    }

}

到此,對於整合工作來說,任務已經完成,剩下就是開發的工作了。

開發人員只需要做三件事:

  1. 定義介面:
package your.domain;

import  org.nebula.contrib.ngbatis.proxy.NebulaDaoBasic;

public interface PersonDao extends NebulaDaoBasic<Person, String> {
    Person selectByName( @Param("name") String param );
}
  1. 在 resources/mapper/PersonDao.xml 中編寫 GQL
<mapper namespace="your.domain.PersonDao">
    <select id="selectByName">
        MATCH (n: person)
        WHERE n.person.name == $name
        RETURN n
        LIMIT 1
    </select>
</mapper>
  1. 呼叫

    1. 注入:

          @Autowired private PersonDao dao;
    2. 呼叫自定義介面

      Person tom = dao.selectByName("Tom");
      更多文件:自定義 nGQL
    3. 呼叫基類介面

        // 不管屬性是否為空,如果資料庫中已有對應 id 的值,則覆蓋
        public void insert( Person person ) {
          dao.insert( person );
        }
      
        // 僅寫入非空屬性
        public void insertSelective( Person preson ) {
          dao.insertSelective( person );
        }
      
        // 此處,Person 的主鍵欄 name 為 String ,則入參為 String
        public Person selectById( String id ) {
          return dao.selectById( id );
        }
      
        // 按屬性查詢
        public List<Person> selectBySelective( Person person ) {
          return dao.selectBySelective( person );
        }
        
      更多文件:使用基類讀寫

八、尾聲

以上就是本次交流的全部內容。如果 NGbatis 實現方式也是你喜歡的,issue、pr、star 都是 ok 的。如果對專案感興趣,也可以參與到開發中來,從中獲得成就感。倉庫地址:https://github.com/nebula-contrib/ngbatis

最後,希望 NGbatis 能給越來越多的開發者帶來開發上的便利。


謝謝你讀完本文 (///▽///)

NebulaGraph Desktop,Windows 和 macOS 使用者安裝圖資料庫的綠色通道,10s 拉起搞定海量資料的圖服務。通道傳送門:http://c.nxw.so/blVC6

想看原始碼的小夥伴可以前往 GitHub 閱讀、使用、(^з^)-☆ star 它 -> GitHub;和其他的 NebulaGraph 使用者一起交流圖資料庫技術和應用技能,留下「你的名片」一起玩耍呢~