本文首發于 NebulaGraph 公衆号 https://mp.weixin.qq.com/s/z56o6AEz1Z4RmS8Zdx6dTA
大家好,我是開源項目 NGbatis 的發起人大葉([CorvusYe@GitHub](https://github.com/CorvusYe))。目前 NGbatis 也已成為 NebulaGraph 開源生态項目之一。在過去的 4 個月裡,NGbatis 從送出第一行代碼以來,已經釋出了 3 個版本,正在一步步變得越來越好。感謝一路同行的人們。
這裡給大家貼上倉庫位址:https://github.com/nebula-contrib/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 為例,即:
根據最樸素的 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'
六、全局流程圖
七、NGbatis 該如何內建到自己的 Springboot 項目
- 添加依賴
<dependency>
<groupId>org.nebula-contrib</groupId>
<artifactId>ngbatis</artifactId>
<version>1.1.0-rc</version>
</dependency>
- 配置 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
- 添加掃描包以引入 NGbatis bean
@SpringBootApplication(
exclude={ DataSourceAutoConfiguration.class },
scanBasePackages = { "org.nebula.contrib", "your.domain" } )
public class YourSpringbootApplication {
}
- 聲明主鍵生成器
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;
}
}
到此,對于內建工作來說,任務已經完成,剩下就是開發的工作了。
開發人員隻需要做三件事:
- 定義接口:
package your.domain;
import org.nebula.contrib.ngbatis.proxy.NebulaDaoBasic;
public interface PersonDao extends NebulaDaoBasic<Person, String> {
Person selectByName( @Param("name") String param );
}
- 在 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>
調用
注入:
調用自定義接口
- Person tom = dao.selectByName("Tom");
調用基類接口
// 不管屬性是否為空,如果資料庫中已有對應 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 它 ~