吐槽:公司领导突然说了一个之前一直没有听说过的数据库->neo4j图形数据库,当时一阵懵逼,一步步填坑过来,留下项目笔记,等一个有缘人。本文讲解了数据库安装、SpringBoot整合、常规CRUD、自定义分页查询、集成Mybatis-Plus、事务处理。
目录:
本文主要分为如下几个章节进行集成 *** 作示例和填坑说明。
1、 neo4j数据库的下载安装 2、 SpringBoot集成neo4j数据库依赖引入和配置 3、 数据库节点CRUD *** 作 4、 自定义分页实现 5、 集成MySQL+Mybatis-Plus 6、多数据源事务处理1、neo4j数据库的下载安装
数据库可以直接在官网下载,根据自己的系统选择,neo4j数据库的使用需要注意JDK的版本,3.*版本jdk要求为8,4.*版本jdk要求为11,本文以jdk8为例。
官网地址:neo4j官网地址社区版下载地址
下载好后windows直接解压(感兴趣的也可下载对应的docker镜像使用)
在对应的bin目录下运行cmd启动数据库输入(相关 *** 作指令可自行了解)
neo4j console
指令运行成功后复制cmd中的地址打开即可访问数据库
如图:
浏览器访问该地址(会自动跳转到http://localhost:7474/browser/)默认会连接上本地运行的neo4j数据库,若未连接可手动连接如图:
初始账号和密码都是neo4j,第一次登录会提示修改密码,自行修改后记住密码即可。连接成功如图:
这样就可以直接在上面的命令行中输入对应的CQL语句进行 *** 作了。
SpringBoot集成的neo4j数据库 *** 作有很多版本,且每个版本对应的neo4j *** 作源码存在很大的差异,这点需要特别注意,版本不对应是无法集成成功的。本文采用springboot.version=2.5.3,对应的neo4j的驱动为6.1.3[此版本高出目前网络上大多资料版本,导致一直踩坑]。
pom关键配置如下:
org.springframework.boot spring-boot-starter-parent2.5.3 org.springframework.boot spring-boot-starter-data-neo4jorg.projectlombok lombok
对应的application.yml数据库连接信息如图:
注意此处的uri地址,有bolt/http/https三种方式,这个在大家安装该数据库时会有了解。
其实和常见实体无太大差别,只是需要指定对应的数据库节点和标明字段为属性等。
import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.Id; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.core.schema.Property; import java.io.Serializable; @Data @Node("dongMan") public class DongMan implements Serializable { @Id @GeneratedValue @ApiModelProperty(value = "主键,neo4j数据库自生成值") private Long id; @Property @ApiModelProperty(value = "姓名") private String name; @Property @ApiModelProperty(value = "年龄") private String age; @Property @ApiModelProperty(value = "性别") private String sex; }创建Service
springboot-data-neo4j提供了和mybatis-plus类似的crud *** 作仓库,我们可以直接继承使用,也可自行编写实现。
service代码:
import org.springframework.data.domain.Page; import work.order.system.entity.dongman.DongMan; import work.order.system.entity.dongman.DongManRelation; import java.util.List; public interface DongManService { DongMan addDongMan(DongMan dongMan); DongMan getInfoById(long id); void delById(long id); void createRelation(String from,String relation, String to); void createRelationByName(String fromName); Page创建DaogetListByPage(int current, int pageSize, String Name); List getAllRealationTypes(); Boolean existById(long id); DongMan updateById(DongMan dongMan); //查询指定用户的所有关系 List getRelationsByName(String name,String relation); }
dao *** 作仓库代码其中有自定义 *** 作集合Mybatis自定义SQL执行类似:
import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.query.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import work.order.system.entity.dongman.DongMan; import work.order.system.entity.dongman.DongManRelation; import java.util.List; @Repository public interface DongManRepository extends Neo4jRepository创建ServiceImpl{ //一对一手动指定关系 @Query("match (n:dongMan {name:{0}}),(m:dongMan {name:{2}})"+ "create (n)-[:动漫人物关系{relation:{1}}]->(m)") void createRelation(String from,String relation, String to); //根据关系数据进行当前用户的所有关系生成 @Query("match (n:dongMan {name:{0}}),(m:dmRelation),(s:dongMan) where m.from={0} and s.name=m.to create(n)-[:动漫人物关系 {relation:m.relation}]->(s)") void createRelationByName(String fromName); //根据关系数据进行当前用户的所有关系生成 @Query("CALL db.relationshipTypes()") List getAllRealationTypes(); //修改 @Query("MATCH (n) WHERe id(n) = :#{#dongMan.id} SET n.name = :#{#dongMan.name},n.age = :#{#dongMan.age},n.sex = :#{#dongMan.sex} RETURN n") DongMan updateById(@Param("dongMan") DongMan dongMan); @Query("match (n:dongMan {name:{name}})-[r:`动漫人物关系`]->(m:dongMan) where r.relation={relation} return m") List getRelationsByName(@Param("name")String name,@Param("relation")String relation); @Query("MATCH (n:dongMan {name:'冯宝宝'}) RETURN n") DongMan getTest(); }
需要注意的是,目前springboot-data-neo4j中的分页首页是从0开始的,不是从1开始的,很多人估计在这个地方郁闷了很久,查询第一页数据时输入1的页码,结果看不到数据。就是因为这个差别导致的!!!
接口实现层代码如下:
import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import work.order.system.dao.doc.DocSqlMapper; import work.order.system.dao.dongman.DongManRepository; import work.order.system.entity.doc.DocSql; import work.order.system.entity.dongman.DongMan; import work.order.system.entity.dongman.DongManRelation; import work.order.system.service.DongManService; import javax.annotation.Resource; import java.util.List; @Service @Transactional(value="transactionManager") public class DongManServiceImpl implements DongManService { @Resource DongManRepository dongManRepository; @Override public DongMan addDongMan(DongMan dongMan) { return dongManRepository.save(dongMan); } @Override public DongMan getInfoById(long id) { return dongManRepository.findById(id).get(); } @Override public void delById(long id) { dongManRepository.deleteById(id); } @Override public void createRelation(String from, String relation, String to) { dongManRepository.createRelation(from,relation,to); } @Override public void createRelationByName(String fromName) { dongManRepository.createRelationByName(fromName); } @Override public Page创建ControllergetListByPage(int current, int pageSize, String Name) { Pageable pageable= PageRequest.of(current,pageSize); return dongManRepository.findAll(pageable); } @Override public List getAllRealationTypes() { return dongManRepository.getAllRealationTypes(); } @Override public Boolean existById(long id) { return dongManRepository.existsById(id); } @Override public DongMan updateById(DongMan dongMan) { return dongManRepository.updateById(dongMan); } @Override public List getRelationsByName(String name,String relation) { return dongManRepository.getRelationsByName(name,relation); } }
提供服务访问入口进行服务消费
控制层代码如下:
import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import work.order.system.entity.dongman.DongMan; import work.order.system.entity.Result; import work.order.system.service.DongManService; import javax.annotation.Resource; @RestController @RequestMapping("/dongMan") @Api(tags="动漫人物 *** 作测试") public class DongManController { @Resource DongManService dongManService; @PostMapping("/addDongMan") @ApiOperation(value="添加对象节点") public Result addDongMan(DongMan dongMan) { return Result.success(" *** 作成功!", dongManService.addDongMan(dongMan)); } @PostMapping("/delById") @ApiOperation(value="根据主键删除") @ApiImplicitParam(name="id",value="主键",paramType="form") public Result delById(Long id) { dongManService.delById(id); return Result.success(" *** 作成功!"); } @PostMapping("/updateDongMane") @ApiOperation(value="修改节点信息") public Result updateDongMane(DongMan dongMan) { return Result.success(" *** 作成功!", dongManService.updateById(dongMan)); } @GetMapping("/getInfoById") @ApiOperation(value="根据主键查询") @ApiImplicitParam(name="id",value="主键",paramType="form") public Result getInfoById(Long id) { return Result.success(" *** 作成功!",dongManService.getInfoById(id)); } @GetMapping("/getAllRelationTypes") @ApiOperation(value="获取所有的关系类型") public Result getAllRelationTypes() { return Result.success(" *** 作成功!", dongManService.getAllRealationTypes()); } @PostMapping("/addDMRelationShip") @ApiOperation(value="指定两个节点的关系(两个节点须存在)") @ApiImplicitParams({ @ApiImplicitParam(name="name",value="对象名:唐三-",paramType="form"), @ApiImplicitParam(name="relation",value="关系[父亲]->",paramType="form"), @ApiImplicitParam(name="to",value="对象名:唐昊",paramType = "form") }) public Result addDMRelationShip(String name,String relation,String to) { //直接指定关系 dongManService.createRelation(name, relation, to); return Result.success(" *** 作成功!" ); } @GetMapping("/getRelationsByName") @ApiOperation(value="获取指定节点指定关系信息") @ApiImplicitParams({ @ApiImplicitParam(name="name",value="对象名:唐三",paramType="form"), @ApiImplicitParam(name="relation",value="具体关系",paramType = "form") }) public Result getRelationsByName(String name,String relation) { return Result.success(" *** 作成功!" , dongManService.getRelationsByName(name,relation)); } }效果演示
运行项目进行接口 *** 作验证,我这边使用swagger进行测试,(因为我数据库中已经有测试数据了,我们先看下数据库中的数据)。
源数据库中的数据如图:
共有18条记录,我们验证添加节点 *** 作,新增武庚和逆天而行。如图:
创建节点时会根据对应实体上的注解指定的节点名进行创建(也可以先手动在数据库中创建好节点)。
创建节点指令:
create (n:dongMan {name:'李四',sex:'男',age:'22'})
数据库中如图:
其他的修改和删除 *** 作就不一一演示了,下面演示下给这两个节点添加关系。
基础的 *** 作就实现了。
在controller中添加如下代码:
@GetMapping("/getListByPage") @ApiOperation(value="分页查询") @ApiImplicitParams({ @ApiImplicitParam(name="current",value="起始页0为第一页",paramType="form"), @ApiImplicitParam(name="pageSize",value="页数量",paramType = "form") }) public Result getListByPage(int current,int pageSize) { return Result.success(" *** 作成功!",dongManService.getListByPage(current,pageSize,"")); }
在swagger中查看效果
该分页实现为源码中的PagingAndSortingRepository仓库实现的,但是源码中只有一个排序 *** 作没有关于参数指定的分页查询实现,即需要我们自己手动实现自定义分页 *** 作。
自定义分页实现service加入如下代码:
PagegetRelationsByName(int current, int pageSize,String name);
dao加入如下代码:
@Query(value="match (n:dongMan {name:{name}})-[r:`动漫人物关系`]->(m:dongMan) return id(n) as pid, n.name as name,r.relation as relation" + ",m as dongMan skip {skip} limit {pageSize}" ,countQuery = "match (n:dongMan {name:{name}})-[r:`动漫人物关系`]->(m:dongMan) return count(r)") PagegetRelationsByName(@Param("name")String name,@Param("skip")int ship,@Param("pageSize")int pageSize,Pageable pageable); //不传递pageable分页无效
对应的CQL需要指定skip和limit的值一级获取总条数的SQL,且必须接收Pageable。
serviceImpl加入如下代码:
@Override public PagegetRelationsByName(int current, int pageSize,String name) { Pageable pageable= PageRequest.of(current,pageSize); return dongManRepository.getRelationsByName(name,current*pageSize,pageSize,pageable); }
controller加入如下代码:
@GetMapping("/getRelations") @ApiOperation(value="获取指节点关系信息") @ApiImplicitParams({ @ApiImplicitParam(name="name",value="对象名:唐三",paramType="form"), @ApiImplicitParam(name="current",value="起始页0为第一页",paramType="form"), @ApiImplicitParam(name="pageSize",value="页数量",paramType = "form") }) public Result getRelations(int current,int pageSize,String name) { return Result.success(" *** 作成功!" , dongManService.getRelationsByName(current,pageSize,name)); }
效果如图:
这样就满足了自定义条件分页查询的实现了。
pom关键添加如下信息:
mysql mysql-connector-javaruntime com.alibaba druid-spring-boot-starter1.2.6 com.baomidou mybatis-plus-boot-starter3.4.2
application.yml添加如下信息:
spring: datasource: type: com.zaxxer.hikari.HikariDataSource driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://xxxxxxxx:3306/xxx?useUnicode=true&characterEncoding=utf-8 username: root password: xxxx #mybatis-plus设置 mybatis-plus: mapper-locations: classpath:mapper*.xml # Mapper文件的位置 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志的实现类(打印SQL) map-underscore-to-camel-case: true # 下划线转驼峰
添加好如上信息后直接按照SpringBoot集成Mybatis-plus的crud *** 作实现即可进行mysql数据库的 *** 作,无需进行数据源切换(目前未明确为何可自行寻找到数据源进行数据库 *** 作,欢迎大家留言讨论)。这里就直接演示MySQL的 *** 作了,如图:
注意哈:mybatis的分页首页为1,neo4j的首页为0。
在实际的 *** 作中很有可能用到事务的回滚功能,集成了MySQL和neo4j数据库后,只需对事务进行简单配置即可实现。
首先启用事务,在启动类上添加@EnableTransactionManagement注解。因为集成了不同的数据库,直接启用事务,系统无法辨别具体的回滚 *** 作在那个数据库执行,我的实现如下,添加事务配置类:
import org.neo4j.driver.Driver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; @Configuration public class TransactionConfig { @Bean("mysqlTransaction") public DataSourceTransactionManager jpaTransactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean("transactionManager") public Neo4jTransactionManager neo4jTransactionManager(Driver driver) { return new Neo4jTransactionManager(driver); } }
此处一定要注意neo4j的事务必须命名为transactionManager,否则会出错。这个地方坑了很长时间。
在对应的serviceImpl类或者具体的实现方法上加入事务注解,指定使用那个事务进行实现。依据数据库进行区分,
mysql数据库 *** 作的添加@Transactional(value=“mysqlTransaction”)
neo4j数据库 *** 作的添加@Transactional(value=“transactionManager”)
这样就可以实现事务的回滚 *** 作了。我们在DongManServiceImpl类中添加异常 *** 作,验证事务是否生效。
修改原有addDongMan方法为:
@Override public DongMan addDongMan(DongMan dongMan) { //正常 *** 作 dongManRepository.save(dongMan); //异常 *** 作 dongManRepository.getTest(); return dongManRepository.save(dongMan); }
修改DongManRepository中的getTest接口的CQL为错误的CQL:
@Query("MATCH (n:dongMan {name:冯宝宝}) RETURN n") DongMan getTest();
进行 *** 作验证,添加一个CSDN的节点,添加成功则事务无效,因为添加成功 *** 作后面有一个异常 *** 作,事务应该回滚才正确。
后台异常如图:
数据库中查看是否添加CSDN成功:
说明neo4j的事务生效了,mysql的事务验证就不演示了,这里再演示下neo4j *** 作成功,mysql *** 作失败的数据库混合 *** 作,看事务是否生效:
修改DongManServiceImpl中的addDongMan,引入mysql的数据库 *** 作。代码如下:
//模拟混合数据源事务管理 @Resource DocSqlMapper sqlMapper; @Override public DongMan addDongMan(DongMan dongMan) { //正常 *** 作 dongManRepository.save(dongMan); //异常 *** 作 DocSql docSql2 = sqlMapper.selectById("3"); docSql2.setDocId(null); docSql2.setCreateTime("111"); sqlMapper.updateById(docSql2); return dongMan; }
运行查看事务是否生效:
MySQL *** 作失败,neo4j *** 作成功,但是2个 *** 作在一个方法中,事务回滚,判定为 *** 作失败,数据库不进行 *** 作成功的数据写入。混合式事务验证成功。
大家不喜勿喷!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)