一、中文分词插件
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
/** * @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
@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
CALL zdr.index.chineseFulltextIndexSearch('IKAnalyzer', 'description:复联* AND year:1999', 100) YIELD node,weight RETURN node.name,node.year,node.description,weight
@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