课程计划
Lucene的Field域
Lucene的索引库维护
lucene的查询
a. Query子对象
b. QueryParser
Lucene相关度排序
solr介绍
solr安装配置
Solrj的使用
准备工作
开发环境准备
数据库jar包
{width=”2.5in” height=”0.3333333333333333in”}
我们这里可以尝试着从数据库中采集数据,因此需要连接数据库,我们一直用MySQL,所以这里需要MySQL的jar包
MyBatis的jar包(可选)
从数据库采集数据就需要查询数据库,我们可以用jdbc原生的写DAO,还可以使用我们之前学习过的MyBatis动态代理DAO,因此可能需要MyBatis的jar包
数据库环境
数据库脚本:【资料\数据库\book.sql】,创建一个lucene数据库(utf-8),然后导入这个脚本。
新建java工程
由于是模拟练习,所以主要是学习Lucene的开发jar包的使用,所以普通的Java工程就可以。
{width=”3.4791666666666665in”
height=”4.989583333333333in”}
开发代码准备
MyBatis持久层开发
1. 创建pojo
package cn.itcast.pojo;
public class Book {
// 图书ID
private Integer id;
// 图书名称
private String name;
// 图书价格
private Float price;
// 图书图片
private String pic;
// 图书描述
private String desc;
getter/setter方法。。。。。。
}
2. 创建DAO接口
package cn.itcast.dao;
import java.util.List;
import cn.itcast.pojo.Book;
public interface BookDao {
public List<Book> queryBookList() throws Exception;
}
3. 创建映射文件
<?xml version=“1.0” encoding=“UTF-8”?>
<!DOCTYPE mapper
PUBLIC “-//mybatis.org//DTD Mapper 3.0//EN”
“http://mybatis.org/dtd/mybatis-3-mapper.dtd“>
<mapper namespace=“cn.itcast.dao.BookDao”>
<select id=“queryBookList” resultType=“cn.itcast.pojo.Book”>
select
id, name, price, pic, description as `desc`
from
book
</select>
</mapper>
创建索引的准备代码
在【cn.itcast.test】中创建【CreateIndexTest.java】
/**
* 使用中文分析器IKAnalyzer创建索引
*
* @author Derek Sun
*/
public class CreateIndexTest {
/**
* 创建索引的准备工作(使用IK分析器)
*/
private IndexWriter createIndexWriter(String indexRepositoryPath) throws Exception {
// 创建Directory对象
Directory dir = FSDirectory.open(new File(indexRepositoryPath));
// 创建一个标准分析器
Analyzer analyzer = new IKAnalyzer();
// 创建IndexWriterConfig对象
// 参数1: Lucene的版本信息, 可以选择对应的Lucene版本也可以使用LATEST
// 参数2: 分析器对象
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
// 创建IndexWriter对象
return new IndexWriter(dir, config);
}
/**
* 从数据库采集数据
*/
private List<Book> getBookInfoFromDB() throws Exception {
SqlSession sqlSession = null;
try {
InputStream inputStream = Resources.getResourceAsStream(“MyBatisConfig.xml”);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
BookDao bookDao = sqlSession.getMapper(BookDao.class);
List<Book> bookList = bookDao.queryBookList();
return bookList;
} catch(Exception e) {
e.printStackTrace();
throw e;
} finally {
sqlSession.close();
}
}
/**
* 把数据库中的数据创建索引
*/
@Test
public void testCreateIndex() throws Exception {
// 。。。。。。
}
}
Field域
Field的属性
Field域是Document文档的基本构成元素,和数据库表的字段类似,用于存储不同的数据,包括Field名和Field值两部分。Field域和数据库表字段一样也有不同类型的Field域。
我们先不讨论Field域的类型,首先不管是什么类型的Field域都会有三个共同的属性:
- 是否分词(tokenized):是否对域的内容进行分词处理。
前提:域内容需要查询时,内容多的分,内容少的不分;域内容不需要查询时不分。
比如:商品名称、商品描述等,这些内容都是查询信息的重点部分,而且内容多,因此需要分词
比如:商品id、订单号、身份证号等,这些内容也是查询的部分,但不需要分词。
- 是否索引(indexed):将Field分析后的词或整个Field的值进行索引,只有索引方可搜索到。
前提:域内容需要查询时索引,不需要查询时不索引。
比如:商品名称、商品描述分析后进行索引;商品id、订单号、身份证号不用分词但也要索引,这些将来都要作为查询条件。
比如:图片路径、文件路径等,不用作为查询条件就不用索引。
- 是否存储(stored):将Field值存储在文档中,存储在文档中的Field才可以从Document中获取。
前提:是否要在搜索结果中将内容展示给用户。
比如:商品名称、订单号,凡是将来要从Document中获取的内容都要存储。
比如:商品描述,内容较大不用存储,可以节省lucene的索引文件空间。如果要向用户展示商品描述可以从系统的关系数据库中获取。
常用Field类型
说完三个共同的属性后,我们再来看看Lucene的Field都有哪些常用类型:
不同类型的Field的上面三个共同属性的值会不同,用户可以根据此选用不同类型的Field完成业务需求。
Field类 数据类型 tokenized是否分词 Indexed Stored 说明
**是否索引** **是否存储**
StringField(FieldName, FieldValue,Store.YES)) 字符串 N Y Y或N 这个Field用来构建一个字符串Field,但是不会进行分词,会将整个串存储在索引中,比如(订单号,身份证号等)
是否存储在文档中用Store.YES或Store.NO决定
LongField(FieldName, FieldValue,Store.YES) Long型 Y Y Y或N 这个Field用来构建一个Long数字型Field,进行分词和索引,比如(价格)
是否存储在文档中用Store.YES或Store.NO决定
StoredField(FieldName, FieldValue) 重载方法,支持多种类型 N N Y 这个Field用来构建不同类型Field
不分析,不索引,但要Field存储在文档中
TextField(FieldName, FieldValue, Store.NO) 字符串 Y Y Y或N 如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略.
或 或
TextField(FieldName, reader) 流
修改Field
修改分析
对昨天的创建索引的代码中创建的Field对象进行类型的修改,根据不同的字段使用合适类型的Field类型:
- 图书id
是否分词:不分词,因为不会根据商品id来搜索商品
是否索引:索引,因为可能需要根据图书ID进行搜索
是否存储:要存储,因为查询结果页面需要使用id这个值。
使用Field:StringField(FieldName, FieldValue, Store.YES)
- 图书名称
是否分词:要分词,因为要根据图书名称的关键词搜索。
是否索引:要索引。
是否存储:要存储。
使用Field:TextField(FieldName, FieldValue, Store.YES)
- 图书价格
是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索引,因为lucene对数字型的内容要特殊
分词处理,需要分词和索引。
是否索引:要索引
是否存储:要存储
使用Field:FloatField(FieldName, FieldValue, Store.YES)
- 图书图片地址
是否分词:不分词
是否索引:不索引
是否存储:要存储
使用Field:StoredField(FieldName, FieldValue)
- 图书描述
是否分词:要分词
是否索引:要索引
是否存储:因为图书描述内容量大,不在查询结果页面直接显示,不存储(不存储是指不在lucene的Field域中保存)
使用Field:TextField(FieldName, FieldValue, Store.NO)
如果要在详情页面显示详细的描述内容信息,解决方案:
从lucene中取出图书的id,根据图书的id查询关系数据库(MySQL)中book表得到描述信息。
代码修改
对之前编写的testCreateIndex()方法进行修改。让不同的列根据需求使用合适类型的Field类
代码片段
// Document文档中添加Field域
// 图书id(不分词、不索引、只存储)
document.add(new StringField(“id”, book.getId().toString(), Store.YES));
// 图书名称(分词、索引、存储)
document.add(new TextField(“name”, book.getName().toString(), Store.YES));
// 图书价格(分词、索引、存储)
document.add(new FloatField(“price”, book.getPrice(), Store.YES));
// 图书图片地址(不分词、不索引、只存储)
document.add(new StoredField(“pic”, book.getPic().toString()));
// 图书描述(分词、索引、不存储)
document.add(new TextField(“desc”, book.getDescription().toString(), Store.NO));
图片地址没有作为索引条目:
{width=”4.550589457567804in”
height=”3.673611111111111in”}
索引库的维护
我们是不可能直接对索引表进行增删改查的,我们可以维护的表只有Document文档表,其它表(索引表+倒排链表)由Lucene的分析器和索引组件自动完成维护。
因此直接对文档对象进行增删改查就间接完成了对索引表和倒排链表的维护工作。
添加文档
添加文档间接完成的处理:(添加新的文档对象经过分析器会产出新的索引项)
对于已存在的索引项,更新这个索引项对应的倒排链表,把新的信息(文档的id以及TF值)加进去;
对于不存在的索引项,不存在,说明是新索引项,索引信息添加进索引表和文档倒排链表中。
代码实现
@Test
public void testAddIndex() throws Exception {
// 第一步:创建IndexWriter
IndexWriter indexWriter = createIndexWriter(“C:\\mydir\\03_workspace\\lucene\\indexDB”);
// 创建两个文档对象
Document doc1 = new Document();
Document doc2 = new Document();
// 给第一个文档对象添加域
// id
doc1.add(new StringField(“id”, “6”, Store.YES));
// 图书名称
doc1.add(new TextField(“name”, “传智播客java”, Store.YES));
// 图书描述
doc1.add(new TextField(“desc”, “新增document2”, Store.NO));
// 给第二个文档对象添加域
// id
doc2.add(new StringField(“id”, “7”, Store.YES));
// 图书名称
doc2.add(new TextField(“name”, “itcast java”, Store.YES));
// 图书描述
doc2.add(new TextField(“desc”, “新增document3”, Store.NO));
// 创建索引
indexWriter.addDocument(doc1);
indexWriter.addDocument(doc2);
// 关闭IndexWriter对象
indexWriter.close();
}
测试
先在扩展词库中增加两个扩展词:
注意:词库文件在eclipse不能直接打开,如果在eclipse中打开会直接用记事本打开,但是记事本一保存可能会保存出带bom头的utf-8格式的文件,所以必须在外面通过专业记事本修改,但要注意这时候要直接去该编译后路径下的文件【bin】下的,如果你改的还是【config】下的就不会被自动编译到bin下,就不会起作用,但可以你还很纳闷为什么不好用。
{width=”1.4978357392825896in”
height=”1.8787871828521434in”}在bin下打开【ext.dic】,增加两个扩展词
{width=”2.025022965879265in”
height=”1.2575754593175854in”}
测试结果:
删除文档
根据索引项条件删除符合的文档对象
删除符合索引项条件的文档详细处理:
索引表中全部索引项保持不变;
清空指定索引项的倒排链表;
从其他索引项的倒排链表中删除第二步中涉及的文档ID节点。
代码【DeleteIndexTest.java】
public class DeleteIndexTest {
/**
* 根据Term删除
* @throws Exception
*/
@Test
public void test() throws Exception {
// 创建目录对象,指定索引路径
Directory dir = FSDirectory.open(new File(“C:\\mydir\\03_workspace\\lucene\\indexDB”));
// 创建分析器
Analyzer analyzer = new IKAnalyzer();
// 创建写入配置
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
// 创建写入对象
IndexWriter indexWriter = new IndexWriter(dir, config);
// 根据一个term对象删除索引
indexWriter.deleteDocuments(new Term(“name”, “itcast”));
// 释放资源
indexWriter.close();
}
}
测试结果:
如果想要彻底删除包括文档,以及文档对应索引的关联,需要强制清空“回收站”:相当于对删除后的索引库进行数据的再整理,将没有关联任何倒排链表的索引删除,空的DocID也删除。执行【DeleteIndexTest.java】的【test2】
/**
* 普通删除+强制情况回收站
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
// 创建目录对象,指定索引路径
Directory dir = FSDirectory.open(new File(“C:\\mydir\\03_workspace\\lucene\\indexDB”));
// 创建分析器
Analyzer analyzer = new IKAnalyzer();
// 创建写入配置
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
// 创建写入对象
IndexWriter indexWriter = new IndexWriter(dir, config);
// 根据一个term对象删除索引
indexWriter.deleteDocuments(new Term(“name”, “itcast”));
// 强制清空回收站
indexWriter.forceMergeDeletes();
// 释放资源
indexWriter.close();
}
强制清空回收站后索引库文件发生了变化:
{width=”1.53125in” height=”2.5520833333333335in”}
用Luke查看删除后的索引库:name:itcast的term索引项已经没有了:
{width=”5.786151574803149in”
height=”4.666666666666667in”}
Document就剩六个了,而且docID的位置也没给留,原先的docID为6的就是name:itcast的那条,已经被删除了:
{width=”5.816102362204725in”
height=”4.7348479877515315in”}
用【name:itcast】条件查询自然也是什么都查不到了:
{width=”5.809414916885389in”
height=”3.2575754593175854in”}
删除全部(慎用)
将索引目录的索引信息全部删除,直接彻底删除,无法恢复。
执行【DeleteIndexTest.java】的【test3】
/**
* 全部删除
* @throws Exception
*/
@Test
public void test3() throws Exception {
// 创建目录对象,指定索引路径
Directory dir = FSDirectory.open(new File(“C:\\mydir\\03_workspace\\lucene\\indexDB”));
// 创建分析器
Analyzer analyzer = new IKAnalyzer();
// 创建写入配置
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
// 创建写入对象
IndexWriter indexWriter = new IndexWriter(dir, config);
// 删除所有的文档对象
indexWriter.deleteAll();
// 释放资源
indexWriter.close();
}
删除后的索引目录:
{width=”1.3712117235345582in”
height=”1.2152055993000874in”}
索引域数据清空
{width=”6.122222222222222in”
height=”3.0097222222222224in”}
文档域数据也清空
{width=”5.767361111111111in”
height=”2.3194444444444446in”}
说明:
建议参照关系数据库的根据主键删除的方式,所以索引中创建document对象时,需要创建一个主键Field,删除时根据此主键Field删除。这样比较安全。
删除小结
正常的删除操作就是执行IndexWriter的deleteDocuments(term)方法就可以了。
慎用forceMergeDeletes()和deleteAll(),因为索引库没有事务,不能回滚。
修改文档
Lucene的更新文档采用的是先删后增的方式,即先删除符合修改条件的文档(即上面刚学完的普通删除),然后再添加新的文档,有关索引项的关联信息跟着做相应的改动,改动方式和上面学到的一致。
执行【UpdateIndexTest.java】
package cn.itcast.test;
import java.io.File;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class UpdateIndexTest {
@Test
public void test() throws Exception {
// 创建分析器对象
Analyzer analyzer = new IKAnalyzer();
// 创建目录对象,指定索引库目录
Directory dir = FSDirectory.open(new File(“C:\\mydir\\03_workspace\\lucene\\indexDB”));
// 创建写入的配置信息
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
// 创建写入对象
IndexWriter indexWriter = new IndexWriter(dir, config);
// 创建一个新的document对象
Document doc = new Document();
doc.add(new StringField(“id”, “3”, Store.YES));
doc.add(new TextField(“name”, “mybatis”, Store.YES));
doc.add(new StoredField(“pic”, “88272828282.jpg”));
doc.add(new FloatField(“price”, 100f, Store.YES));
// 根据term词项更新
indexWriter.updateDocument(new Term(“name”, “mybatis”), doc);
// 释放资源
indexWriter.close();
}
}
测试结果:
[]{#_MON_1574945023 .anchor}
与原来的文档对象相比相当于把价格修改了:但实际上文档对象的ID已经发生了改变,即先删除后增加了文档。
注意:如果更新索引的目标文档对象不存在,则直接执行添加。
小结
学习索引库的维护并不是要求大家掌握Lucene维护的代码方法(当然能掌握更好),主要目的是让大家了解Lucene对索引库进行维护时的一些底层操作,即维护操作对文档对象做了什么,对索引做了什么,了解索引库维护的基本操作。
Lucene索引库查询
查询方式
学习Lucene查询的目的:初步了解Lucene查询语句的写法,在接下来Solr学习中更方便的写出各种查询语句。
Lucene要搜索信息需要通过Query查询对象进行。实际查询中都需需要通过QueryParser将查询信息进行解析,然后自动生成各种类型的Query对象。所以这里我们先来认识一下Lucene的查询对象Query。
Query的子类
Query是一个抽象类,lucene提供了很多它的实现类,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。实际的查询条件语句分析后会形成符合这些类型的Query子类。
当然也可以直接通过程序手动创建这些查询对象,但因为没有分析器,这样new出来的查询对象没有实际意义的,只能用在学习试验。
我们这里直接手动new各种Query子类主要是了解一下各种子类的查询结果以及各个子类对应的查询语句是什么样。
抽取搜索逻辑:
private void doSearch(Query query) throws IOException {
System.out.println(“实际的查询条件:” + query);
// 创建Directory对象
Directory directory = FSDirectory.open(new File(“C:\\mydir\\03_workspace\\lucene\\indexDB”));
// 创建IndexReader对象
IndexReader reader = DirectoryReader.open(directory);
// 创建IndexSearcher对象
IndexSearcher searcher = new IndexSearcher(reader);
// 执行搜索,返回结果集TopDocs
// 参数1:查询条件对象,参数2:返回的数据条数,指定查询结果最顶部的n条数据返回
TopDocs topDocs = searcher.search(query, 10);
System.out.println(“查询到的数据总条数是:” + topDocs.totalHits);
// 获取查询结果集
ScoreDoc[] docs = topDocs.scoreDocs;
// 遍历结果集
for (ScoreDoc scoreDoc : docs) {
// 根据文档对象ID取得文档对象
int docID = scoreDoc.doc;
Document doc = searcher.doc(docID);
System.out.println(“======================================”);
System.out.println(“docID:” + docID);
System.out.println(“bookId:” + doc.get(“id”));
System.out.println(“name:” + doc.get(“name”));
System.out.println(“price:” + doc.get(“price”));
System.out.println(“pic:” + doc.get(“pic”));
// System.out.println(“desc:” + doc.get(“desc“));
}
// 3. 关闭IndexReader对象,释放资源
reader.close();
}
全部索引查询——MatchAllDocsQuery
查询索引目录下所有文档的全部内容:相当于【*:*】
// 用MatchAllDocsQuery对象查询
@Test
public void testMatchAllDocsQuery() throws Exception {
Query query = new MatchAllDocsQuery();
doSearch(query);
}
结果:
实际的查询条件:*:*
查询到的数据总条数是:5
======================================
docID:0
bookId:1
name:java 编程思想
price:71.5
pic:23488292934.jpg
======================================
docID:1
bookId:2
name:apache lucene
price:66.0
pic:77373773737.jpg
======================================
docID:2
bookId:3
name:mybatis
price:55.0
pic:88272828282.jpg
======================================
docID:3
bookId:4
name:spring
price:56.0
pic:83938383222.jpg
======================================
docID:4
bookId:5
name:solr
price:78.0
pic:99999229292.jpg
精确指定索引词项查询——TermQuery
TermQuery:通过Term项查询。
TermQuery不使用分析器,所以不进行分析,是精确匹配,大小写敏感。所以建议匹配不分词的Field域查询,比如订单号、分类ID号等。而且只接受一个Term对象
【查询name域中包含luceneupdatetest的文档】
/**
* 使用TermQuery对象查询
*/
@Test
public void testByTermQuery() throws Exception {
Query query = new TermQuery(new Term(“name”, “mybatis”));
doSearch(query);
}
结果:
实际的查询条件:name:mybatis
查询到的数据总条数是:1
======================================
docID:2
bookId:3
name:mybatis
price:55.0
pic:88272828282.jpg
因为在创建索引时经过分析name
Filed域已经把大写转小写了,所以索引表中的term此项是【mybatis】而不是【MyBatis】
如果把条件改成:Query query = new TermQuery(new Term(“name”,
“MyBatis”));
结果:
实际的查询条件:name:MyBatis
查询到的数据总条数是:0
数值范围查询——NumericRangeQuery
[[]{#OLE_LINK67 .anchor}]{#OLE_LINK68
.anchor}NumericRangeQuery指定数字范围的查询,五个参数分别是:域名、最小值、最大值、是否包含最小值、是否包含最大值:
/**
* 使用NumericRangeQuery
*/
@Test
public void testByNumericRangeQuery() throws Exception {
Query query = NumericRangeQuery.newFloatRange(“price”, 55f, 66f, true, false);
doSearch(query);
System.out.println(” “);
query = NumericRangeQuery.newFloatRange(“price”, 55f, 66f, false, true);
doSearch(query);
System.out.println(” “);
query = NumericRangeQuery.newFloatRange(“price”, 55f, 66f, true, true);
doSearch(query);
}
结果:用方括号表示包含,用大括号表示不包含
实际的查询条件:price:[55.0 TO 66.0}
查询到的数据总条数是:2
======================================
docID:2
bookId:3
name:mybatis
price:55.0
pic:88272828282.jpg
======================================
docID:3
bookId:4
name:spring
price:56.0
pic:83938383222.jpg
实际的查询条件:price:{55.0 TO 66.0]
查询到的数据总条数是:2
======================================
docID:1
bookId:2
name:apache lucene
price:66.0
pic:77373773737.jpg
======================================
docID:3
bookId:4
name:spring
price:56.0
pic:83938383222.jpg
实际的查询条件:price:[55.0 TO 66.0]
查询到的数据总条数是:3
======================================
docID:1
bookId:2
name:apache lucene
price:66.0
pic:77373773737.jpg
======================================
docID:2
bookId:3
name:mybatis
price:55.0
pic:88272828282.jpg
======================================
docID:3
bookId:4
name:spring
price:56.0
pic:83938383222.jpg
组合条件查询——BooleanQuery
BooleanQuery布尔查询,实现组合条件查询:它可以组合多个其他类型的query对象。
参数1:被组合的一个query对象
参数2:每加一个条件要指定是MUST,还是MUST_NOT,还是SHOULD,是对参数1条件的逻辑控制。
参数2的值:
Occur.MUST:必须满足此条件
Occur.SHOULD:应该满足此条件(也可以不满足)
Occur.MUST_NOT:必须不满足此条件
MUST、SHOULD、MUST_NOT的使用规则:
MUST:在任何时候,与其他条件组合都有效。
MUST_NOT:
不能与MUST_NOT组合,否则没有任何结果返回
与其他条件组合都有效。
SHOULD:
不能与MUST组合,否则SHOULD条件失效
与其他条件组合都有效。
常用组合关系代表的意思如下:
1、MUST+MUST表示“与”的关系,即“交集”。
2、SHOULD+SHOULD表示“或”的关系,即“并集”。
3、MUST+MUST_NOT表示包含前者并且排除后者的集合。
4、MUST_NOT+MUST_NOT,什么都查询不出来(违规条件)
5、SHOULD+MUST,只查MUST条件的查询结果,SHOULD条件失效(违规条件)
6、SHOULD+MUST_NOT,与MUST+MUST_NOT等效。
注意:从上面的组合可以得出,正常情况下SHOULD不应该跟MUST或MUST_NOT组合使用,SHOULD就跟SHOULD组合使用,这样才是正常的检索。MUST_NOT和MUST_NOT也是扯淡的。
示例【MUST与MUST】:交集
/**
* 使用BooleanQuery:MUST与MUST表示“与”的关系,即“交集”。
*/
@Test
public void testByBooleanQuery1() throws Exception {
Query query1 = new TermQuery(new Term(“name”, “apache”));
Query query2 = NumericRangeQuery.newFloatRange(“price”, 55f, 66f, false, true);
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(query1, Occur.MUST); // 书名带有apache的必须包含进来
booleanQuery.add(query2, Occur.MUST); // 价格大于55,小于等于66范围内的书籍必须包含进来
System.out.println(“实际的查询条件:” + booleanQuery);
// and相当于求它们的交际
doSearch(booleanQuery);
}
结果:
实际的查询条件:+name:apache +price:{55.0 TO 66.0]
查询到的数据总条数是:1
======================================
docID:1
bookId:2
name:apache lucene
price:66.0
pic:77373773737.jpg
示例【SHOULD与SHOULD】:并集
/**
* 使用BooleanQuery:SHOULD与SHOULD表示“或”的关系,即“并集”。
*/
@Test
public void testByBooleanQuery2() throws Exception {
Query query1 = new TermQuery(new Term(“name”, “lucene”));
Query query2 = NumericRangeQuery.newFloatRange(“price”, 55f, 66f, true, false);
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(query1, Occur.SHOULD); // 书名带有mybatis的应该包含进来
booleanQuery.add(query2, Occur.SHOULD); // 价格大于等于55,小于66范围内的书籍应该包含
// and相当于求它们的交际
doSearch(booleanQuery);
}
结果:
实际的查询条件:name:lucene price:[55.0 TO 66.0}
查询到的数据总条数是:3
======================================
docID:1
bookId:2
name:apache lucene
price:66.0
pic:77373773737.jpg
======================================
docID:2
bookId:3
name:mybatis
price:55.0
pic:88272828282.jpg
======================================
docID:3
bookId:4
name:spring
price:56.0
pic:83938383222.jpg
示例【MUST与MUST_NOT】:前者包含后者排除
/**
*使用BooleanQuery:MUST与MUST_NOT表示包含前者并且排除后者的集合。
*/
@Test
public void testByBooleanQuery3() throws Exception {
Query query1 = NumericRangeQuery.newFloatRange(“price”, 55f, 66f, true, false);
Query query2 = new TermQuery(new Term(“name”, “mybatis”));
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(query1, Occur.MUST); // 价格大于等于55,小于66范围内的书籍
booleanQuery.add(query2, Occur.MUST_NOT); // 书名带有mybatis的不能包含进来
System.out.println(“实际的查询条件:” + booleanQuery);
// and相当于求它们的交际
doSearch(booleanQuery);
}
结果:
实际的查询条件:-name:mybatis +price:[55.0 TO 66.0}
查询到的数据总条数是:1
======================================
docID:3
bookId:4
name:spring
price:56.0
pic:83938383222.jpg
示例【MUST_NOT和MUST_NOT】:违规条件,什么都查询不到
/**
*使用BooleanQuery:MUST_NOT与MUST_NOT,什么都查询不出来(违规条件)
*/
@Test
public void testByBooleanQuery4() throws Exception {
Query query1 = NumericRangeQuery.newFloatRange(“price”, 55f, 56f, true, true);
Query query2 = new TermQuery(new Term(“name”, “apache”));
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(query1, Occur.MUST_NOT); // 书名带有apache的必须不包含进来
booleanQuery.add(query2, Occur.MUST_NOT); // 价格大于等于55,小于等于56范围内的书籍必须不包含进来
// and相当于求它们的交际
doSearch(booleanQuery);
}
结果:
实际的查询条件:-name:apache -price:[55.0 TO 56.0]
查询到的数据总条数是:0
MUST_NOT必须在它之前有条件确定下来一个固定范围内再进行排除条件过滤,但是两个MUST_NOT,谁也没有给定范围,所以最终查不到结果。
【SHOULD与MUST】:SHOULD控制的条件失效不被查询考虑,只查询MUST控制的条件。
/**
*使用BooleanQuery:SHOULD与MUST,只查MUST条件的查询结果,SHOULD条件失效(违规条件)
*/
@Test
public void testByBooleanQuery5() throws Exception {
Query query1 = new TermQuery(new Term(“name”, “lucene”));
Query query2 = NumericRangeQuery.newFloatRange(“price”, 55f, 56f, true, true);
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(query1, Occur.SHOULD); // 书名带有lucene的应该包含进来(SHOULD控制的条件失效)
booleanQuery.add(query2, Occur.MUST); // 价格大于等于55,小于等于56范围内的书籍必须包含进来
// and相当于求它们的交际
doSearch(booleanQuery);
}
结果:
实际的查询条件:name:lucene +price:[55.0 TO 56.0]
查询到的数据总条数是:2
======================================
docID:2
bookId:3
name:mybatis
price:55.0
pic:88272828282.jpg
======================================
docID:3
bookId:4
name:spring
price:56.0
pic:83938383222.jpg
示例【SHOULD与MUST_NOT】:与MUST+MUST_NOT等效。
/**
*使用BooleanQuery:SHOULD与MUST_NOT, 与MUST+MUST_NOT等效。
*/
@Test
public void testByBooleanQuery6() throws Exception {
Query query1 = NumericRangeQuery.newFloatRange(“price”, 55f, 66f, true, false);
Query query2 = new TermQuery(new Term(“name”, “mybatis”));
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(query1, Occur.SHOULD); // 价格大于等于55,小于66范围内的书籍应该包含
booleanQuery.add(query2, Occur.MUST_NOT); // 书名带有mybatis的不能包含进来
// and相当于求它们的交际
doSearch(booleanQuery);
// 与MUST+MUST_NOT等效
Query query3 = NumericRangeQuery.newFloatRange(“price”, 55f, 66f, true, false);
Query query4 = new TermQuery(new Term(“name”, “mybatis”));
BooleanQuery booleanQuery2 = new BooleanQuery();
booleanQuery2.add(query3, Occur.MUST); // 价格大于等于55,小于66范围内的书籍应该包含
booleanQuery2.add(query4, Occur.MUST_NOT); // 书名带有mybatis的不能包含进来
// and相当于求它们的交际
doSearch(booleanQuery2);
}
结果:
实际的查询条件:-name:mybatis price:[55.0 TO 66.0}
查询到的数据总条数是:1
======================================
docID:3
bookId:4
name:spring
price:56.0
pic:83938383222.jpg
通过QueryParser搜索
我们可以直接写类似上面打印出来的查询语句,通过QueryParser的Parse方法进行解析生成Query对象。
如果使用QueryParser需要的jar包就是:
{width=”2.7578958880139983in”
height=”1.0984853455818022in”}
QueryParser
QueryParser对象的创建就是直接new,参数1:默认的搜索域,参数2:一个分析器对象。
默认搜索域的作用:当查询条件中没有指定域名时就使用默认域。
基础查询
也叫关键词查询。
语法:域名+:+搜索的关键字。 例如:name:java
示例:由于有分析器,所以条件大小写都是一样的,经过分析器后都会处理成小写。
@Test
public void testByQueryParser1() throws Exception {
// 创建分析器
Analyzer analyzer = new StandardAnalyzer();
// 创建查询解析器
QueryParser queryParser = new QueryParser(“desc”, analyzer);
// 根据查询解析器常见查询对象
Query query = queryParser.parse(“name:Mybatis”);
// 进行查询
doSearch(query);
System.out.println(” “);
// 根据查询解析器常见查询对象
query = queryParser.parse(“name:mybatis”);
// 进行查询
doSearch(query);
System.out.println(” “);
// 根据查询解析器常见查询对象
query = queryParser.parse(“mybatis”);
// 进行查询
doSearch(query);
}
结果:
实际的查询条件:name:mybatis
查询到的数据总条数是:1
======================================
docID:2
bookId:3
name:mybatis
price:55.0
pic:88272828282.jpg
实际的查询条件:name:mybatis
查询到的数据总条数是:1
======================================
docID:2
bookId:3
name:mybatis
price:55.0
pic:88272828282.jpg
实际的查询条件:desc:mybatis
查询到的数据总条数是:1
======================================
docID:2
bookId:3
name:mybatis
price:55.0
pic:88272828282.jpg
范围查询
Lucene有一个最低级的缺点:QueryParser不支持对数字范围的搜索,它只支持字符串的范围。虽然上面的NumericRangeQuery代码示例打印出来的条件写法和这里要求的是一样的,但是仍然不能在QueryParser中这样写,很无奈,所以做数值范围查询只能用NumericRangeQuery子类。
这里主要强调一下数值范围的查询语句写法:**域名+:+[最小值 TO
最大值](用[表示小于等于,{表示小于,]表示大于等于,}表示大于,TO要大写)**
例如:size:[1 TO 1000](1 <= size <=1000)
好消息是solr支持这样(域名+:+[最小值 TO 最大值])的数值范围查询语法。
组合条件查询
在QueryParser对象中写条件时可以使用:AND,OR,NOT要大写
必须满足此条件 AND或+(加号) 相当于Occur.MUST
应该满足此条件 OR或空(不用符号) 相当于Occur.SHOULD
必须不满足此条件 NOT或-(减号) 相当于Occur.MUST_NOT
前提:QueryParser queryParser = new QueryParser(“desc”, analyzer);
1)+条件1 +条件2:两个条件都必须满足(相当于MUST与MUST)
queryParser.parse(“+mybatis +lucene”) 或
queryParser.parse(“mybatis AND lucene”)
2)条件1 条件2:两个条件满足其一即可(相当于SHOULD与SHOULD)
queryParser.parse(“mybatis lucene”)
queryParser.parse(“mybatis OR lucene”)
3)+条件1 条件2:必须满足条件1,条件2失效(相当于MUST与SHOULD)
queryParser.parse(“+lucene mybatis”),必须包含lucene的,忽略mybatis的。
╳ queryParser.parse(“AND lucene OR
mybatis”),最前面不能写AND或OR,但可以写NOT
4)+条件1
-条件2:条件1必须满足,条件2必须不满足(相当于MUST与MUST_NOT)
queryParser.parse(“+lucene -mybatis”)
queryParser.parse(“NOT mybatis AND lucene”)
5)-条件1 条件2:相当于MUST_NOT与SHOULD
queryParser.parse(“-lucene mybatis”)
queryParser.parse(“NOT lucene OR mybatis”)
默认搜索域对查询条件的影响
设置了默认搜索域后,查询条件中必须明确给出要搜索的域才会按照给出的域搜索,否则都会默认认为是搜索默认搜索域。假设:QueryParser
queryParser = new QueryParser(“desc”,
analyzer);表明设置了desc为默认搜索域。
比如:
Query query = queryParser.parse(“java AND lucene”);
被解释成:desc:java AND
desc:lucene,条件中没有明确指定时就用默认的域desc。
Query query = queryParser.parse(“name:java AND lucene”);
被解释成:name:java AND desc:lucene,而不是name:java AND name:lucene。
Query query = queryParser.parse(“name:lucene AND java”);
被解释成:name:lucene AND desc:java,而不是name:lucene AND name:java。
以上两种条件最终的查询结果是不同的,但如果理解错误很容易误解为查询结果应该是一样的。
Query query = queryParser.parse(“desc:java AND lucene”);
被解释成:desc:java AND
desc:lucene,第二个条件其实使用的是默认域,只不过默认域和前面指定
的域名一致而已
Query query = queryParser.parse(“name:java”);
还可以指定一个新的Field域,完全跟默认的搜索域没有任何关系,这时只会搜索name域,desc域与本次查询没有任何关系。
MultiFieldQueryParser
通过MultiFieldQueryParse对多个域查询,两个域之间相当于用或连接:
/**
* 使用MultiFieldQueryParser多域查询解析器进行查询
*/
@Test
public void testByMultiFieldQueryParser() throws Exception {
// 创建分析器
Analyzer analyzer = new StandardAnalyzer();
String[] fields = {“name”, “desc”};
// 创建多域查询解析器
MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, analyzer);
// 根据查询条件解析出对应的查询对象
Query query = queryParser.parse(“lucene”);
// 打印查询对象
System.out.println(query);
// 进行查询
doSearch(query);
}
结果:
name:lucene desc:lucene
查询到的数据总条数是:2
======================================
docID:1
bookId:2
name:apache lucene
price:66.0
pic:77373773737.jpg
======================================
docID:4
bookId:5
name:solr
price:78.0
pic:99999229292.jpg
生成的查询语句:
name:lucene desc:lucene
没有+和-,说明是或条件,即name:lucene OR desc:lucene
相关度排序(了解)
什么是相关度排序
相关度排序是查询结果按照与查询关键字的相关性进行排序,越相关的越靠前。比如搜索“Lucene”关键字,与该关键字最相关的文章应该排在前边。
相关度打分
Lucene对查询的关键字与包含这个关键字的文档的相关度进行打分,得分高的就排在前边。如何打分呢?Lucene是在用户进行检索时实时根据搜索的关键字计算出来的,分两步:
1)计算出词(Term)的权重
2)根据词的权重值,计算文档相关度得分。
什么是词的权重?
通过索引部分的学习,明确索引的最小单位是一个Term(索引词典中的一个词)。搜索也是从索引域中查询Term,再根据Term找到文档。Term对文档的重要性称为权重,影响Term权重有两个因素:
- Term Frequency (tf):
指此Term在此文档中出现了多少次。tf 越大说明越重要。
词(Term)在文档中出现的次数越多,说明此词(Term)对该文档越重要,如“Lucene”这个词,在文档中出现的次数很多,说明该文档主要就是讲Lucene技术的。
- Document Frequency (df):
指有多少文档包含此Term。df 越大说明越不重要。
比如,在多篇英语文档中,this出现的次数更多,就说明越重要吗?不是的,有越多的文档包含此词(Term),
说明此词(Term)太普通,不足以区分这些文档,因而重要性越低。
设置boost值影响相关度排序
boost是一个加权值(默认加权值为1.0f),它可以影响权重的计算。在索引时对某个文档中的field设置加权值,设置越高,在搜索时匹配到这个文档就可能排在前边。
未设置权重:
当未设置boost加权时name为spring的排名是最后一名,希望把name为spring的排名提高。
{width=”5.7625in” height=”4.1375in”}
先清空索引库,然后可以重新做一个类试验加权值boost的,就是在创建索引的代码中追加加权值的设置逻辑:
package cn.itcast.test2;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.FloatField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Before;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
import cn.itcast.dao.BookDao;
import cn.itcast.pojo.Book;
/**
* 增加加权值boost
*
* @author Derek Sun
*
*/
public class CreateIndexTest3 {
private SqlSessionFactory sqlSessionFactory = null;
@Before
public void init() throws Exception {
InputStream inputStream = Resources.getResourceAsStream(“MyBatisConfig.xml”);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test() throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession();
BookDao bookDao = sqlSession.getMapper(BookDao.class);
// 1. 从数据库采集数据
List<Book> bookList = bookDao.queryBookList();
// 2. 创建Document文档对象
List<Document> documents = new ArrayList<Document>();
for (Book book : bookList) {
Document document = new Document();
// Document文档中添加Field域
// 图书id
// 不分词、不索引、只存储
document.add(new StoredField(“id”, book.getId().toString()));
// 图书名称
// 分词、索引、存储
document.add(new TextField(“name”, book.getName().toString(), Store.YES));
// 图书价格
// 分词、索引、存储
document.add(new FloatField(“price”, book.getPrice(), Store.YES));
// 图书图片地址
// 不分词、不索引、只存储
document.add(new StoredField(“pic”, book.getPic().toString()));
// 图书描述
// 分词、索引、不存储
TextField detailField = new TextField(“desc”, book.getDescription().toString(), Store.NO);
// 判断是不是spring的那一条,如果是就增加它的加权值
if (book.getId() == 4) {
detailField.setBoost(100f);
}
document.add(detailField);
// 把Document放到list中
documents.add(document);
}
// 3. 创建Analyzer分词器(分析文档,对文档进行切分词)
Analyzer analyzer = new IKAnalyzer();
// 4. 创建索引
// 4-1. 创建Directory对象,声明索引库位置
Directory dir = FSDirectory.open(new File(“C:\\mydir\\03_workspace\\lucene\\indexDB”));
// 4-2. 创建IndexWriteConfig对象,写入索引需要的配置
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
// 4-3. 创建IndexWriter写入对象
IndexWriter indexWriter = new IndexWriter(dir, config);
// 4-4. 把Document写入到索引库,通过IndexWriter对象添加文档对象document
for (Document doc : documents) {
indexWriter.addDocument(doc);
}
// 4-5. 释放资源(释放资源同时还有commit操作)
indexWriter.close();
} catch(Exception e) {
e.printStackTrace();
throw e;
} finally {
sqlSession.close();
}
}
}
执行创建索引的逻辑,使用luke重载新生成的索引库,再次查询spring在第一:
{width=”3.8059142607174103in”
height=”2.798611111111111in”}
Solr介绍(重点)
什么是solr
Solr是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务。Solr是一个可以独立运行的搜索服务器,可以独立运行在Jetty、Tomcat等这些Servlet容器中。使用solr进行全文检索服务的话,只需要通过http请求访问该服务器即可。
Solr提供了比Lucene更为丰富的查询语言,同时实现了文档的Field域的可配置、可扩展,并对索引、搜索性能进行了优化。
使用Solr 进行创建索引和搜索索引的实现方法很简单,如下:
创建索引:客户端(可以是浏览器可以是Java程序)用 POST 方法向 Solr
服务器发送一个描述 Field 及其内容的 XML
文档,Solr服务器根据xml文档添加、删除、更新索引 。搜索索引:客户端(可以是浏览器可以是Java程序)用 GET方法向 Solr
服务器发送请求,然后对
Solr服务器返回Xml、json等格式的查询结果进行解析。Solr不提供构建页面UI的功能。Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。
Solr和Lucene的区别
[[]{#OLE_LINK12 .anchor}]{#OLE_LINK11
.anchor}Lucene是一个开放源代码的全文检索引擎开发工具包,它不是一个完整的全文检索应用,不能独立运行。我们可以借助它在企业的系统中实现全文检索搜索功能,或者以Lucene为基础构建一个独立的全文检索搜索引擎应用,像百度,谷歌一样。
Solr是用Lucene开发的一个全文检索搜索引擎服务,它是一个完整的全文检索应用,可以独立运行,企业中可以利用solr的搜索引擎服务非常快速的构建企业自己的搜索引擎,也可以通过Solr高效的完成站内搜索功能。
说直白一些就是:Lucene不能拿来直接用,要在它基础上开发;Solr就是用Lucene开发的搜索引擎服务,可以拿来直接用。
{width=”4.7194083552056in”
height=”2.756522309711286in”}
Solr安装配置(重点)
下载solr
Solr和lucene的版本是同步更新的,本课程使用的版本:4.10.3
下载地址:http://archive.apache.org/dist/lucene/solr/
Linux下需要solr-4.10.3.tgz,windows下需要solr-4.10.3.zip。
{width=”2.9055555555555554in” height=”1.84375in”}
解压solr-4.10.3.zip:
{width=”2.7604166666666665in”
height=”4.082638888888889in”}
目录说明:
example:solr工程的实例目录:
- example/solr:
该目录是一个标准的SolrHome,它包含一个默认配置信息的SolrCore目录。
- example/multicore:
该目录包含了在Solr的multicore中设置的多个Core目录。
- example/webapps:
该目录中包括一个solr.war,该war可作为solr的运行实例工程。
SolrHome和SolrCore
[[]{#OLE_LINK24 .anchor}]{#OLE_LINK23
.anchor}SolrHome就是MySQL的数据库主机。
SolrCore就是MySQL的数据库主机下的一个数据库。这里面叫做索引库。MySQL的数据库是相互独立的,SolrCore同样也是相互独立的。
SolrHome是Solr索引库的主目录,一个SolrHome可以包括多个SolrCore([[]{#OLE_LINK118
.anchor}]{#OLE_LINK117
.anchor}Solr实例),每个SolrCore就是一个索引库,提供单独的搜索和索引服务,有自己独立的配置文件和数据文件。
{width=”1.7440474628171478in”
height=”1.7440474628171478in”}
在【资料\solr\solr-4.10.3\example】目录下,【\solr】是一个SolrHome目录结构,如下:
{width=”2.6517672790901137in”
height=”1.7095920822397201in”}
上图中“collection1”是一个SolrCore(Solr实例)目录 ,目录内容如下所示:
{width=”3.234847987751531in”
height=”2.084031058617673in”}
SolrCore创建与配置
Solr中许多功能都是配置就可以使用的,不需要做太多的实现编码,我们要做的只是配置好,然后调用即可。而且SolrCore的创建就是拷贝官方提供的一个实例,然后改一改配置即可。
创建SolrCore
最快的创建即拷贝解压缩包中的例子,拷到Solr工作目录:【C:\mydir\03_workspace\solr】。拷贝【solr-4.10.3\example\solr】文件夹到Solr工作目录,并改名为【solrhome】(改名不是必须的,只是为了便于理解),这是一个SolrHome目录,它里面自带一个collection1就是一个SolrCore。
{width=”1.4015146544181978in”
height=”2.2982895888013997in”}
{width=”3.8774726596675415in”
height=”1.2272725284339459in”}
配置SolrCore
一个SolrCore的配置都是自己独立的。主要的配置文件在SolrCore目录下的conf/solrconfig.xml。
{width=”2.926388888888889in”
height=”2.3958333333333335in”}
这个文件配置SolrCore实例的相关信息。主要是Solr索引库依赖的lib(lib标签)、索引文件存放的路径(datadir标签)、Solr提供的请求访问URL配置(requestHandler标签)。如果使用默认配置可以不用做任何修改。
lib 标签
在solrconfig.xml中默认配置了【contrib】和【dist】两个目录下的jar包,这些jar目录都在解压后的文件夹中,所以这两个文件夹也需要拷贝过来:
{width=”7.268055555555556in”
height=”1.7659722222222223in”}
【solr.install.dir】表示当前的SolrCore的目录位置,即【C:\mydir\03_workspace\solr\
solrhome\collection1】,冒号:后面的【../../..】是针对【solr.install.dir】的相对路径,这个相对路径取决于我们把这两个文件夹放哪。这里还可以直接指定绝对路径,但不推荐使用绝对路径。
比如:我们把【contrib】与【dist】复制到【C:\mydir\03_workspace\solr】目录下:
{width=”3.3155369641294836in”
height=”1.2424234470691164in”}
那么【solr.install.dir:】后的【../../..】应该改为【../..】:
(./ 表示当前目录 ../表示上一级目录)
{width=”5.764583333333333in”
height=”2.709722222222222in”}
datadir标签
配置SolrCore的data目录。data目录用来存放SolrCore的索引文件和tlog日志文件
【solr.data.dir】默认路径是【collection1\data】文件夹,如果没有data,solr会自动创建。
{width=”3.4547933070866144in”
height=”1.9090912073490813in”}
{width=”3.905561023622047in”
height=”1.5303029308836396in”}
如果不想使用默认的目录也可以通过solrconfig.xml更改索引目录 ,例如:
{width=”5.757658573928259in”
height=”0.1904757217847769in”}
(建议不修改,因为各自的索引文件和日志放在各自的SolrCore目录下更好,而且当配置多个SolrCore会报错)
requestHandler标签
requestHandler请求处理器,定义了索引和搜索的URL访问方式。官方提供的配置文件中就已经给我们配置了许多url访问方式,基本不用改。requestHandler也是可以根据自己的要求自定义。
/update:维护索引使用的url,可以完成索引的添加、修改、删除操作。
{width=”6.6875in” height=”2.0208333333333335in”}
/select:查询索引使用的url。
{width=”6.779861111111111in”
height=”2.4166666666666665in”}
设置搜索参数完成搜索,搜索参数也可以设置一些默认值,如下:
<requestHandler name=”/select” class=”solr.SearchHandler”>
<!– 设置默认的参数值,可以在请求地址中修改这些参数–>
<lst name=”defaults”>
<str name=”echoParams”>explicit</str>
<int name=”rows”>10</int><!–显示数量–>
<str name=”wt”>json</str><!–显示格式–>
<str name=”df”>text</str><!–默认搜索字段–>
</lst>
</requestHandler>
Solr运行环境
solr
需要运行在一个Servlet容器中,Solr4.10.3要求jdk使用1.7以上,Solr默认提供Jetty(java写的Servlet容器),
使用jetty启动:使用cmd命令行,进入example文件夹启动。启动命令java -jar
start.jar
{width=”5.061805555555556in”
height=”0.5208333333333334in”}
启动后访问地址:http://127.0.0.1:8983/solr
但是企业中一般使用Tomcat作为服务器,本课程也是一样,
相关环境如下:
Solr:4.10.3
Jdk环境:1.7(solr4.10 不能使用jdk1.7以下)
服务器:Tomcat 7
Solr服务部署
上面拷贝并配置的不是SolrWeb服务,那只是一个个的Solr索引库。
我们现在要部署的才是真正的Solr Web服务。
由于在项目中用到的web服务器大多数是用的Tomcat,所以就进行solr和Tomcat的整合。
安装Tomcat
复制自己的Tomcat7到这里
{width=”2.3333333333333335in”
height=”1.6041666666666667in”}
删除不用的应用(可以不删)
{width=”3.238888888888889in”
height=”2.3854166666666665in”}
修改server.xml配置文件里面的端口号(否则后面eclipse使用Tomcat会冲突)
{width=”3.301388888888889in”
height=”2.1354166666666665in”}
修改以下三个端口号
{width=”6.552083333333333in”
height=”1.4583333333333333in”}
{width=”6.394676290463692in”
height=”0.7944444444444444in”}
{width=”6.59375in” height=”0.7045461504811898in”}
部署solr.war到Tomcat中
- 从solr解压包下的solr-4.10.3\example\webapps目录中拷贝solr.war
复制solr.war
{width=”3.270138888888889in” height=”1.90625in”}
粘贴到自己Tomcat的webapps里
{width=”3.270138888888889in”
height=”1.8333333333333333in”}在Tomcat的webapps里,把war解压到当前路径,并删除solr.war
{width=”4.738888888888889in”
height=”2.8430555555555554in”}效果:
{width=”3.1243055555555554in”
height=”1.2291666666666667in”}{width=”3.8430555555555554in”
height=”2.7909722222222224in”}
添加solr服务的扩展jar包(日志包)
把solr解压包下solr-4.10.3\example\lib\ext目录下的所有jar包拷贝到Tomcat部署的solr的WEB-INF/lib文件夹:
复制扩展jar包
{width=”2.76871719160105in”
height=”2.113636264216973in”}
粘贴到Tomcat的webapps的solr工程的WEB-INF\lib目录
{width=”4.947222222222222in”
height=”2.3333333333333335in”}
配置solr应用的web.xml
需要修改web.xml,让Tomcat使用JNDI的方式告诉solr服务器SolrHome在哪。
{width=”5.197222222222222in”
height=”3.3743055555555554in”}
修改内容:第42行的Solr/home名称必须是固定的,修改第43行,如下图
{width=”5.7625in” height=”2.0972222222222223in”}
启动Tomcat进行访问
访问:http://localhost:8081/solr/,出现以下界面则说明solr安装成功!!!
{width=”6.177777777777778in”
height=”3.6145833333333335in”}
管理界面功能介绍
Dashboard
仪表盘,显示了该Solr实例开始启动运行的时间、版本、系统资源、jvm等信息。
Logging
Solr运行日志信息
Cloud
Cloud即SolrCloud,即Solr云(集群),当使用Solr
Cloud模式运行时会显示此菜单,该部分功能在第二个项目,即电商项目会演示。
Core Admin
Solr
Core的管理界面。在这里可以添加SolrCore实例(有bug,不推荐使用浏览器界面添加SolrCore)。
推荐使用手动添加solrcore:
第一步:复制collection1改名为collection2
第二步:修改core.properties。name=collection2
第三步:重启tomcat
java properties
Solr在JVM
运行环境中的属性信息,包括类路径、文件编码、jvm内存设置等信息。
Tread Dump
显示Solr Server中当前活跃线程信息,同时也可以跟踪线程运行栈信息。
Core selector(重点)
选择一个SolrCore进行详细操作,如下:
{width=”1.5506944444444444in”
height=”2.9895833333333335in”}
Analysis
通过此界面可以测试索引分析器和搜索分析器的具体分析执行结果
{width=”4.803472222222222in”
height=”3.3305555555555557in”}
Solr中自带了许多已经定义好的Field,而且Solr的Field中都带有分析器可以对设置的内容进行分析处理,这一点比Lucene要高级,因为Lucene的Field对象不能指定分析器。
在这个界面中可以选择不同的Field域对你输入的内容进行分析测试,用于查看某个Field的分词情况,从而我们可以在开发中选用正确的Field。
1. 我们先选用【_root_】域:
结果:没有分词
{width=”4.73117125984252in”
height=”3.961538713910761in”}
我们发现【_root_】的实际类型是【StrField】,String类型的Field是不分词的,这个和Lucene的类似。
{width=”4.878205380577428in”
height=”4.043152887139108in”}
2. 我们选用一个可能有分词功能的Field【content】
结果:分词了,但是分的不好
{width=”4.8995964566929135in”
height=”4.07051290463692in”}
【content】的Field类型是TextField,它使用的分析器是solr标准分析器,只能对英文分词,所以对汉字分的不好。
{width=”4.9287598425196855in”
height=”4.096153762029746in”}
结论:
虽然solr提供了许多可用的Field,但是这些Field对汉语支持不好,所以在实际开发是我们需要自定义支持汉语的Field才可以进行我们的系统开发。Solr对Field域定义非常灵活、强大,这个明天具体学习。
dataimport
可以定义数据导入处理器,从关系数据库将数据创建索引并导入到Solr索引库中。默认没有配置,需要手工配置。
Document
通过此菜单可以创建索引、更新索引、删除索引等操作。
通过/update表示更新索引,solr默认根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新。
{width=”3.843157261592301in”
height=”3.346153762029746in”}
overwrite=”true” :
solr在做索引的时候,如果文档已经存在,就用xml中的文档进行替换commitWithin=”1000” : solr
在做索引的时候,每隔1000(1秒)毫秒,做一次文档提交。为了方便测试也可以在Document中立即提交,</doc>后添加“<commit/>”
1. 添加索引
{width=”5.8974354768153985in”
height=”3.446162510936133in”}
id域=c001的Document不存在时,创建Document对象,有两个域:id域和title域,查看结果:
{width=”5.870833333333334in”
height=”3.4294881889763777in”}
Solr会针对你添加的Document对象自动创建索引。
2. 更新索引
Solr只能更新已经存在的索引
{width=”5.877952755905512in”
height=”3.4606299212598426in”}
查询结果:
{width=”5.877952755905512in”
height=”3.4291338582677167in”}
3. 如果使用了solr不存在的Field,是不允许的:
{width=”6.08966426071741in”
height=”3.0192300962379703in”}
4. 如果不带id域也是不允许的:
{width=”6.089744094488189in”
height=”2.742305336832896in”}
5. 根据id删除索引:
{width=”5.294872047244095in”
height=”3.324346019247594in”}
查询删除结果:
{width=”4.134490376202975in”
height=”3.3910247156605426in”}
6. 根据条件删除:
先添加两个Document:
{width=”5.4695461504811895in”
height=”3.4935903324584427in”}
查询结果:
{width=”4.488578302712161in”
height=”5.057692475940508in”}
执行条件删除:
{width=”4.0641021434820646in”
height=”3.3655227471566054in”}
再查询:只有c002的数据了
{width=”4.063888888888889in”
height=”2.984430227471566in”}
Query
通过/select执行搜索索引,必须指定“q”查询条件方可搜索。这个我们明天还要好好学习,今天就简单了解。
{width=”1.6427384076990377in”
height=”3.0064096675415573in”}
Solrj的使用(重点)
什么是solrj
solrj是访问Solr服务的java客户端,提供索引和搜索的请求方法,如下图:
Solrj和图形界面操作的区别就类似于数据库中使用jdbc和mysql客户端的区别一样。
需求
使用solrj调用solr服务实现对索引库的增删改查操作。
环境准备
Solr:4.10.3
Jdk环境:1.7
IDE环境:Eclipse Mars2
工程搭建
创建java工程
{width=”2.6354166666666665in” height=”1.3125in”}
添加jar
Solrj的包,\solr-4.10.3\dist\目录下
{width=”2.8141021434820646in”
height=”3.8458606736657917in”}solrj依赖包,\solr-4.10.3\dist\solrj-lib
{width=”2.8585126859142607in”
height=”3.038461286089239in”}
Solr服务的日志依赖包,\solr\example\lib\ext
{width=”2.916965223097113in”
height=”2.185896762904637in”}
添加日志配置文件
创建【config】:
{width=”1.8583333333333334in” height=”1.5in”}
加入log4j.properties。
代码实现
添加&修改索引
步骤
创建(new)HttpSolrServer对象,通过它和Solr服务器建立连接,需要提供URL
创建SolrInputDocument对象,然后通过它的addField(域名,域值)来添加域。
通过HttpSolrServer对象的add()方法将SolrInputDocument添加到索引库。
HttpSolrServer的commit(提交)。
代码
说明:根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新。
@Test
public void testCreateAndUpdateIndex() throws Exception {
// 1. 创建HttpSolrServer对象
// 设置solr服务接口,浏览器客户端地址http://127.0.0.1:8081/solr/
// 如果想上面不指定某个Solr实例默认使用第一个,但最好还是指定一个
String baseURL = “http://127.0.0.1:8081/solr/collection1“;
HttpSolrServer httpSolrServer = new HttpSolrServer(baseURL);
// 2. 创建SolrInputDocument对象
SolrInputDocument document = new SolrInputDocument();
document.addField(“id”, “c1001”);
document.addField(“content “, “Hello world!”);
// 3. 把SolrInputDocument对象添加到索引库中
httpSolrServer.add(document);
// 4. 提交
httpSolrServer.commit();
}
查询索引
抽取HttpSolrServer 的创建代码
private HttpSolrServer httpSolrServer;
// 提取HttpSolrServer创建
@Before
public void init() {
// 1. 创建HttpSolrServer对象
// 设置solr服务接口,浏览器客户端地址http://127.0.0.1:8081/solr/#/
String baseURL = “http://127.0.0.1:8081/solr/“;
this.httpSolrServer = new HttpSolrServer(baseURL);
}
根据条件的简单查询(查询全部)
/**
* 简单搜索
*
* @throws Exception
*/
@Test
public void testSearchIndex1() throws Exception {
// 创建搜索对象
SolrQuery query = new SolrQuery();
// 设置搜索条件
query.setQuery(“*:*”);
// 发起搜索请求
QueryResponse response = this.httpSolrServer.query(query);
// 处理搜索结果
SolrDocumentList results = response.getResults();
System.out.println(“搜索到的结果总数:” + results.getNumFound());
// 遍历搜索结果
for (SolrDocument solrDocument : results) {
System.out.println(“—————————————————-“);
System.out.println(“id:” + solrDocument.get(“id”));
System.out.println(“content” + solrDocument.get(“content”));
}
}
删除索引
删除索引逻辑,两种:
根据id删除
根据条件删除,根据条件删除,可以使用*:*作为条件,就是删除所有数据(慎用)
@Test
public void testDeleteIndex() throws Exception {
// 根据id删除索引数据
// this.httpSolrServer.deleteById(“c1001”);
// 根据条件删除(如果是*:*就表示全部删除,慎用)
//this.httpSolrServer.deleteByQuery(“*:*”);
this.httpSolrServer.deleteByQuery(“id:c1001”);
// 提交
this.httpSolrServer.commit();
}