本文首發於 NebulaGraph 公眾號 https://mp.weixin.qq.com/s/z56o6AEz1Z4RmS8Zdx6dTA
大家好,我是開源專案 NGbatis 的發起人大葉(CorvusYe@GitHub)。目前 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>
呼叫
注入:
@Autowired private PersonDao dao;
呼叫自定義介面
Person tom = dao.selectByName("Tom");
更多檔案:自定義 nGQL
呼叫基類介面
// 不管屬性是否為空,如果資料庫中已有對應 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 使用者一起交流圖資料庫技術和應用技能,留下「你的名片」一起玩耍呢~