欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

再谈compass:集成站内搜索 博客分类: Compass Lucene SpringquartzJPABeanlucene 

程序员文章站 2024-03-08 13:41:04
...
前段时间已经写了一篇关于compass的文章,相信大家对compass也已经有了一定的了解

由于最近做的项目中涉及到了站内搜索,而且是基于JPA注解形式的,在网上找了好久,关于JPA集成compass的例子很少,有些也是基于 xml的,基于注解形式的甚是少,没有办法只有去compass的官网下载英文文档自己研究一下,花费了一下午时间调试出来,集成到项目中!

在这里给大家分享下,希望大家可以少走些弯路!

1.去官方网站下载compass的jar包,我用的的2.1版本

http://www.compass-project.org/

ProductInfo.java

@Entity
@Searchable
public class ProductInfo implements Serializable{
  private static final long serialVersionUID = -8860864584425256200L;
  private Integer id;
  /** 货号 **/
  private String code;
  /** 产品名称 **/
  private String name;
  /** 产品类型 **/
  private ProductType type;
  /** 产品样式 **/
  private Set<ProductStyle> styles = new HashSet<ProductStyle>();

  public ProductInfo() {}

  @OneToMany(cascade={CascadeType.REMOVE,CascadeType.PERSIST}, mappedBy="product",fetch=FetchType.EAGER)
  @OrderBy("visible desc, id asc")
  @SearchableReference
  public Set<ProductStyle> getStyles() {
  return styles;
  }
  public void setStyles(Set<ProductStyle> styles) {
  this.styles = styles;
  }

  @Id @GeneratedValue
  @SearchableId 
  public Integer getId() {
  return id;
  }
  public void setId(Integer id) {
  this.id = id;
  }
  @Column(length=30)
  @SearchableProperty(index = Index.TOKENIZED, store = Store.YES)
  public String getCode() {
  return code;
  }
  public void setCode(String code) {
  this.code = code;
  }
  @Column(length=50,nullable=false)
  @SearchableProperty(index = Index.TOKENIZED, store = Store.YES)
  public String getName() {
  return name;
  }
  public void setName(String name) {
  this.name = name;
  }

  @ManyToOne(cascade=CascadeType.REFRESH,optional=false)
  @JoinColumn(name="typeid")
  @SearchableReference
  public ProductType getType() {
  return type;
  }
  public void setType(ProductType type) {
  this.type = type;
  }

  @Override
  public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  return result;
  }
  @Override
  public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  final ProductInfo other = (ProductInfo) obj;
  if (id == null) {
   if (other.id != null)
   return false;
  } else if (!id.equals(other.id))
   return false;
  return true;
  }
}



ProductType.java
@Entity
@Searchable
public class ProductType implements Serializable{
  private static final long serialVersionUID = 8106351120886053881L;
  /** 类别id **/
  private Integer typeid;
  /** 类别名称 **/
  private String name;
  /** 子类别 **/
  private Set<ProductType> childtypes = new HashSet<ProductType>();
  /** 所属父类 **/
  private ProductType parent;

  private Set<ProductInfo> products = new HashSet<ProductInfo>();

  @OneToMany(mappedBy="type", cascade=CascadeType.REMOVE)
  @SearchableReference
  public Set<ProductInfo> getProducts() {
  return products;
  }

  public void setProducts(Set<ProductInfo> products) {
  this.products = products;
  }

  public ProductType() {}

  @ManyToOne(cascade=CascadeType.REFRESH)
  @JoinColumn(name="parentid")
  public ProductType getParent() {
  return parent;
  }

  public void setParent(ProductType parent) {
  this.parent = parent;
  }
  @OneToMany(cascade={CascadeType.REFRESH,CascadeType.REMOVE},mappedBy="parent")
  public Set<ProductType> getChildtypes() {
  return childtypes;
  }

  public void setChildtypes(Set<ProductType> childtypes) {
  this.childtypes = childtypes;
  }

  @Column(length=36,nullable=false)
  public String getName() {
  return name;
  }

  public void setName(String name) {
  this.name = name;
  }

  @Id @GeneratedValue(strategy=GenerationType.AUTO)
  @SearchableId 
  public Integer getTypeid() {
  return typeid;
  }

  public void setTypeid(Integer typeid) {
  this.typeid = typeid;
  }

  @Override
  public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((typeid == null) ? 0 : typeid.hashCode());
  return result;
  }

  @Override
  public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  final ProductType other = (ProductType) obj;
  if (typeid == null) {
   if (other.typeid != null)
   return false;
  } else if (!typeid.equals(other.typeid))
   return false;
  return true;
  }
}


ProductStyle.java
@Entity
@Searchable
public class ProductStyle implements Serializable{
  private static final long serialVersionUID = -4926119953511144279L;
  private Integer id;
  /** 样式的名称 **/
  private String name;
  /** 图片 **/
  private String imagename;
  private String image140FullPath;
  /** 是否可见 **/
  private Boolean visible = true;
  private ProductInfo product;

  public ProductStyle() {}

  public ProductStyle(Integer id) {
  this.id = id;
  }

  public ProductStyle(String name, String imagename) {
  this.name = name;
  this.imagename = imagename;
  }
  @ManyToOne(cascade=CascadeType.REFRESH,optional=false)
  @JoinColumn(name="productid")
  @SearchableReference
  public ProductInfo getProduct() {
  return product;
  }
  public void setProduct(ProductInfo product) {
  this.product = product;
  }
  @Id @GeneratedValue
  @SearchableId 
  public Integer getId() {
  return id;
  }
  public void setId(Integer id) {
  this.id = id;
  }
  @Column(length=30,nullable=false)
  public String getName() {
  return name;
  }
  public void setName(String name) {
  this.name = name;
  }
  @Column(length=40,nullable=false)
  @SearchableProperty(index = Index.UN_TOKENIZED, store = Store.YES)
  public String getImagename() {
  return imagename;
  }
  public void setImagename(String imagename) {
  this.imagename = imagename;
  }
  @Column(nullable=false)
  public Boolean getVisible() {
  return visible;
  }
  public void setVisible(Boolean visible) {
  this.visible = visible;
  }
  @Transient 
  public String getImageFullPath(){
  return "/images/product/"+ this.getProduct().getType().getTypeid()+ "/"+
  this.getProduct().getId()+ "/prototype/"+ this.imagename;
  }

  @Transient 
  public String getImage140FullPath(){
  image140FullPath = "/images/product/"+ this.getProduct().getType().getTypeid()+ "/"+
  this.getProduct().getId()+ "/140x/"+ this.imagename;
  return image140FullPath;
  }

  @Override
  public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  return result;
  }
  @Override
  public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  final ProductStyle other = (ProductStyle) obj;
  if (id == null) {
   if (other.id != null)
   return false;
  } else if (!id.equals(other.id))
   return false;
  return true;
  }
}


这里要特别注意有集合类型要搜索或显示的时候,两边定义的@SearchableReference或 @SearchableComponent必须一致

2.再spring的配置文件中加入以下代码

<bean id="annotationConfiguration" class="org.compass.annotations.config.CompassAnnotationsConfiguration">
</bean>
  <!-- compass Bean -->
<bean id="compass" class="org.compass.spring.LocalCompassBean">
  <property name="compassConfiguration"
   ref="annotationConfiguration" />
  <property name="transactionManager" ref="txManager" />
  <property name="compassSettings">
   <props>
  <!-- 定义索引的存储位置 -->
<prop key="compass.engine.connection">d:/compass</prop>
<prop key="compass.transaction.factory">
    org.compass.spring.transaction.SpringSyncTransactionFactory
</prop>
  <!-- 定义分词器-->
<prop key="compass.engine.analyzer.MMAnalyzer.CustomAnalyzer">
org.mira.lucene.analysis.IK_CAnalyzer 
</prop>
</props>
</property>
     <property name="resourceDirectoryLocations">
   <list>
   <value>classpath:net/shopin/bean/product</value>
   </list>
  </property>

  </bean>

  <bean id="jpaGpsDevice"
  class="org.compass.gps.device.jpa.JpaGpsDevice">
  <property name="name">
   <value>JpaGpsDevice</value>
  </property>
  <property name="entityManagerFactory"
   ref="entityManagerFactory" />
  <property name="mirrorDataChanges">
   <value>true</value>
  </property>
  </bean>
  <!-- 数据库中的数据变化后同步更新索引 -->
  <bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps"
  init-method="start" destroy-method="stop">
  <property name="compass" ref="compass" />
  <property name="gpsDevices">
   <list>
   <bean 
    class="org.compass.spring.device.SpringSyncTransactionGpsDeviceWrapper">
    <property name="gpsDevice" ref="jpaGpsDevice" />
   </bean>
   </list>
  </property>
  </bean>

  <bean id="compassTemplate"
  class="org.compass.core.CompassTemplate">
  <property name="compass" ref="compass" />
  </bean>

  <!-- 定时重建索引(利用quartz)或随Spring ApplicationContext启动而重建索引 -->
  <bean id="compassIndexBuilder"
  class="net.shopin.service.search.impl.CompassIndexBuilder"
  lazy-init="false">
  <property name="compassGps" ref="compassGps" />
  <property name="buildIndex" value="true" />
  <property name="lazyTime" value="5" />
  </bean>




3.自动建立索引的java bean
/**
  * 通过quartz定时调度定时重建索引或自动随Spring ApplicationContext启动而重建索引的Builder.
  * 会启动后延时数秒新开线程调用compassGps.index()函数.
  * 默认会在Web应用每次启动时重建索引,可以设置buildIndex属性为false来禁止此功能.
  * 也可以不用本Builder, 编写手动调用compassGps.index()的代码.
  *
  */
public class CompassIndexBuilder implements InitializingBean {
   // 是否需要建立索引,可被设置为false使本Builder失效.
   private boolean buildIndex = false;

   // 索引操作线程延时启动的时间,单位为秒 
   private int lazyTime = 10;

   // Compass封装 
   private CompassGps compassGps;

   // 索引线程 
   private Thread indexThread = new Thread() {

     @Override
     public void run() {
       try {
         Thread.sleep(lazyTime * 1000);
         System.out.println("begin compass index...");
         long beginTime = System.currentTimeMillis();
         // 重建索引.
         // 如果compass实体中定义的索引文件已存在,索引过程中会建立临时索引,
         // 索引完成后再进行覆盖.
         compassGps.index();
         long costTime = System.currentTimeMillis() - beginTime;
         System.out.println("compss index finished.");
         System.out.println("costed " + costTime + " milliseconds");
       } catch (InterruptedException e) {
         e.printStackTrace();
       }
     }
   };

   /**
    * 实现<code>InitializingBean</code>接口,在完成注入后调用启动索引线程.
    *
    * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
    */
   public void afterPropertiesSet() throws Exception {
     if (buildIndex) {
       indexThread.setDaemon(true);
       indexThread.setName("Compass Indexer");
       indexThread.start();
     }
   }

   public void setBuildIndex(boolean buildIndex) {
     this.buildIndex = buildIndex;
   }

   public void setLazyTime(int lazyTime) {
     this.lazyTime = lazyTime;
   }

   public void setCompassGps(CompassGps compassGps) {
     this.compassGps = compassGps;
   }
}




4.建立索引Service 层代码

@Service
@Transactional 
public class SearchServiceBean extends DaoSupport implements SearchService {
  @Resource(name = "compass")
  Compass compass;

/** 创建索引 **/
public void index(ProductInfo p) {

CompassSession session = compass.openSession();
CompassTransaction tx = null;
try {
  tx = session.beginTransaction();
  session.create(p);
  tx.commit();
} catch (Exception e) {
  if (tx != null) {
tx.commit();
  }
  throw new RuntimeException(e);
  } finally {
  if (session != null) {
  session.close();
  }
  }
  }
  /** 删除一条索引 **/
  public void delete(ProductInfo p) {
  CompassTemplate ct = new CompassTemplate(compass);
  ct.delete(p);
  }
  /** 更新(重新创建)一条索引 **/
  public void update(final ProductInfo p) {
  CompassTemplate ct = new CompassTemplate(compass);

  CompassCallback<Object> action = new CompassCallback<Object>() {

   public Object doInCompass(CompassSession session)
    throws CompassException {
   session.delete(p);
   session.create(p);
   return null;
   }

  };

  ct.execute(action);
  }
  /** 索引查询 **/
  public List<ProductInfo> find(final String keywords) {
  CompassTemplate ct = new CompassTemplate(compass);
  return ct.execute(new CompassCallback<List<ProductInfo>>() {

   public List<ProductInfo> doInCompass(CompassSession session)
    throws CompassException {
   List<ProductInfo> result = new ArrayList<ProductInfo>();
   CompassQueryBuilder queryBuilder = session.queryBuilder();
   CompassHits hits = null; // session.find(query);
   /** 在所有字段中查询 **/
   CompassQuery allPropertyQuery = queryBuilder.queryString(keywords).toQuery();
   hits = allPropertyQuery.hits();
   /** 在指定字段中查询 **/
   // CompassQuery query = queryBuilder.term("name", keywords);
   // hits = query.hits();
   /** 指定范围查询 **/
//   CompassQuery dateRangeQuery =
//   queryBuilder.between("postTime",startTime, endTime, true);
//   hits = queryBuilder.bool()
//   .addMust(allPropertyQuery)
//   .addMust(dateRangeQuery)
//   .toQuery()
//   .hits();
//  System.out.println("---------");
   for (int i = 0; i < hits.length(); i++) {
    ProductInfo p = (ProductInfo) hits.data(i);
    /** 如果进行高亮的属性中没有出现关键字, 则返回null **/
//   String ht = hits.highlighter(i).fragment("name");
//   if (ht != null) {
//   p.setName(ht);
//   }
//   String hc = hits.highlighter(i).fragment("code");
//   if (hc != null) {
//   p.setCode(hc);
//   }
    result.add(p);
   }
   return result;
   }
  });
  }



控制层

@Controller("/search/gosearch")
public class SearchAction extends Action {
  @Resource(name = "searchServiceBean")
  private SearchService SearchService;

  @Override
  public ActionForward execute(ActionMapping mapping, ActionForm form, 
   HttpServletRequest request, HttpServletResponse response)
   throws Exception {
  String keywords=request.getParameter("word").trim();
  if(keywords==null||"".equals(keywords)){
   return mapping.findForward("noproduct");
  }
  System.out.println("------"+keywords);
  List<ProductInfo> list = SearchService.find(keywords);
  request.setAttribute("word", keywords);
  request.setAttribute("product",list);
     if(list.isEmpty()){
      return mapping.findForward("noproduct");
     }else{
      return mapping.findForward("list");

     }
  }
} 



junit测试
public class SearchTest {

  private static AbstractApplicationContext context;
  @BeforeClass 
  public static void setUpBeforeClass() throws Exception {
  try {
   context = new ClassPathXmlApplicationContext("beans.xml");
  } catch (Exception e) {
   e.printStackTrace();
  }
  }

  @Test 
  public void testDelete() {
  SearchService searchService = (SearchService) context 
  .getBean("searchServiceBean");
  ProductInfo p = new ProductInfo(2);
  searchService.delete(p);
  }

  @Test 
  public void createIndex(){
  SearchService searchService = (SearchService) context 
  .getBean("searchServiceBean");
  ProductInfoService productInfoService = (ProductInfoService) context 
  .getBean("productInfoServiceBean");
  List<ProductInfo> list=productInfoService.getAllProduct();
  for(ProductInfo productInfo:list){
//  System.out.println("-------"+productInfo.getName());
   searchService.index(productInfo);
  }
  }

  @Test 
  public void testSearch() {
  SearchService searchService = (SearchService) context 
  .getBean("searchServiceBean");
  String query = "手机";
  List<ProductInfo> ProductInfos;

   ProductInfos = searchService.find(query);
   for (ProductInfo p : ProductInfos) {
   System.out.println(p.getName());
   }
  System.out.println("------------");
  }
}