使用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 个赞

浙ICP备20010487号