我们在使用Lucene管理Document时,难免有些复杂,并且在做增量索引时会比较繁琐,因此需要用到Compass。 Compass对Lucene的使用就如同Hibernate和对Jdbc的使用,Compass的工作流程如下:
本文使用Compass2.2版本,所需jar如下:
使用compass的步骤和使用hibernate的步骤基本一致:
使用hibernate时步骤一般为:
1.建立实体类,也就是Entity,如Student,Teacher,这些实体类。
2.配置实体类到数据库的映射。配置方法一般有两种:
(1)使用xml。如(Student.hbm.xml)
(2)使用annotation 注解 @Entity ,@Id等
Hiberante中要求每个实体都要有个标识(Id),compass中也这么要求。
3.使用hibernate的API操作实体
Configuration cfg = new Configuration().configure(); //默认加载hibernate.cfg.xml
SessionFactory sessionFactory =cfg.buildSessionFactory(); //全局唯一
Session session = sessionFactory.openSession();
session.beginTransaction(); //开启事务
session.save(xxx);//保存
session.delete(xxx);//删除
session.get(xxx.class,id)//获得对象
........//其他操作
session.getTransaction().commit(); //提交事务
session.close(); //关闭session
而使用compass 和 hibernate的步骤几乎是一样的:
1.建立实体类,如Student,Teacher等实体类。
2.配置实体类到索引库的映射。
以Student类为例:
- @Entity //在hibernate中,使用@Entity将Student类映射到数据库的student表
- @Searchable(alias="product") //在compass中,使用@Searchable将Student映射到索引库中的 product 文档(Document),alias用来指定该实体类对应的document的名称,不设置alias则默认使用小写的类名。如果searchable的root属性设为false,则该类不会被创建为一个单独的document。一般该类作为另外一个类的组件时,才设置该属性为false。这事要想通过该类的属性搜索该类就会报错。
- public class Student{
- //hibernate 和 compass中都规定,实体必须有一个标识字段
- private int id;
- private String name;
- private String fond;
- private Teacher teacher;
- @Id //hiberante中,使用@Id来将一个字段映射为实体的标识ID
- @SearchableId //compass中,则使用@SearchableId将一个字段映射为实体的标识ID
- public int getId(){
- return id;
- }
- /*
- * 在hibernate中,对应属性如果不设置的话,则默认的映射为对应名称的字段;
- 在compass中实体类的属性则需要使用@SearchableProperty来设置,设置用来检索的属性,以及属性值是否被索引、是否被存储,以及相关度boost
- @SearchableProperty中的name属性用来指定filed的名称;
- index指定是否建立索引,
- Index.ANALYZE表示建立索引并进行分词(为默认值),即建立索引时会对该filed进行分词,并在检索的时候可以通过该filed进行搜索;
- Index.NOT_ANALYZED 表示会对该字段建立索引,但不会进行分词;
- Index.NO 表示不会对该字段建立索引,当然也不会进行分词了;
- store表示该字段是否要被存储到索引库中,默认值为store=Store.YES;
- 指定Index.NOT_ANALYZED并且指定Store.YES,则是为了搜索到实体时,将该filed值加载出来;
- boost表示相关度,相关度是一个float型的值,值越大,通过字段匹配的结果排序越靠前;
- */
- @SearchableProperty(name="name",index=Index.ANALYZED,store=Store.YES,boost=3f)
- public String getName(){
- return name;
- }
- public String getFond(){
- return fond;
- }
- /*
- @SearchableComponent复合类型,该类型的对象Teacher被看做是当前类Student的一部分,一个组件,创建document时,复合类型的属性会和student的属性放在同一个document中。
- 当进行查询时,会先查询主类的数据元字段,也会查询组件类如Teacher类中的字段,因为在创建document时,teacher的数据元字段作为student的一部分放到了document中。
- */
- @SearchableComponent
- public Teacher getTeacher(){
- return teacher;
- }
- public void setTeacher(Teacher teacher){
- this.teacher = teacher;
- }
- }
- //把root属性设为false,表示该类不是一个独立的实体,它只是其他类(Student)的一部分
- @Searchable(root=false)
- public class Teacher {
- //此时该类的id就不能做为索引库中的标识了,因为该类被当做是Student的的一部分,而Student中已经有标识了。
- @SearchableProperty(name="tid",index=Index.NO,store=Store.YES)
- private int id;
- /*应为在创建索引时,该name对应的索引字段为name,而在Student中name对应的索引字段也为name,会造成冲突,
- * 所以需要将复合类型中与主类属性名字相同的该为其他的的名称,以免冲突。
- */
- @SearchableProperty(name="tname",index=Index.ANALYZED,store=Store.YES)
- private String name;
- @SearchableProperty(name="tage",index=Index.NO,store=Store.YES)
- private int age;
- @SearchableProperty(name="tfond",index=Index.ANALYZED,store=Store.YES)
- private String fond;
- //也就是说,查询到Teacher类的某个实体时,可以根据该实体加载出来其对应的student对象。
- @SearchableReference
- private Student student;
- //…………………………省略get/set方法
- }
3.使用Compass中的API对实体进行操作:
Compasscompass = new CompassConfiguration().configure().bulidCompass();
/*compass 相当于 hibernate的sessionfactory
上面的configure()方法会自动去classpath找compass.cfg.xml配置文件,这个方法和hibernate中的configure()方法很相似,hibernate中的该方法用来加载hibernate.cfg.xml.
如果要制定其他名称的配置文件,可以使用configure(xxx).
CompassSession相当于hibernate中的Session
*/
CompassSession compassSession = compass.openSession();
compassSession.beginTransaction(); //CompassTransaction相当于 Hiberante中的Transaction,即compss中也是有事务支持的,和数据库很相似(也有事务回滚tx.rollback())
compassSession.create(entity)/.create(entity);//保存到索引文件
compassSession.delete(entity);//将实体从索引库中删除
compassSession.commit();
compassSession.close();
下面是测试时使用compass进行保存,查询等操作的关键代码:
- @Searchable //告诉compass,将该类映射到Lucene的Document
- public class Product {
- private Integer id;
- private String name;
- private String content;
- private Float price;
- private String note;
- private Integer position;
- private Integer typeid;
- public Product(){}
- public Product(Integer id) {
- this.id = id;
- }
- public Product(Integer id, String name, String content, Float price,String note,Integer typeid,Integer position) {
- this.id = id;
- this.name = name;
- this.content = content;
- this.price = price;
- this.note = note;
- this.typeid = typeid;
- this.position = position;
- }
- @SearchableId //compass要求每个搜索实体类都要具有一个标识属性,这点和Hibernate相似
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- @SearchableProperty(index=Index.NOT_ANALYZED, store=Store.YES)//store的默认值为Store.YES
- public Integer getTypeid() {
- return typeid;
- }
- public void setTypeid(Integer typeid) {
- this.typeid = typeid;
- }
- @SearchableProperty(boost=2)//boost的默认值为1,用于设置属性在索引中的重要性
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- @SearchableProperty
- public String getContent() {
- return content;
- }
- public void setContent(String content) {
- this.content = content;
- }
- //价格不需要进行搜索,但需要存储,如果没有存储,就需要从数据库中获取价格了
- @SearchableProperty(index=Index.NO)//store的默认值为Store.YES
- public Float getPrice() {
- return price;
- }
- public void setPrice(Float price) {
- this.price = price;
- }
- @SearchableProperty(store=Store.YES)
- public String getNote() {
- return note;
- }
- public void setNote(String note) {
- this.note = note;
- }
- @SearchableProperty(index=Index.NOT_ANALYZED, store=Store.YES)//store的默认值为Store.YES
- public Integer getPosition() {
- return position;
- }
- public void setPosition(Integer position) {
- this.position = position;
- }
- }
- public class ProductSearchBean {
- private Compass compass = null;//-->SessionFactory
- public ProductSearchBean(){
- try {
- //编程式配置
- compass = new CompassAnnotationsConfiguration()
- .setSetting(CompassEnvironment.CONNECTION, "file://indexfile")
- //.setSetting(CompassEnvironment.CONNECTION, "ram://index")//在内存中建立索引
- .setSetting("compass.engine.analyzer.default.type", "net.paoding.analysis.analyzer.PaodingAnalyzer")
- .setSetting("compass.engine.highlighter.default.formatter.simple.pre","<font color='red'>")
- .setSetting("compass.engine.highlighter.default.formatter.simple.post","</font>")
- .addScan("lxq.compass").buildCompass();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public void buildIndex(){
- CompassSession session = null;
- CompassTransaction tx = null;
- try {
- session = compass.openSession();
- tx = session.beginTransaction();
- Product p1 = new Product(12,"c瑜珈球","非常好的瑜珈球",12f, "www", 2, 12);
- session.create(p1);
- Product p2 = new Product(35,"b瑜珈球","天花板瑜珈球,good",42f, "mmm",2,9);
- session.create(p2);
- Product p3 = new Product(8,"a蓝球瑜珈球","蓝球小子",125f, "ppp",5,8);
- session.create(p3);
- tx.commit();
- } catch (CompassException e) {
- e.printStackTrace();
- tx.rollback();
- }finally{
- if(session!=null && !session.isClosed()) session.close();
- }
- }
- public void deleteIndex(Product product) {
- CompassSession session = null;
- CompassTransaction tx = null;
- try {
- session = compass.openSession();
- tx = session.beginTransaction();
- session.delete(product);
- tx.commit();
- } catch (CompassException e) {
- e.printStackTrace();
- tx.rollback();
- }finally{
- if(session!=null && !session.isClosed()) session.close();
- }
- }
- public void updateIndex(Product product) {
- CompassSession session = null;
- CompassTransaction tx = null;
- try {
- session = compass.openSession();
- tx = session.beginTransaction();
- session.delete(product);
- session.save(product);
- tx.commit();
- } catch (CompassException e) {
- e.printStackTrace();
- tx.rollback();
- }finally{
- if(session!=null && !session.isClosed()) session.close();
- }
- }
- public void destroy(){
- compass.close();
- }
- public QueryResult<Product> search(String keyword, int firstIndex, int maxResult) {
- QueryResult<Product> qr = new QueryResult<Product>();
- CompassSession session = null;
- CompassTransaction tx = null;
- try {
- session = compass.openSession();
- tx = session.beginTransaction();
- //对所有索引Field进行搜索,你也可以指定对某个Field搜索,如:"name:jack",如果想指定多个字段可以用空格和"+"隔开如"name:jack +content:xxx"
- CompassHits hits = session.find(keyword);
- List<Product> products = new ArrayList<Product>();
- int length = firstIndex+ maxResult;
- if(length>hits.length()) length = hits.length();
- for(int i=firstIndex; i<length; i++){
- Product product = (Product)hits.data(i);
- product.setContent(hits.highlighter(i).fragment("content"));
- }
- qr.setResultlist(products);
- qr.setTotalrecord(hits.length());
- hits.close();
- } catch (CompassException e) {
- e.printStackTrace();
- tx.rollback();
- }finally{
- if(session!=null && !session.isClosed()) session.close();
- }
- return qr;
- }
- public QueryResult<Product> search(String keyword, Integer typeid, int firstIndex, int maxResult) {
- QueryResult<Product> qr = new QueryResult<Product>();
- CompassSession session = null;
- CompassTransaction tx = null;
- try {
- session = compass.openSession();
- tx = session.beginTransaction();
- //查询指定类别的匹配记录,并按position降序排序
- CompassQueryBuilder queryBuilder = session.queryBuilder();
- CompassHits hits = queryBuilder.bool()
- .addMust(queryBuilder.spanEq("typeid", typeid))
- .addMust(queryBuilder.queryString(keyword).toQuery())
- .toQuery().addSort("position", SortPropertyType.FLOAT, SortDirection.REVERSE)
- .hits();//sql: typeid=1 and (xxxx like ?) order by positoin desc
- List<Product> products = new ArrayList<Product>();
- int length = firstIndex+ maxResult;
- if(length>hits.length()) length = hits.length();
- for(int i=firstIndex; i<length; i++){
- //在hits调用data(i)方法时,才会真正的返回指定索引号(i)的结果
- Product product = (Product)hits.data(i);
- if(hits.highlighter(i).fragment("content ")!=null)
- product.setName(hits.highlighter(i).fragment("content")); // 如果没有高亮内容时返回null
- products.add(product);
- }
- qr.setResultlist(products);
- qr.setTotalrecord(hits.length());
- hits.close();
- } catch (CompassException e) {
- e.printStackTrace();
- tx.rollback();
- }finally{
- if(session!=null && !session.isClosed()) session.close();
- }
- return qr;
- }
- }
补充说明:
一、 这两段代码具有一样的功能,下面的代码另外还多了个很重要的功能,自动选择正文中最匹配关键字的内容中的一部分输出。因为很多时候一篇文章几千字,我们只想显示有关键字的那部分的摘要,这时候这个功能就很方便。
- Stringcontent = hits.highlighter(i) .fragment("content");
- String content =hits.highlighter(i).setTextTokenizer(CompassHighlighter.TextTokenizer.AUTO).fragment("content");
二、另外在getContent()方法上的@SearchableProperty中还可以加入例如converter ="htmlPropertyConverter",主要是用来将文章中的HTML标签进行过滤获取纯文本,在建立到索引中。在配置文件中添加
- <bean id="compass" class="org.compass.spring.LocalCompassBean">
- <property name="convertersByName">
- <map>
- <entry key="htmlPropertyConverter">
- <bean class="com.compass.converter.HtmlPropertyConverter"/>
- </entry>
- </map>
- </property>
- </bean>
然后就可以自己写实现类了,我也是网上看到的,给个链接http://blog.csdn.net/u010469430/article/details/12384347。
三、多种查询方式
- // 查询方式一:使用查询字符串(可以有查询语法)
- // CompassHits hits = session.find(queryString);
- // -----------------------------------------------------------------
- // 查询方式二:构建CompassQuery对象
- // 1,查询所有
- CompassQuery query1 = session.queryBuilder().matchAll();
- // 2,关键词查询
- CompassQuery query2 = session.queryBuilder().term("title", "lucene");
- // 3,范围查询
- CompassQuery query3 = session.queryBuilder().between("id", 5, 15, true);
- // 4,通配符查询
- CompassQuery query4 = session.queryBuilder().wildcard("title", "lu*n?");
- // 5,短语查询
- // 设定精确间隔的匹配方式,写法1
- CompassMultiPhraseQueryBuilder qb = session.queryBuilder().multiPhrase("title");
- qb.add("lucene", 0); // 第一个词位置从0开始
- qb.add("工作", 2); // 第二个词位置从2开始
- CompassQuery query5 = qb.toQuery();
- // 设定精确间隔的匹配方式,写法2
- CompassQuery query6 = session.queryBuilder().multiPhrase("title")//
- .add("lucene", 0) // 第一个词位置从0开始
- .add("工作", 2) // 第二个词位置从2开始
- .toQuery();
- // 设定最多间隔的匹配方式
- CompassQuery query7 = session.queryBuilder().multiPhrase("title")//
- .add("lucene") //
- .add("工作") //
- .setSlop(5) // 指定的词之间的最长间隔不超过5个词
- .toQuery();
- // 6,布尔查询
- CompassQuery query = session.queryBuilder().bool()//
- // .addMust(query)
- // .addMustNot(query)
- // .addShould(query)
- .addMust(query1)//
- .addMustNot(query3)//
- .toQuery();
- CompassHits hits = query.hits();
四、当然上面可以不用编程式配置,而通过Compass compass= new CompassConfiguration().configure().bulidCompass();使用类似如下的配置文件。
- <?xml version="1.0" encoding="utf-8"?>
- <compass-core-config xmlns="http://www.compass-project.org/schema/core-config"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.compass-project.org/schema/core-config
- http://www.compass-project.org/schema/compass-core-config-2.2.xsd">
- <compass name="default">
- <!-- 配置索引库的存储目录 -->
- <connection>
- <file path="e:/liuyan_index" />
- </connection>
- <cache>
- <firstLevel type="org.compass.core.cache.first.NullFirstLevelCache" />
- </cache>
- <mappings>
- <class name="com.sharp.liuyan.so.ArticleSo" />
- </mappings>
- <settings>
- <!-- setting元素就像于property元素 -->
- <setting name="compass.engine.analyzer.default.type"
- value="net.paoding.analysis.analyzer.PaodingAnalyzer"/>
- <!-- 配置高亮 -->
- <setting name="compass.engine.highlighter.default.formatter.simple.pre"
- value="<font color='red'><b>"/>
- <setting name="compass.engine.highlighter.default.formatter.simple.post"
- value="</b></font>"/>
- </settings>
- </compass>
- </compass-core-config>
Compass 与Spring的集成
先思考一下compass不与spring集成时的情况。
我们将一个entity保存到数据库的同时,也要将该实体相关的数据信息保存到索引库中。所以代码常为一下情况:
- Public void saveStudent(Student stu){
- hibernateTeplate.save(stu);//保存到数据库中
- compassSession.save(stu);//保存到索引库中
- }
我们有一点需要保证的是,必须在数据库保存成功后才能将实体对象保存到索引库中,然而在没有任何外界条件限制的情况先,数据库的事务和compass的事务是两个不同体系的事务,他们不可能自动的将事务并为一起。这样就无法保证整个操作的流程控制在同一个事务中,所以我们就要想办法来解决这个问题。
使用compass与spring集成,能很好的解决这个问题,spring会为hibernate与compass提供同一个事务管理,将整个操作流程(如上的saveStudent()方法)纳入同一个事务支持当中。
下面看一下配置中JPA(hibernate)、compass、spring的集成:
其中JPA(hibernate)与spring集成与前面完全一致,就是在后面加了一个compass的配置:
- <aop:aspectj-autoproxy/>
- <!-- 配置哪些包下的类需要自动扫描 -->
- <context:component-scan base-package="cn.itcast"/>
- <!-- 这里的itcast要与persistence.xml中的 <persistence-unit name="itcast " transaction-type="RESOURCE_LOCAL">中的name值要一致,这样才能找到相关的数据库连接
- -->
- <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
- <property name="persistenceUnitName" value="itcast"/>
- </bean>
- <!-- 配置事物管理器 -->
- <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
- <property name="entityManagerFactory" ref="entityManagerFactory"/>
- </bean>
- <!-- 配置使用注解来管理事物 -->
- <tx:annotation-driven transaction-manager="transactionManager"/>
- <!—compass对象相当于 hibernate中的sessionFactory,在整个应用中时单例,这里使用org.compass.spring.LocalCompassBean,表示由spring来生成并管理compass对象,生成该对象需要一些属性设置。
- -->
- <bean id="compass" class="org.compass.spring.LocalCompassBean">
- <!-- 如果compass有单独的配置文件,可以从这里引入
- <property name="configLocation" value="classpath:compass.cfg.xml"/>
- -->
- <!-- 数据索引存储位置 -->
- <property name="connection" value="/lucene/indexes"/>
- <!—配置compass的实体类映射-->
- <property name="classMappings">
- <list>
- <value>cn.itcast.bean.product.ProductInfo</value>
- <value>cn.itcast.bean.product.Brand</value>
- <value>cn.itcast.bean.product.ProductStyle</value>
- <value>cn.itcast.bean.product.ProductType</value>
- </list>
- </property>
- <!---compass相关的一些属性设置
- <property name="compassSettings"><props>
- -->
- <!—- 指定compass的默认分词器,为:paoding分词,paoding分词器是基于字典的分词器,所以最好字典,使用paoding分词器时,将paoding分词器压缩目录下的字典目录(dic)放到项目的src目录下,并将paoding分词的paoding-dic-home.properties文件拷贝到src下,并在该配置文件中指定字典dic的路径为:classpath:dic。如果需要自定义一些字词,只需在dic目录下的字典文件中加上该词即可。-->
- <prop key="compass.engine.analyzer.default.type">
- net.paoding.analysis.analyzer.PaodingAnalyzer
- </prop>
- <!—- compass索引库的位置 -->
- <!-- file://e:/index 中以file://开头表示在硬盘上指定位置创建索引库-->
- <!— ram:// index 中以ram://开头表示在内存中创建索引库-->
- <prop key="compass.engine.connection">
- file://e:/index
- </prop>
- <!-- 指定摘要文本的长度 -->
- <prop key="compass.engine.highlighter.default.fragmenter.simple.size"> 200
- </prop>
- <!—- 对关键字进行高亮(highlighter)设置 -->
- <prop key="compass.engine.highlighter.default.formatter.simple.pre"><![CDATA[<font color='red'>]]>
- </prop>
- <prop key="compass.engine.highlighter.default.formatter.simple.post"><![CDATA[</font>]]>
- </prop>
- <!-- transaction.factory用于指定compass的事务工厂,这里指定为spring提供的事务工厂类,该工厂类会从spring中获得当前的事务,compass就可以使用当前事务了 -->
- <prop key="compass.transaction.factory">
- org.compass.spring.transaction.SpringSyncTransactionFactory
- </prop>
- </props>
- </property>
- <!—- spring中的事务是由事务管理器来管理的,上面的指定的compass的transaction.factory要从spring获得当前事务,就必须去访问spring的事务管理器,这里就指定了compass的事务工厂要访问的spring的事务管理器,该事务管理器在前面已经配置好了,和hibernate那个一致,这里只需要引用就可以了-->
- <property name="transactionManager" ref="transactionManager" />
- </bean>
按照上面的配置你仍然需要在代码中写相应的创建、删除索引的语句,在配置文件中添加下面的代码就可以一切都自动化了。
CompassGps - Gps的核心模块,管理GpsDevice,有两种实现:SingleCompassGps和DualCompassGps。
CompassGpsDevice - 处理各种数据源到索引的操作:JDBC, Hibernate,iBatis等。不能独立使用而必须融合到CompassGps中。
- <!-- SingleCompassGps 类是compass的GPS机制的实现类,spring在实例化该类的对象后,会立即执行init-method属性指定的SingleCompassGps中的start()方法,start方法用于启动 对 hibernate或JPA等持久化工具对实体类的增删改查的操作 的监听捕获,因为hibernate或JPA等的持久化操作都会触发一些生命周期事件,而GPS可以对这些生命周期事件进行监听捕获,从而可以使用compass同步的将实体类信息更新到索引库中去-->
- <bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps" init-method="start" destroy-method="stop">
- <!—GPS在监听到hibernate、JPA等对实体类的操作时,要使用compass对象将实体信息更新到索引库中,这里就是指出GPS要使用的compass对象,该compass对象在前面已经配置,这里只需要引用即可 -->
- <property name="compass" ref="compass" />
- <property name="gpsDevices">
- <list>
- <bean class="org.compass.spring.device.SpringSyncTransactionGpsDeviceWrapper">
- <property name="gpsDevice" >
- <!—GPS要监听与实体类对象相关的操作的生命周期,就必须知道是什么产品的工具在对实体类进行操作,这些工具可能是hibernate,JPA等,本项目使用的是JPA,所以要配置JPA相关的驱动,用来监听JPA操作实体时生命周期所发生的变化-->
- <bean class="org.compass.gps.device.jpa.JpaGpsDevice">
- <!—- 这是设置的名字,随便取 -->
- <property name="name" value="jpaDevice" />
- <!—GPS要监听实体被操作的生命周期的变化,就需要去访问管理实体的工厂对象,这里指定entityManagerFactory 为前面配置的JPA提供的实体管理工厂-->
- <property name="entityManagerFactory" ref="entityManagerFactory" />
- <!— 指定是否监听与实体相关的声明周期事件 -->
- <property name="injectEntityLifecycleListener" value="true"/>
- </bean>
- </property>
- </bean>
- </list>
- </property>
- </bean>