一、中文分词插件
NEO4J中文全文索引,分词组件使用IKAnalyzer。为了支持高版本LUCENE,IKAnalyzer需要做一些调整。
ELASTICSEARCH-IKAnlyzer 高版本实现参考
1、分词组件的调整
调整之后的分词组件 casia.isiteam.zdr.wltea
// 调整之后的实现 public final class IKAnalyzer extends Analyzer { // 默认细粒度切分 true-智能切分 false-细粒度切分 private Configuration configuration = new Configuration(false); /** * IK分词器Lucene Analyzer接口实现类 * <p> * 默认细粒度切分算法 */ public IKAnalyzer() { } /** * IK分词器Lucene Analyzer接口实现类 * * @param configuration IK配置 */ public IKAnalyzer(Configuration configuration) { super(); this.configuration = configuration; } /** * 重载Analyzer接口,构造分词组件 */ @Override protected TokenStreamComponents createComponents(String fieldName) { Tokenizer _IKTokenizer = new IKTokenizer(configuration); return new TokenStreamComponents(_IKTokenizer); } }
2、分词测试
自定义分词函数
RETURN zdr.index.iKAnalyzer('复联终章快上映了好激动,据说知识图谱与人工智能技术应用到了那部电影!吖啶基氨基甲烷磺酰甲氧基苯胺是一种药嘛?',true) AS words 1
/** * @param text:待分词文本 * @param useSmart:true 用智能分词,false 细粒度分词 * @return * @Description: TODO(支持中英文本分词) */ @UserFunction(name = "zdr.index.iKAnalyzer") @Description("Fulltext index iKAnalyzer - RETURN zdr.index.iKAnalyzer({text},true) AS words") public List<String> iKAnalyzer(@Name("text") String text, @Name("useSmart") boolean useSmart) { PropertyConfigurator.configureAndWatch("dic" + File.separator + "log4j.properties"); Configuration cfg = new Configuration(useSmart); StringReader input = new StringReader(text.trim()); IKSegmenter ikSegmenter = new IKSegmenter(input, cfg); List<String> results = new ArrayList<>(); try { for (Lexeme lexeme = ikSegmenter.next(); lexeme != null; lexeme = ikSegmenter.next()) { results.add(lexeme.getLexemeText()); } } catch (IOException e) { e.printStackTrace(); } return results; }
二、样例数据准备
# 构造样例数据 MERGE (a:Loc {name:'A'}) SET a.description='复联终章快上映了好激动,据说知识图谱与人工智能技术应用到了那部电影!吖啶基氨基甲烷磺酰甲氧基苯胺是一种药嘛?' MERGE (b:Loc {name:'B'}) SET b.description='复联终章快上映了好激动,据说知识图谱与人工智能技术应用到了那部电影!吖啶基氨基甲烷磺酰甲氧基苯胺是一种药嘛?' MERGE (c:Loc {name:'C'}) SET c.description='复联终章快上映了好激动,据说知识图谱与人工智能技术应用到了那部电影!吖啶基氨基甲烷磺酰甲氧基苯胺是一种药嘛?' MERGE (d:Loc {name:'D'}) SET d.description='复联终章快上映了好激动,据说知识图谱与人工智能技术应用到了那部电影!吖啶基氨基甲烷磺酰甲氧基苯胺是一种药嘛?' MERGE (e:Loc {name:'E'}) SET e.description='复联终章快上映了好激动,据说知识图谱与人工智能技术应用到了那部电影!吖啶基氨基甲烷磺酰甲氧基苯胺是一种药嘛?' MERGE (f:Loc {name:'F'}) SET f.description='复联终章快上映了好激动,据说知识图谱与人工智能技术应用到了那部电影!吖啶基氨基甲烷磺酰甲氧基苯胺是一种药嘛?' MERGE (a)-[:ROAD {cost:50}]->(b) MERGE (a)-[:ROAD {cost:50}]->(c) MERGE (a)-[:ROAD {cost:100}]->(d) MERGE (b)-[:ROAD {cost:40}]->(d) MERGE (c)-[:ROAD {cost:40}]->(d) MERGE (c)-[:ROAD {cost:80}]->(e) MERGE (d)-[:ROAD {cost:30}]->(e) MERGE (d)-[:ROAD {cost:80}]->(f) MERGE (e)-[:ROAD {cost:40}]->(f);
三、通过中文全文分词组件创建节点索引
自定义创建索引过程
CALL zdr.index.addChineseFulltextIndex('IKAnalyzer', 'Loc', ['description']) YIELD message RETURN message 1
@Procedure(value = "zdr.index.addChineseFulltextIndex", mode = Mode.WRITE) @Description("CALL zdr.index.addChineseFulltextIndex(String indexName, String labelName, List<String> propKeys) YIELD message RETURN message," + "为一个标签下的所有节点的指定属性添加索引") public Stream<NodeIndexMessage> addChineseFulltextIndex(@Name("indexName") String indexName, @Name("labelName") String labelName, @Name("properties") List<String> propKeys) { Label label = Label.label(labelName); List<NodeIndexMessage> output = new ArrayList<>(); // // 按照标签找到该标签下的所有节点 ResourceIterator<Node> nodes = db.findNodes(label); System.out.println("nodes:" + nodes.toString()); int nodesSize = 0; int propertiesSize = 0; while (nodes.hasNext()) { nodesSize++; Node node = nodes.next(); System.out.println("current nodes:" + node.toString()); // 每个节点上需要添加索引的属性 Set<Map.Entry<String, Object>> properties = node.getProperties(propKeys.toArray(new String[0])).entrySet(); System.out.println("current node properties" + properties); // 查询该节点是否已有索引,有的话删除 Index<Node> index = db.index().forNodes(indexName, FULL_INDEX_CONFIG); System.out.println("current node index" + index); index.remove(node); // 为了该节点的每个需要添加索引的属性添加全文索引 for (Map.Entry<String, Object> property : properties) { propertiesSize++; index.add(node, property.getKey(), property.getValue()); } } String message = "IndexName:" + indexName + ",LabelName:" + labelName + ",NodesSize:" + nodesSize + ",PropertiesSize:" + propertiesSize; NodeIndexMessage indexMessage = new NodeIndexMessage(message); output.add(indexMessage); return output.stream(); }
四、中文分词索引查询
自定义查询索引过程
CALL zdr.index.chineseFulltextIndexSearch('IKAnalyzer', 'description:吖啶基氨基甲烷磺酰甲氧基苯胺', 100) YIELD node RETURN node 1
CALL zdr.index.chineseFulltextIndexSearch('IKAnalyzer', 'description:复联* AND year:1999', 100) YIELD node,weight RETURN node.name,node.year,node.description,weight 1
@Procedure(value = "zdr.index.chineseFulltextIndexSearch", mode = Mode.WRITE) @Description("CALL zdr.index.chineseFulltextIndexSearch(String indexName, String query, long limit) YIELD node RETURN node," + "执行LUCENE全文检索,返回前{limit个结果}") public Stream<ChineseHit> chineseFulltextIndexSearch(@Name("indexName") String indexName, @Name("query") String query, @Name("limit") long limit) { if (!db.index().existsForNodes(indexName)) { log.debug("如果索引不存在则跳过本次查询:`%s`", indexName); return Stream.empty(); } return db.index() .forNodes(indexName, FULL_INDEX_CONFIG) .query(new QueryContext(query).sortByScore().top((int) limit)) .stream() .map(ChineseHit::new); // provider }
【跨标签类型检索】使用addChineseFulltextIndex给标签下节点属性添加的索引,默认可以使用chineseFulltextIndexSearch合并检索出来
// 增加一个非Loc标签的节点,然后使用检索 CREATE (n:LocProvince {name:'P'}) SET n.description='复联终章快上映了好激动,据说知识图谱与人工智能技术应用到了那部电影!' RETURN n // 节点增加索引(索引名与已有相同) CALL zdr.index.addChineseFulltextIndex('IKAnalyzer', 'LocProvince', ['description','year']) YIELD message RETURN message // 通过属性检索节点 CALL zdr.index.chineseFulltextIndexSearch('IKAnalyzer', 'description:复联', 100) YIELD node,weight RETURN node
五、总结
上述NEO4J中文全文索引解决方法,索引不会自动更新,修改节点属性以及新增节点时都需要重新建立索引。
NEO4J默认索引实现参考:neo4j-lucene-index