使用 MyBatis 操作 Nebula Graph 的實踐

語言: CN / TW / HK

本文首發於 Nebula Graph Community 公眾號

使用 MyBatis 操作 Nebula Graph 的實踐

我最近注意到很多同學對於 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 小助手會拉你進群~~