使用 MyBatis 操作 Nebula Graph 的實踐
本文首發於 Nebula Graph Community 公眾號
我最近注意到很多同學對於 ORM 框架的需求比較迫切,而且有熱心的同學已經捐贈了自己開發的專案,Nebula 社群也在 working on it。下面主要介紹一下我們在使用 MyBatis 操作 Nebula Graph 方面的一些經驗,希望能夠幫助到大家。
MyBatis
Java 開發的同學想必對 MyBatis 都比較熟悉了。MyBatis 是一款優秀的持久層框架,它支援自定義 SQL、儲存過程以及高階對映,並且免除了幾乎所有的 JDBC 程式碼以及設定引數和獲取結果集的工作。
實現途徑
主要通過 MyBatis 結合 nebula-jdbc 來實現引數返回值對映以及語句執行。
Demo 示例
完整程式碼參見 GitHub 。
Nebula Schema
CREATE SPACE basketballplayer(partition_num=10,replica_factor=1,vid_type=fixed_string(32)); CREATE TAG IF NOT EXISTS player(name string, age int); CREATE EDGE IF NOT EXISTS follow(degree int);
工程結構
application.yaml
spring: datasource: driver-class-name: com.vesoft.nebula.jdbc.NebulaDriver url: jdbc:nebula://localhost:9669/basketballplayer username: nebula password: nebula hikari: maximum-pool-size: 20 mybatis: mapper-locations: classpath:mapper/*.xml
DO
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class PlayerDO { /** * vid */ private String id; private String name; private Long age; }
Dao
public interface PlayerDao { int insert(PlayerDO entity); int update(PlayerDO entity); int insertBatch(List<PlayerDO> batch); PlayerDO select(String id); List<PlayerDO> selectBatch(List<String> batch); int delete(String id); int deleteBatch(List<String> batch); //以上程式碼自動生成 PlayerDO selectReturnV(String id); }
Mapper
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.dao.PlayerDao"> <!-- 通用查詢對映結果 --> <resultMap id="BaseResultMap" type="com.example.pojo.PlayerDO"> <result column="id" property="id" jdbcType="VARCHAR"/> <result column="name" property="name" jdbcType="VARCHAR"/> <result column="age" property="age" jdbcType="BIGINT"/> </resultMap> <!-- 插入點或邊 --> <insert id="insert" parameterType="com.example.pojo.PlayerDO"> insert vertex `player` ( <trim suffixOverrides=","> <if test="name != null"> name, </if> <if test="age != null"> age, </if> </trim> ) values #{id} :( <trim suffixOverrides=","> <if test="name != null"> #{name}, </if> <if test="age != null"> #{age}, </if> </trim> ) </insert> <!-- 批量插入點或邊--> <insert id="insertBatch" parameterType="com.example.pojo.PlayerDO"> insert vertex `player` <trim prefix="(" suffix=")" suffixOverrides=","> name, age, </trim> values <foreach collection="list" item="item" separator=","> #{item.id} : <trim prefix="(" suffix=")" suffixOverrides=","> #{item.name}, #{item.age}, </trim> </foreach> </insert> <!-- 更新點或邊 --> <update id="update" parameterType="com.example.pojo.PlayerDO"> UPDATE vertex ON `player` #{id} <trim prefix="set" suffixOverrides=","> <if test="name != null"> name = #{name}, </if> <if test="age != null"> age = #{age}, </if> </trim> </update> <!-- 查詢點 --> <select id="select" resultType="com.example.pojo.PlayerDO"> match (v:`player`) where id(v) == #{id} return <trim suffixOverrides=","> id(v) as id, v.name as name, v.age as age, </trim> </select> <!-- 批量查詢點 --> <select id="selectBatch" resultType="com.example.pojo.PlayerDO"> match (v:`player`) where id(v) in [ <foreach collection="list" item="item" separator=","> #{item} </foreach> ] return <trim suffixOverrides=","> id(v) as id, v.name as name, v.age as age, </trim> </select> <!-- 刪除點或邊 --> <delete id="delete" parameterType="java.lang.String"> delete vertex #{id} </delete> <!-- 批量刪除點或邊 --> <delete id="deleteBatch" parameterType="java.lang.String"> delete vertex <foreach collection="list" item="item" separator=","> #{item} </foreach> </delete> <!--以上程式碼自動生成--> <select id="selectReturnV" resultMap="BaseResultMap"> match (v:`player`) where id(v) == #{id} return v </select> </mapper>
Tag 操作
@SpringBootTest public class PlayerDaoTest { @Resource private PlayerDao playerDao; @Test public void operation() { //insert PlayerDO player = PlayerDO.builder().id("daiyi").name("daiyi").age(22l).build(); playerDao.insert(player); //insertBatch PlayerDO playerBatch = PlayerDO.builder().id("daiyi").name("daiyi").age(22l).build(); PlayerDO joe = PlayerDO.builder().id("joe").name("joe").age(24l).build(); playerDao.insertBatch(Lists.newArrayList(playerBatch, joe)); //update playerDao.update(PlayerDO.builder().id("daiyi").name("daiyiupdate").build()); //select PlayerDO playerDO = playerDao.select("daiyi"); //selectBatch List<PlayerDO> players = playerDao.selectBatch(Lists.newArrayList("daiyi", "joe")); //selectReturnV playerDao.selectReturnV("daiyi"); //delete playerDao.delete("daiyi"); //deleteBatch playerDao.deleteBatch(Lists.newArrayList("daiyi", "joe")); } }
Edge 及 Path 操作
篇幅有限,詳情可以參見 github 。
版本適配
目前僅支援了 Nebula 2.5 版本,後續版本的支援還在適配中。
總結
優點
- 使用簡單,消除了使用 JDBC 或 nebula-client 帶來的冗餘程式碼。
- 可以使用配套連線池管理連線,並且可以與 Spring Boot 無縫銜接。
- nGQL 與程式碼解耦,方便管理。
- 大量便捷標籤,免除了程式碼拼接語句的煩惱。
存在的問題
- 針對返回值為 Vertex(類似
MATCH v RETURN v
)、Edge、無屬性 Path 的型別目前採用在 MyBatis 中的 Interceptor 做攔截處理,也能滿足使用。但這種實現方式感覺不是很好,後期有待優化。 - 對於返回值型別為帶屬性 Path、多 Tag 查詢以及 GET SUBGRAPH 語句的情況,因為返回的結果中實體以及邊的型別可能有多種,目前沒有想到比較好的對映方式也就沒有支援。
- 上述示例中使用的 JDBC 驅動是我們自己開發的版本(詳見 http:// github.com/DA1Y1/nebula -jdbc ),與社群版的主要區別在 URL 上服務地址的指定以及⼀些轉義字元的處理,後續也希望能將這些 Feature 合併到社群版本中,統⼀使⽤。
為了方便使用我們還開發了類似 mybatis-generator 這種工具來生成一些基礎程式碼,提供基本的增刪改查功能。感興趣的同學可以在 IDEA 的 Plugins 中搜索 Nebula Generator 下載,使用方式參見: http:// plugins.jetbrains.com/p lugin/18026-nebula-generator
最後感謝 @DA1Y1 以及其他幾位小夥伴的貢獻!
交流圖資料庫技術?加入 Nebula 交流群請先 填寫下你的 Nebula 名片 ,Nebula 小助手會拉你進群~~
「其他文章」
- 一文了解 NebulaGraph 上的 Spark 專案
- 使用 MyBatis 操作 Nebula Graph 的實踐
- GraphX 圖計算實踐之模式匹配抽取特定子圖
- 圖資料庫|如何從零到一構建一個企業股權圖譜系統?
- GitHub 自動合併 pr 的機器人——auto-merge-bot
- 圖資料庫|正反向邊的最終一致性——TOSS 介紹
- Nebula Graph 在網易遊戲業務中的實踐
- Nebula Graph 在企查查的應用
- 基於 BDD 理論的 Nebula 整合測試框架重構(下篇)
- 圖資料庫 Nebula Graph 叢集通訊:從心跳說起
- 在 Spark 資料匯入中的一些實踐細節
- GraphX 在圖資料庫 Nebula Graph 的圖計算實踐
- 除錯 Docker 容器內部的 Nebula Graph 程序