使用Mybatis操作Nebula Graph的实践

最近有注意到很多同学对于ORM框架的需求比较迫切,有热心的同学已经捐赠了自己开发的项目,社区也在 working on it。下面主要介绍一下我们在使用Mybatis操作Nebula方面的一些经验,希望能够帮助到大家。

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

版本适配

目前仅支持了nebula2.5版本,后续版本的支持还在适配中。

总结

优点

  • 使用简单,消除了使用jdbc或nebula-client带来的冗余代码
  • 可以使用配套连接池管理连接,并且可以与springboot无缝衔接
  • ngql与代码解耦,方便管理
  • 大量便捷标签,免除了代码拼接语句的烦恼

存在的问题

  • 针对返回值为Vertex(类似match v return v)、Edge、无属性Path的类型目前采用在mybatis中的Interceptor做拦截处理,也能满足使用。但这种实现方式感觉不是很好,后期有待优化。
  • 对于返回值类型为带属性Path、多tag查询以及GET SUBGRAPH语句的情况,因为返回的结果中实体以及边的类型可能有多种,目前没有想到比较好的映射方式也就没有支持。
  • 上述示例中使用的jdbc驱动是我们自己开发的版本(详见github),与社区版的主要区别在url上服务地址的指定以及⼀些转义字符的处理,后续也希望能将这些feature合并到社区版本中,统⼀使⽤。

为了方便使用我们还开发了类似mybatis-generator这种工具来生成一些基础代码,提供基本的增删改查功能。感兴趣的同学可以在IDEA的Plugins中搜索Nebula Generator下载,使用方式参见Plugin Homepage

最后感谢@DA1Y1以及其他几位小伙伴的贡献!

本文正在参加 首届Nebula征文活动,如果你觉得本文对你有所帮助可以给我点个:heart:,以示鼓励~
谢谢(#^.^#)

11 个赞

大佬,请问下您,我用文末的自动生成工具,设置好了 1. File->Settings->Tools->NEBULA GENERATOR->nebula url; 2. Tools-Nebula Generator->table name; 为何生成的是空文件?没有一行代码呀。

1 个赞

看一下idea有没有相关报错呢?


tony大神,有一些error,但是看不懂 :sweat_smile:1. ERROR - nSystem.impl.ActionManagerImpl - Icon cannot be found in ‘com.mininglamp.km.nebula.generator.icons.SdkIcons.Sdk_default_icon’,; 2. 找不到指定模板或者加载模板错(TEMPLATE_LOAD_ERROR)

nebula 版本是 2.6.1

跟neubla相关的只有这些嘛

tony大佬您看看,这是日志文件idea.log (3.1 MB)

我看了一下 应该是打包的时候模版漏掉了,一会我更新一下插件哈

3 个赞


表空间加上试试?

1 个赞

加上了也生成的空文件哈。感觉是 tony 大佬说的问题,因为我看 v1.0.3 插件比 v1.0.4 插件大了 2mb。

https://github.com/DA1Y1/nebula-generator-plugin/releases/download/v1.0.5/nebula-generator-plugin-1.0.5.zip
try一下

1 个赞

已经更新过了,插件市场还需要审核,可以先下载上面的插件包拖到IDEA里面试试

2 个赞

好的,我这边试一下,谢谢!

好的,谢谢!