基於neo4j圖數據庫,實現人口關係大圖的基本思路及實現方案。

 

近期因爲工做須要,須要作一我的口關係大圖的存儲及檢索方案,咱們主要的數據對象有:人口(年齡,身份證號碼,性別。。) ;學校信息(學校地址,學校名稱,學校級別,學校下邊的年級班級。。);就任信息(公司名稱,公司地址,公司企業信用代碼。。)以及論文發佈信息(論文主題,參與人,發佈時間)java

問題思考:

  1. 針對這類人口關係大圖,或者叫圖譜的數據通常都是非關係型,並且有多鐘關系的存在,好比張三跟李四兩我的,多是同窗,同事,老鄉,配偶,夫妻,等多種關係,那通常若是採起關係型數據庫(oracle)爲例,可能就須要創建人員信息表, 同窗關係表,同事關係表,老鄉關係表,配偶關係表,夫妻關係表,這樣一旦後續增長一個關係,就要增長一張表,增長一張表,並且後續數據庫中就基本上只剩關係表了,後續的維護,擴充,代碼的修改就機器的困難,簡直就是噩夢。
  2. 經過上述的第一點,咱們不難看出,採用關係型數據庫來存儲並非最好的解決方案,那麼就很容易想到採用關係型數據庫存,那麼目前比較流行的有哪些非關係型數據庫麼,redis,mongoDB,neo4j都能存儲單個菲關係型的數據,但他們三者仍是有區別的,前二者通常在項目中做爲一個數據緩存的容器,將一些不常常變化的數據放在容器中,需者自取。而neoj4j專門用於網絡圖的存儲,網絡圖,這個跟咱們的項目需求不是不謀而合?因而心底暗自拍板,就你了 ,neo4j。

數據庫設計:

 

經過關係表中存儲的ID來進行關聯;注意設置的時候是將與一我的全部關聯的信息進行抽象出來並劃分爲五個對象,即上圖的五個表單。 經過上圖咱們能夠和明確的看出來有以下幾種關係: 人 -就讀->學校;web

人 -發佈論文->論文; 人-就任->企業; 人-居住->家庭;這四類的外部關係(以單我的節點爲參照),另一方面,人根根之間也是存在關係的,好比剛開始提到的同事,同窗類的,那麼這類屬於內部關係,內部關係咱們通常須要經過外部關係區分,好比張三和李四隻有在同一個班級上學,那麼菜算得上同窗吧,只有在同一個公司就任纔算得上同事吧.。這樣的話,咱們數據庫設計出來,邏輯也梳理出來,尤爲是內部關係外部關係。redis

實現方案:

我項目採用的是spring+springMvc的環境。spring

組件代碼:

注意我是將neo4j的配置及實體放在一塊的,至於其餘的邏輯操做在其餘的包中。數據庫

部分代碼:瀏覽器

設置Configuration屬性到neo4j中
package com.audaque.module.graphData.neo4j.config;

import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/***
 *@ClassName AppConfiguration
 *@desc TODO
 *@Author xxxxxx
 *@Date 2019/5/17 0017 下午 10:57
 *@version 1.0.1
 **/
@Configuration
@EnableNeo4jRepositories(basePackages = "com.audaque.module.graphData.neo4j.repos")
@EnableTransactionManagement
@ComponentScan("com.audaque.module.graphData.neo4j.config")
public class AppConfiguration extends Neo4jConfiguration {
@Autowired private org.neo4j.ogm.config.Configuration config; @Bean public org.neo4j.ogm.config.Configuration getConfiguration() { return config; } @Override @Bean public SessionFactory getSessionFactory() { // with domain entity base package(s) return new SessionFactory(config,"com.audaque.module.graphData.neo4j.model"); } @Override @Bean @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public Session getSession() throws Exception { return super.getSession(); } }

  

動態設置屬性到Configuration中屬性到中
package com.audaque.module.graphData.neo4j.config;

import org.neo4j.ogm.authentication.UsernamePasswordCredentials;
import org.neo4j.ogm.config.Configuration;

import static org.neo4j.ogm.config.DriverConfiguration.CREDENTIALS;
import static org.neo4j.ogm.config.DriverConfiguration.DRIVER;
import static org.neo4j.ogm.config.DriverConfiguration.URI;

/***
 *@ClassName MyConfiguration
 *@desc 自定義bean動態配置neo4j數據源。
 *@Author xiaokang.ma@audaque.com
 *@Date 2019/5/24 9:14
 *@version 1.0.1
 **/
public class MyConfiguration extends Configuration {
    public MyConfiguration(String driverClass,String driverURL,String userName,String password){
        super.set(DRIVER[0],driverClass);
        super.set(URI[0],driverURL);
        super.set(CREDENTIALS[0],new UsernamePasswordCredentials(userName, password));

    }

}


PaperInfoRepository配置(實體的保存):
package com.audaque.module.graphData.neo4j.repos;

import com.audaque.module.graphData.neo4j.model.PaperInfo;
import org.springframework.data.neo4j.repository.GraphRepository;
import org.springframework.stereotype.Repository;

/***
 *@ClassName PaperInfoRepository
 *@desc 論文信息接口
 *@Author xxxx
 *@Date 2019/5/17 0017 下午 5:43
 *@version 1.0.1
 **/
@Repository
public interface PaperInfoRepository extends GraphRepository<PaperInfo> {



}

 

內部關係的Repository我採用的自定義查詢語句查詢,以下:緩存

package com.audaque.module.graphData.neo4j.repos;

import com.audaque.module.graphData.neo4j.model.Neo4jRelationInfo;
import org.neo4j.consistency.store.paging.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.GraphRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.List;
import java.util.Map;

/***
 *@ClassName Neo4jRelationInfoRepository
 *@desc 八大關係型數據。
 *@Author xxxx
 *@Date 2019/5/17 0017 下午 5:43
 *@version 1.0.1
 **/
@Repository
public interface Neo4jRelationInfoRepository extends GraphRepository<Neo4jRelationInfo> {

    /**
     *  pe - pe 鄰居關係
     * @param fromId
     * @param toId
     * @return
     */
    @Query(value = "match(a:pe),(b:pe) where a.ids={fromId} and b.ids={toId}  merge (a) - [r:鄰居{direction:{direction}}]->(b)")
    Neo4jRelationInfo generateGraphDataRelation_LJ(@Param("fromId") String fromId, @Param("toId") String toId, @Param("direction") String direction);

    /**
     *  pe -> pe 同窗關係
     * @param fromId
     * @param toId
     * @return
     */
    @Query(value = "match(a:pe),(b:pe) where a.ids={fromId} and b.ids={toId}  merge (a) - [r:同窗{direction:{direction}}]->(b)")
    Neo4jRelationInfo generateGraphDataRelation_TX(@Param("fromId") String fromId, @Param("toId") String toId, @Param("direction") String direction);

    /**
     *  pe -> pe 校友關係
     * @param fromId
     * @param toId
     * @return
     */
    @Query(value = "match(a:pe),(b:pe) where a.ids={fromId} and b.ids={toId}  merge (a) <- [r:校友{direction:{direction}}]->(b)")
    Neo4jRelationInfo generateGraphDataRelation_XY(@Param("fromId") String fromId, @Param("toId") String toId, @Param("direction") String direction);

    /**
     *  pe -> li 居住關係
     * @param fromId
     * @param toId
     * @return
     */
    @Query(value = "match(a:pe),(b:li) where a.ids={fromId} and b.ids={toId}  merge (a) - [r:居住{direction:{direction}}]->(b)")
    Neo4jRelationInfo generateGraphDataRelation_JUZ(@Param("fromId") String fromId, @Param("toId") String toId, @Param("direction") String direction);

    /**
     *  pe -> pe 同事關係
     * @param fromId
     * @param toId
     * @return
     */
    @Query(value = "match(a:pe),(b:pe) where a.ids={fromId} and b.ids={toId}  merge (a) - [r:同事{direction:{direction}}]-(b)")
    Neo4jRelationInfo generateGraphDataRelation_TS(@Param("fromId") String fromId, @Param("toId") String toId, @Param("direction") String direction);



    /**
     *  pe -> wu 就任關係
     * @param fromId
     * @param toId
     * @return
     */
    @Query(value = "match(a:pe),(b:wu) where a.ids={fromId} and b.ids={toId}  merge (a) - [r:就任{direction:{direction}}]->(b)")
    Neo4jRelationInfo generateGraphDataRelation_JZGX(@Param("fromId") String fromId, @Param("toId") String toId, @Param("direction") String direction);


    /**
     *  pe -> sc 畢業院校關係
     * @param fromId
     * @param toId
     * @return
     */
    @Query(value = "match(a:pe),(b:sc) where a.ids={fromId} and b.ids={toId}  merge (a) - [r:畢業{direction:{direction}}]->(b)")
    Neo4jRelationInfo generateGraphDataRelation_BYYX(@Param("fromId") String fromId, @Param("toId") String toId, @Param("direction") String direction);


    /**
     *  pe -> pa 論文參與關係
     * @param fromId
     * @param toId
     * @return
     */
    @Query(value = "match(a:pe),(b:pa) where a.ids={fromId} and b.ids={toId}  merge (a) - [r:參與者{direction:{direction}}]->(b)")
    Neo4jRelationInfo generateGraphDataRelation_PA(@Param("fromId") String fromId, @Param("toId") String toId, @Param("direction") String direction);

    /**
     *  pe -> pa 論文合做關係
     * @param fromId
     * @param toId
     * @return
     */
    @Query(value = "match(a:pe),(b:pe) where a.ids={fromId} and b.ids={toId}  merge (a) - [r:論文合做{direction:{direction}}]->(b)")
    Neo4jRelationInfo generateGraphDataRelation_LWHZ(@Param("fromId") String fromId, @Param("toId") String toId, @Param("direction") String direction);



    /**
     * web查詢接口,查詢與當前節點爲1的數據---根據節點編號,適用於登陸第二次節點點擊以後的查詢
     */
    @Query(value = "match(a)-[r]-(b) where a.ids={qid} return a,r,b")
    List<Map<String,String>> queryRelationDataByIds(@Param("qid") String qid);

    /**
     * web查詢接口,查詢與當前節點爲1的數據
     */
    @Query(value = "match(a:pe)-[r]-(b) where a.cardNo={cardNo} return a,r,b")
    List<Map<String,String>> queryRelationDataByCardNo(@Param("cardNo") String cardNo);

}

 

 

 

bean配置:網絡

<!--初始化配置的Config-->
    <bean id="myConfiguration" class="com.audaque.module.graphData.neo4j.config.MyConfiguration">
        <constructor-arg name="driverClass" value="org.neo4j.ogm.drivers.http.driver.HttpDriver" index="0" type="java.lang.String"/>
        <constructor-arg name="driverURL" value="http://10.229.183.142:7474" index="1" type="java.lang.String"/>
        <constructor-arg name="userName" value="neo4j" index="2" type="java.lang.String"/>
        <constructor-arg name="password" value="123456" index="3" type="java.lang.String"/>
    </bean>

 

 經過上述的簡單配置就在spring環境中搭建到了neo4j。session

業務邏輯代碼:

  1. 存儲全部的節點,直接讀取數據中的人員信息,學校信息,居住信息,教育信息,工做信息,5張表的數據直接save到neo4j,部分代碼以下.

  

 /**
     * 初始化圖數據須要的人員節點數據
     *
     * @param personBaseInfoRepository
     * @return
     */
    @Override
    public void resetNeo4jPersonBaseInfoNode(PersonBaseInfoRepository personBaseInfoRepository) {
        //查詢人員節點列表
        List<PersonBaseInfo> personBaseInfos =  graphDataDao.queryPersonBaseInfo();

        for (PersonBaseInfo personBaseInfo :personBaseInfos){
            try {
                personBaseInfoRepository.save(personBaseInfo);
            }
            catch (Throwable t){
                //生成節點異常時,繼續跳過。
                continue;
            }
        }
    }

 

初始化內部外部關心數據,代碼以下:oracle

 /********************************************開始初始化節點間關係的數據**********************************************/

    /**
     * 初始化五大節點間的關係數據。
     * @param neo4jRelationInfoRepository
     * @return
     */
    @Override
    public void resetAllRelationInfo(Neo4jRelationInfoRepository neo4jRelationInfoRepository) {
        long start = System.currentTimeMillis();
        //初始化人員-->工做關係,人員->學校;人員->論文;人員->住房信息4個主關係的數據。
        List<Map<String,Object>> lists = graphDataDao.queryEntityRelationInfo();
        //計算內部關係信息 ,同事,校友,鄰居,合做者關係。
        Map<String,List<String>> schoolFriendMap = new ConcurrentHashMap<>(); //校友

        Map<String,List<String>> workTogetherMap = new ConcurrentHashMap<>(); //同事

        Map<String,List<String>> neighborMap = new ConcurrentHashMap<>();    //鄰居
        Map<String,List<String>> collaboratorMap = new ConcurrentHashMap<>();//論文合做者
        for(Map<String,Object> map :lists){

            String userId = "";
            //獲取人員信息
            if(StringUtils.isNoneEmpty((String)map.get("USERID")) && StringUtils.isNoneEmpty((String)map.get("USERNAME"))){
                //取工做單位字段 UNIT_ID , UNIT_NAME
                if(StringUtils.isNoneEmpty((String)map.get("UNIT_ID")) && StringUtils.isNoneEmpty((String)map.get("UNIT_NAME"))){
                    //插入就任關係
                    neo4jRelationInfoRepository.generateGraphDataRelation_JZGX((String)map.get("USERID"),(String)map.get("UNIT_ID"),(String)map.get("USERID")+"->"+(String)map.get("UNIT_ID"));

                    //處理同事關係
                    if(workTogetherMap.containsKey((String)map.get("UNIT_ID"))){
                        List<String> list = workTogetherMap.get((String)map.get("UNIT_ID"));
                        if(!list.contains((String)map.get("USERID"))){
                            list.add((String)map.get("USERID"));
                            workTogetherMap.put((String)map.get("UNIT_ID"),list);
                        }
                    }else{
                        List<String> lis = new ArrayList<String>();
                        lis.add((String)map.get("USERID"));
                        workTogetherMap.put((String)map.get("UNIT_ID"),lis);
                    }

                }
                //取學校字段 SCHOOL_ID , SCHOOL_NAME
                if(StringUtils.isNoneEmpty((String)map.get("SCHOOL_ID")) && StringUtils.isNoneEmpty((String)map.get("SCHOOL_NAME"))){
                    //插入畢業院校
                    neo4jRelationInfoRepository.generateGraphDataRelation_BYYX((String)map.get("USERID"),(String)map.get("SCHOOL_ID"),(String)map.get("USERID")+"->"+(String)map.get("SCHOOL_ID"));

                    //處理校友關係
                    if(schoolFriendMap.containsKey((String)map.get("SCHOOL_ID"))){
                        List<String> list = schoolFriendMap.get((String)map.get("SCHOOL_ID"));
                        if(!list.contains((String)map.get("USERID"))){
                            list.add((String)map.get("USERID"));
                            schoolFriendMap.put(this.schoolFriend,list);
                        }
                    }else{
                        List<String> lis = new ArrayList<String>();
                        lis.add((String)map.get("USERID"));
                        schoolFriendMap.put((String)map.get("SCHOOL_ID"),lis);
                    }

                }

                //取住房信息字段 HOUSE_ID , HOUSE_ADDR
                if(StringUtils.isNoneEmpty((String)map.get("HOUSE_ID")) && StringUtils.isNoneEmpty((String)map.get("HOUSE_ADDR"))){
                    //插入畢業院校
                    neo4jRelationInfoRepository.generateGraphDataRelation_JUZ((String)map.get("USERID"),(String)map.get("HOUSE_ID"),(String)map.get("USERID")+"->"+(String)map.get("HOUSE_ID"));

                    //處理鄰居關係
                    if(neighborMap.containsKey((String)map.get("HOUSE_ID"))){
                        List<String> list = neighborMap.get((String)map.get("HOUSE_ID"));
                        if(!list.contains((String)map.get("USERID"))){
                            list.add((String)map.get("USERID"));
                            neighborMap.put((String)map.get("HOUSE_ID"),list);
                        }
                    }else{
                        List<String> lis = new ArrayList<String>();
                        lis.add((String)map.get("USERID"));
                        neighborMap.put((String)map.get("HOUSE_ID"),lis);
                    }
                }
                //取論文信息字段 LW_ID , LW_TITLE
                if(StringUtils.isNoneEmpty((String)map.get("LW_ID")) && StringUtils.isNoneEmpty((String)map.get("LW_TITLE"))){
                    //插入畢業院校
                    neo4jRelationInfoRepository.generateGraphDataRelation_PA((String)map.get("USERID"),(String)map.get("LW_ID"),(String)map.get("USERID")+"->"+(String)map.get("LW_ID"));
                    //處理合做者關係
                    if(collaboratorMap.containsKey((String)map.get("LW_ID"))){
                        List<String> list = collaboratorMap.get((String)map.get("LW_ID"));
                        if(!list.contains((String)map.get("USERID"))){
                            list.add((String)map.get("USERID"));
                            collaboratorMap.put((String)map.get("LW_ID"),list);
                        }
                    }else{
                        List<String> lis = new ArrayList<String>();
                        lis.add((String)map.get("USERID"));
                        collaboratorMap.put((String)map.get("LW_ID"),lis);
                    }
                }

            }
        }

        //開始剔除長度爲1的數據,沒有對應的關係。
        schoolFriendMap.forEach((key,value) -> {
            //獨立個體沒法造成關係,剔除
            if(((List<String>)value).size()<=1){
                schoolFriendMap.remove(key);
            }
        });
        workTogetherMap.forEach((key,value) -> {
            //獨立個體沒法造成關係,剔除
            if(((List<String>)value).size()<=1){
                workTogetherMap.remove(key);
            }
        });
        neighborMap.forEach((key,value) -> {
            //獨立個體沒法造成關係,剔除
            if(((List<String>)value).size()<=1){
                neighborMap.remove(key);
            }
        });
        collaboratorMap.forEach((key,value) -> {
            //獨立個體沒法造成關係,剔除
            if(((List<String>)value).size()<=1){
                collaboratorMap.remove(key);
            }
        });

        //開始遍歷校友關係數據,生成節點。
        schoolFriendMap.forEach((key,value) -> {
            List<String> li = (List<String>)value;
            for(int st = 0;st<li.size() ; st++){
                for(int ss = li.size()-1;ss>st;ss--){
                    System.out.println("生成關係"+li.get(st)+"->"+li.get(ss));
                    neo4jRelationInfoRepository.generateGraphDataRelation_XY(li.get(st),li.get(ss),"<->");

                }
            }
        });
        //同事關係
        workTogetherMap.forEach((key,value) -> {
            List<String> li = (List<String>)value;
            for(int st = 0;st<li.size() ; st++){
                for(int ss = li.size()-1;ss>st;ss--){
                    System.out.println("生成關係"+li.get(st)+"->"+li.get(ss));
                    neo4jRelationInfoRepository.generateGraphDataRelation_TS(li.get(st),li.get(ss),"<->");

                }
            }
        });
        //鄰居關係
        neighborMap.forEach((key,value) -> {
            List<String> li = (List<String>)value;
            for(int st = 0;st<li.size() ; st++){
                for(int ss = li.size()-1;ss>st;ss--){
                    System.out.println("生成關係"+li.get(st)+"->"+li.get(ss));
                    neo4jRelationInfoRepository.generateGraphDataRelation_LJ(li.get(st),li.get(ss),"<->");

                }
            }
        });
        //合做者關係
        collaboratorMap.forEach((key,value) -> {
            List<String> li = (List<String>)value;
            for(int st = 0;st<li.size() ; st++){
                for(int ss = li.size()-1;ss>st;ss--){
                    System.out.println("生成關係"+li.get(st)+"->"+li.get(ss));
                    neo4jRelationInfoRepository.generateGraphDataRelation_LWHZ(li.get(st),li.get(ss),"<->");

                }
            }
        });
        System.out.println("總共花費時間:" + (System.currentTimeMillis() - start) + "ms");

    }

 

 

而後Control中對外暴露三個接口, 1.初始化節點,  2.初始化關係。 就能夠了。

而後經過瀏覽器直接能夠看到圖數據的結構,分享下我作出來的效果。

 

至此一個從數據庫存入到neo4j數據庫中,而後能夠對外開放一個查詢的接口,返回與之有關係而且深度爲1的數據,方便其餘系統展現,接口以下:

/**
     * web查詢接口,查詢與當前節點爲1的數據---根據節點編號,適用於登陸第二次節點點擊以後的查詢。
     */
    @Query(value = "match(a)-[r]-(b) where a.ids={qid} return a,r,b")
    List<Map<String,String>> queryRelationDataByIds(@Param("qid") String qid);

    /**
     * web查詢接口,用戶登陸以後第一次查詢,需根據身份證號碼查詢。第二次查詢就直接會調用上一個查詢。
     */
    @Query(value = "match(a:pe)-[r]-(b) where a.cardNo={cardNo} return a,r,b")
    List<Map<String,String>> queryRelationDataByCardNo(@Param("cardNo") String cardNo);
相關文章
相關標籤/搜索