正在用的Neo4j是当前最新版:3.1.0,各种踩坑。说一下如何在Neo4j 3.1.0中使用中文索引。选用了IKAnalyzer做分词器。
1. 首先参考文章:
https://segmentfault.com/a/1190000005665612
里面大致讲了用IKAnalyzer做索引的方式。但并不清晰,实际上,这篇文章的背景是用嵌入式Neo4j,即Neo4j一定要嵌入在你的Java应用中(https://neo4j.com/docs/java-reference/current/#tutorials-java-embedded),切记。否则无法使用自定义的Analyzer。其次,文中的方法现在用起来已经有问题了,因为Neo4j 3.1.0用了lucene5.5,故官方的IKAnalyzer已经不适用了。
2. 修正
转用 IKAnalyzer2012FF_u1.jar,在Google可以下载到(https://code.google.com/archive/p/ik-analyzer/downloads)。这个版本的IKAnalyzer是有小伙伴修复了IKAnalyzer不适配lucene3.5以上而修改的一个版本。但是用了这个包仍有问题,报错提示:
Caused by: java.lang.AbstractMethodError: org.apache.lucene.analysis.Analyzer.createComponents(Ljava/lang/String;)Lorg/apache/lucene/analysis/Analyzer$TokenStreamComponents;
解决方案:再增加两个类
package com.uc.wa.function;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Tokenizer;
public class IKAnalyzer5x extends Analyzer{
private boolean useSmart;
public boolean useSmart() {
return useSmart;
}
public void setUseSmart(boolean useSmart) {
this.useSmart = useSmart;
}
public IKAnalyzer5x(){
this(false);
}
public IKAnalyzer5x(boolean useSmart){
super();
this.useSmart = useSmart;
}
/**
protected TokenStreamComponents createComponents(String fieldName, final Reader in) {
Tokenizer _IKTokenizer = new IKTokenizer(in , this.useSmart());
return new TokenStreamComponents(_IKTokenizer);
}
**/
/**
* 重写最新版本的createComponents
* 重载Analyzer接口,构造分词组件
*/
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer _IKTokenizer = new IKTokenizer5x(this.useSmart());
return new TokenStreamComponents(_IKTokenizer);
}
}
package com.uc.wa.function;
import java.io.IOException;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;
public class IKTokenizer5x extends Tokenizer{
//IK�ִ���ʵ��
private IKSegmenter _IKImplement;
//��Ԫ�ı�����
private final CharTermAttribute termAtt;
//��Ԫλ������
private final OffsetAttribute offsetAtt;
//��Ԫ�������ԣ������Է���ο�org.wltea.analyzer.core.Lexeme�еķ��ೣ����
private final TypeAttribute typeAtt;
//��¼���һ����Ԫ�Ľ���λ��
private int endPosition;
/**
public IKTokenizer(Reader in , boolean useSmart){
super(in);
offsetAtt = addAttribute(OffsetAttribute.class);
termAtt = addAttribute(CharTermAttribute.class);
typeAtt = addAttribute(TypeAttribute.class);
_IKImplement = new IKSegmenter(input , useSmart);
}**/
/**
* Lucene 5.x Tokenizer��������캯��
* ʵ�����µ�Tokenizer�ӿ�
* @param useSmart
*/
public IKTokenizer5x(boolean useSmart){
super();
offsetAtt = addAttribute(OffsetAttribute.class);
termAtt = addAttribute(CharTermAttribute.class);
typeAtt = addAttribute(TypeAttribute.class);
_IKImplement = new IKSegmenter(input , useSmart);
}
/* (non-Javadoc)
* @see org.apache.lucene.analysis.TokenStream#incrementToken()
*/
@Override
public boolean incrementToken() throws IOException {
//������еĴ�Ԫ����
clearAttributes();
Lexeme nextLexeme = _IKImplement.next();
if(nextLexeme != null){
//��Lexemeת��Attributes
//���ô�Ԫ�ı�
termAtt.append(nextLexeme.getLexemeText());
//���ô�Ԫ����
termAtt.setLength(nextLexeme.getLength());
//���ô�Ԫλ��
offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());
//��¼�ִʵ����λ��
endPosition = nextLexeme.getEndPosition();
//��¼��Ԫ����
typeAtt.setType(nextLexeme.getLexemeTypeString());
//����true��֪�����¸���Ԫ
return true;
}
//����false��֪��Ԫ������
return false;
}
/*
* (non-Javadoc)
* @see org.apache.lucene.analysis.Tokenizer#reset(java.io.Reader)
*/
@Override
public void reset() throws IOException {
super.reset();
_IKImplement.reset(input);
}
@Override
public final void end() {
// set final offset
int finalOffset = correctOffset(this.endPosition);
offsetAtt.setOffset(finalOffset, finalOffset);
}
}
3. 最后
Neo4j中文索引建立和搜索示例:
/**
* 为单个结点创建索引
*
* @param propKeys
*/
public static void createFullTextIndex(long id, List propKeys) {
log.info("method[createFullTextIndex] begin.propKeys<"+propKeys+">");
Index entityIndex = null;
try (Transaction tx = Neo4j.graphDb.beginTx()) {
entityIndex = Neo4j.graphDb.index().forNodes("NodeFullTextIndex",
MapUtil.stringMap(IndexManager.PROVIDER, "lucene", "analyzer", IKAnalyzer5x.class.getName()));
Node node = Neo4j.graphDb.getNodeById(id);
log.info("method[createFullTextIndex] get node id<"+node.getId()+"> name<"
+node.getProperty("knowledge_name")+">");
/**获取node详细信息*/
Set> properties = node.getProperties(propKeys.toArray(new String[0]))
.entrySet();
for (Map.Entry property : properties) {
log.info("method[createFullTextIndex] index prop<"+property.getKey()+":"+property.getValue()+">");
entityIndex.add(node, property.getKey(), property.getValue());
}
tx.success();
}
}
/**
* 使用索引查询
*
* @param query
* @return
* @throws IOException
*/
public static List
注意到,在这里我用了IKQueryParser,即根据我们的查询词和要查询的字段,自动构造Query。这里是绕过了一个坑:用lucene查询语句直接查的话,是有问题的。比如:“address:南昌市” 查询语句,会搜到所有带市字的地址,这是非常不合理的。改用IKQueryParser即修正这个问题。IKQueryParser是IKAnalyzer自带的一个工具,但在 IKAnalyzer2012FF_u1.jar却被删减掉了。因此我这里重新引入了原版IKAnalyzer的jar包,项目最终是两个jar包共存的。
到这里坑就踩得差不多了。