1. 图数据库Neo4j之爱的初体验 ----与君初相识,犹似故人归

在如今大数据(big data)横行的时代,传统的关系型数据库如oracle,mysql在大数据量,高并发的场景下显得力不从心。于是乎,NoSQL横空出世,如column-based的cassandra数据库,document-based的MongoDB,还有今天介绍的小众的graph-based的图数据数据库Neo4j。

图数据库名字的由来其实与其在底层的存储方式有关,图数据库并不表示会存储图形,图片等。Neo4j底层会以图的方式把用户定义的节点(nodes)以及关系(relationships)存储起来,Nodes 和 Relationships 包含key/value形式的属性。Nodes通过Relationships所定义的关系相连起来,形成关系型网络结构。通过这种方式,可是高效的实现从某个节点开始,通过节点与节点间关系,找出两个节点间的联系。

2. Neo4j之爱的再判断----天生我材必有用

以金庸先生的《神雕侠侣》为例,这里面的人物关系图很复杂,神雕大侠杨过有很多粉丝,他的粉丝又有自己的粉丝(二度人脉),粉丝的粉丝又有自己的粉丝……..假设杨大侠从南宋就开了个博客,到如今必定圈粉无数,如果要找到杨过的粉丝和他的六度七度人脉,运用传统的关系型数据库的话,则需要在关系表里存储很多关系数据,成万上亿,百亿,千亿甚至更多。不论用关系型数据库的分库分表技术还是其他优化手段,在复杂度和性能方面都可能会遇到瓶颈。

而图数据库neo4j运用了图的遍历算法设计,即从一个节点开始,根据其连接的关系,可以快速和方便地找出它的邻近节点,从而具有简单的存储方式和极高的查询性能。

本文着重关注Spring boot 2.0 如何简洁的调用neo4j.

3. Neo4j执Spring boot之手----窈窕淑女,Spring boot好逑

在我的第一篇博客里介绍了Spring boot 2.0。本文将Spring boot 结合Spring Data,用极少的代码,基本零配置,不用书写任何查询(queries)语句(这里是cypher)实现了Neo4j的增删改查。

3.1 Neo4j的连接配置

Spring boot的配置文件默认在src/main/resources下面,支持传统的application.properties 和application.yml

application.properties版:






​spring.data.neo4j.username=neo4j​






​spring.data.neo4j.password=helloworld​


application.yml版:      






​spring:​






​ data:​






​ neo4j:​






​ username: neo4j​






​ password: helloworld​


3.2 实体类(model) 






​@NodeEntity​






​public class Legend {​












​ @Id @GeneratedValue private Long id;​












​ private String name;​












​ private Legend() {​






​ // Empty constructor required as of Neo4j API 2.0.5​






​ };​












​ public Legend(String name) {​






​ this.name = name;​






​ }​












​ /**​






​ * Neo4j doesn't REALLY have bi-directional relationships. It just means when querying​






​ * to ignore the direction of the relationship.​






​ * https://dzone.com/articles/modelling-data-neo4j​






​ */​






​ @Relationship(type = "FANS", direction = Relationship.UNDIRECTED)​






​ public Set<Legend> fans;​












​ public void fansWith(Legend legend) {​






​ if (fans == null) {​






​ fans = new HashSet<>();​






​ }​






​ fans.add(legend);​






​ }​












​ public String toString() {​












​ //java 8 stream and optional​






​ return this.name + "'s fans => "​






​ + Optional.ofNullable(this.fans).orElse(​






​ Collections.emptySet()).stream()​






​ .map(Legend::getName)​






​ .collect(Collectors.toList());​






​ }​












​ public String getName() {​






​ return name;​






​ }​












​ public void setName(String name) {​






​ this.name = name;​






​ }​






​}​


实体类annotated by 注解@NodeEntity,这样当调用保存这个实体时(save方法),会将它保存到neo4j数据库。

另一个重要的部分是

        @Relationship(type = "FANS", direction = Relationship.UNDIRECTED)


用来建立关系,UNDIRECTED表示忽略关系的方向性。

应用程序通过调用fansWith方法,可以将神雕的人物们联系起来。

3.3 Spring data neo4j

        Spring Data  属于Spring 大家庭,用于简化对数据库的访问,在很多情况下,甚至都不用写任何queries就可以实现对数据库的各种操作。 






​import org.springframework.data.repository.CrudRepository;​












​public interface LegendRepo extends CrudRepository<Legend, Long> {​












​ Legend findByName(String name);​






​}​


 CrudRepository 是关键,它封装常用的如保存,更新等操作。

上面的findByName方法表示用name来查询。name必须是实体类的一个属性。在关系型数据库里,spring data会自己将他转化成 select * from table where name=?。而neo4j使用cypher语言,类似转化成查询语句






​MATCH (n:`Legend`) WHERE n.`name` = { `name_0` } WITH n RETURN n,​






​[ [ (n)-[r_f1:`FANS`]-(l1:`Legend`) | [ r_f1, l1 ] ] ], ID(n) with params {name_0=杨过}​


如果要表达Or或者And关系(假设legend有属性level),方法名将会是

findByNameAndLevel(String name,String level)

如果要分页,需要继承PagingAndSortingRepository,而不是CrudRepository 。有关springdata的更多细节,笔者将会在以后的博客中详细介绍。


3.4 Spring boot 启动类 






​@SpringBootApplication​






​@EnableNeo4jRepositories​






​public class SpringBootNeo4jApplication {​












​ public static void main(String[] args) {​






​ SpringApplication.run(SpringBootNeo4jApplication.class, args);​






​ }​






​}​


这里的注解@EnableNeo4jRepositories告诉spring boot程序使用neo4j repository。

3.5 Spring boot 测试类 

 开发工具Spring tool suites自动会生成测试类,添加自己的逻辑代码,保存三个节点:杨过,小龙女和郭襄。

然后建立她们之间的关系,小龙女和郭襄均是杨过的粉丝。最后查询出杨过的粉丝。






​@RunWith(SpringRunner.class)​






​@SpringBootTest​






​public class SpringBootNeo4jApplicationTests {​


















​ @Autowired​






​ LegendRepo legendRepo;​






​ private final static Logger log = LoggerFactory.getLogger(SpringBootNeo4jApplicationTests.class);​












​ @Test​






​ public void contextLoads() {​






​ legendRepo.deleteAll();​












​ Legend yangguo = new Legend("杨过");​






​ Legend dragonGirl = new Legend("小龙女");​






​ Legend guoxiang = new Legend("郭襄");​












​ List<Legend> team = Arrays.asList(yangguo, dragonGirl, guoxiang);​












​ log.info("Before linking up with Neo4j...");​






​ //java 8 stream​






​ team.stream().forEach(legend -> log.info("\t" + legend.toString()));​












​ legendRepo.save(yangguo);​






​ legendRepo.save(dragonGirl);​






​ legendRepo.save(guoxiang);​












​ yangguo = legendRepo.findByName(yangguo.getName());​






​ yangguo.fansWith(dragonGirl);​






​ yangguo.fansWith(guoxiang);​






​ legendRepo.save(yangguo);​












​ dragonGirl = legendRepo.findByName(dragonGirl.getName());​






​ dragonGirl.fansWith(guoxiang);​






​ // We already know that dragonGirl is a fan of yangguo​






​ legendRepo.save(dragonGirl);​












​ // We already know guoxiang fans with yangguo and dragongirl​






​ log.info("Look up yangguo's fans ...");​






​ log.info(legendRepo.findByName("杨过").toString());​






​ }​












​}​


运行代码查看日志:

Look up yangguo's fans ...
Request: MATCH (n:`Legend`) WHERE n.`name` = { `name_0` } WITH n RETURN n,[ [ (n)-[r_f1:`FANS`]-(l1:`Legend`) | [ r_f1, l1 ] ] ], ID(n) with params {name_0=杨过}
杨过's fans => [郭襄, 小龙女]

4. Neo4j与Spring boot----一生一代一双人

Neo4j在某些场合能发挥自己的优势,而用spring boot的方式,使得neo4j的使用非常简单,自从有了spring boot,生活变得好轻松。

关注spring boot,请继续关注我的博客。