安装ik 分词器
准备 ik 分词器使用的 jar 包(下载地址)
复制 ik-analyzer-solr5-5.x.jar 到 apache-tomcat-8.5.34/webapp/solr/WEB-INF/lib
打开 \apache-solr\solr_home\solr_core\conf\managed-schema文件,在最下方追加
<!-- ik 中文分词器 -->
<fieldType name="text_ik" class="solr.TextField">
<analyzer>
<tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="false" />
</analyzer>
<analyzer type="query">
<tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="true" />
</analyzer>
</fieldType>
<!-- ik 中文分词器 end -->
或者(我使用的是第一种代码)
<fieldType name="text_ik" class="solr.TextField">
<analyzer type="index" useSmart="false" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
<analyzer type="query" useSmart="true" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>
<!-- 属性说明 -->
<!--
name:域的名称
class:指定solr的类型,
analyzer:分词器配置
type:index(索引分词器),query(查询分词器)
tokenizer:配置分词器
filter:过滤器
-->
重启 tomcat 就可以看到 分词器字段:text_ik
验证一下中文分词效果
如果启动tomcat 报如下的错
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile (default-compile) on project ik-analyzer-solr5: Fatal error compiling: invalid target release: 1.8 -> [Help 1]
检查你的JAVA_HOME。如果JAVA_HOME存在,则可能不是JAVA8。
配置动态域 dynamicField
何谓动态域呢?就是这个域的名称,是由表达式组成的,只要名称满足了这个 表达式,就可以用这个域
同样的认识一下这些属性
name:域的名称,该域的名称是通过一个表达式来指定的,只要符合这这个规则,就可以使用这个域。比如 aa_i,bb_i,13_i等等,只要满足这个表达式皆可
type:对应的值类型,相应的值必须满足这个类型,不然就会报错
indexed:是否要索引
stored:是否要存储
...其它的属性与普通的域一致
主键域 uniqueKey
<uniqueKey>id</uniqueKey>
用于确定和执行文档唯一性的字段,除非该字段标记为必需的“false”,否则将是必填字段,不能随便更改
复制域 copyField
<copyField source="company_name" dest="keywords_list"/>
source是源域,dest 是目标域,意思是将源域的内容复制到目标域中
目标域必须是允许多值的,如下,nultiValued必须为true,因为可能多个源域对应一个目标域,所以它需要以数组来存储
<field name="keywords_list" type="text_ik" indexed="true" stored="false" multiValued="true"/>
现在我们用field来配置实际的业务字段,建立一个测试数据表
打开 data-config.xml 新增一个 employee_info 的 entity节点
<dataConfig>
<dataSource type="JdbcDataSource" name="dataSource1" driver="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost/james" user="root" password="root" />
<document>
<entity name="article" pk="id"
query="select id,title,author,type from article"
deltaImportQuery="select * from article where id ='${dih.delta.id}'"
deltaQuery="select id from article where update_time > '${dataimporter.last_index_time}'">
<field name="id" column="id"/>
<field name="title" column="title"/>
<field name="author" column="author"/>
<field name="type" column="type"/>
<field name="update_time" column="update_time"/>
</entity>
<entity name="employee_info" pk="id"
query="select id,company_name,user_name,industry,position from employee_info"
deltaImportQuery="select * from employee_info where id ='${dih.delta.id}'"
deltaQuery="select id from employee_info where update_time > '${dataimporter.last_index_time}'">
<field name="id" column="id"/>
<field name="company_name" column="company_name"/>
<field name="user_name" column="user_name"/>
<field name="industry" column="industry"/>
<field name="position" column="position"/>
<field name="update_time" column="update_time"/>
</entity>
</document>
</dataConfig>
配置 managed-schema 文件,id 和 updata_time 共用,不需要再新增一个,主要看 employee_info的配置,将需要分词的域类型改成我们前面配置过的 “text_ik”
<!-- 主键的id就不需要配置了,默认已经把id配置为主键了,默认的配置如下 -->
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />
<field name="update_time" type="date" indexed="true" stored="true" omitNorms="true" />
<!-- article -->
<field name="title" type="string" indexed="true" stored="true" omitNorms="true"/>
<field name="author" type="string" indexed="true" stored="true" omitNorms="true"/>
<field name="type" type="string" indexed="true" stored="true" omitNorms="true" />
<!-- employee_info -->
<field name="company_name" type="text_ik" indexed="true" stored="true" omitNorms="true"/>
<field name="user_name" type="text_ik" indexed="true" stored="true" omitNorms="true"/>
<field name="industry" type="text_ik" indexed="true" stored="true" omitNorms="true" />
<field name="position" type="text_ik" indexed="true" stored="true" omitNorms="true" />
前面我们只说了复制域的属性,没有了解它的应用场景,我们举个例子来说明一下:
用户在搜索框搜索的时候,有可能输入的是商品名称,也有可能输入的是商品描述,也有可能输入的是一个商品类型,那么这些值的搜索,肯定在后台是对应一个域的,那么既然如此,我们就可以把这些域合并成一个,这样在后台只需要单独的对这一个域进行搜索就可以了
先定义一个目标域
<field name="keywords_list" type="text_ik" indexed="true" stored="false" multiValued="true"/>
将商品名称、描述、类型复制到上面定义的目标域(类型名和例子的描述不一样,不要太在意)
<copyField source="company_name" dest="keywords_list"/>
<copyField source="user_name" dest="keywords_list"/>
<copyField source="industry" dest="keywords_list" />
<copyField source="position" dest="keywords_list" />
保存,然后重启tomcat,打开 http://localhost:8080/solr/admin.html
在过滤条件处添加刚才复制域的 名称(keywords_list)后面接想要输入的关键字
使用SolrJ
既然是开发,当然需要使用代码来操作solr,SolrJ 是一个用来访问solr的java客户端,是一个可以和solr轻松对话的API,SolrJ隐藏了许多连接到Solr的细节,并允许你的应用程序通过简单的高级方法与Solr进行交互。
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>compile</scope>
</dependency>
SolrCient 是抽象的,所以连接远程Solr的时候,会创建一个HttpSolrClient 或 CouldSolrClient的实例,两者间通过Http与Solr通信,
不同之处在于HttpSolrClient使用明确的Solr URL进行配置,而CloudSolrClient是使用 SolrCloud 群集的 zkHost 字符串配置的。
单节点 Solr 客户端
配置指向具体 Solr 的索引库的 HTTP 路径,并实例化 HttpSolrClient 创建客户端
private static final String URL = "http://127.0.0.1:8080/solr/solr_core";
private HttpSolrClient solrClient = null;
@Before
public void init() {
solrClient = new HttpSolrClient(URL);
}
执行查询
@Test
public void testQuery() {
String queryStr = "*:*";
SolrQuery params = new SolrQuery(queryStr);
params.setStart(0);
params.setRows(10);
//输入单个条件进行查询
params.setQuery("keywords_list:董事*");
//输入多个条件进行查询
// params.setFilterQueries("company_name:阿里*","user_name:马*");
params.setFilterQueries("");
//排序
params.setSort("id", SolrQuery.ORDER.asc);
//只读取指定的属性,可写多个也可用逗号分开,默认显示全部
// params.setFields("id","company_name","user_name","industry");
try {
QueryResponse response = null;
response = solrClient.query(params);
SolrDocumentList list = response.getResults();
System.out.println("########### 总共 : " + list.getNumFound() + "条记录");
for (SolrDocument doc : list) {
System.out.println("{id:" + doc.get("id") + ",company_name:" + doc.get("company_name") + ",user_name:" + doc.get("user_name") + ",industry:" + doc.get("industry") + ",position:" + doc.get("position") + "}");
}
} catch (SolrServerException e) {
e.printStackTrace();
}
}
增加、修改的方法是一样的,创建一个 SolrInputDocument并将其传递到SolrClient的add()方法中,如果 Solr索引库 里存在提交的数据是修改,不存在是增加
@Test
public void addAndUpdateDoc() throws IOException, SolrServerException {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", 7);
doc.addField("company_name", "字节跳动科技有限公司");
doc.addField("user_name", "张某某");
doc.addField("industry", "互联网");
doc.addField("position", "CEO");
UpdateResponse rsp = solrClient.add(doc);
UpdateResponse rspCommit = solrClient.commit();
System.out.println("commit doc to index" + " result:" + rspCommit.getStatus() + " Qtime:" + rspCommit.getQTime());
}
删除方法,可以删除单个、多个,也可以根据查询条件删除
@Test
public void deleteDoc() throws IOException, SolrServerException {
// solrClient.deleteById("15");//删除单个
solrClient.deleteById(Arrays.asList("6", "12")); //批量删除
// solrClient.deleteByQuery("position:ceo");
solrClient.commit();
}
不管是 query(),delete(),还是add(),commit() 以后才能生效