最近有注意到很多同学对于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征文活动,如果你觉得本文对你有所帮助可以给我点个,以示鼓励~
谢谢(#^.^#)