前段時間已經寫了一篇關於compass的文章,相信大家對compass也已經有了一定的了解
由於最近做的項目中涉及到了站內搜索,而且是基於JPA注解形式的,在網上找了好久,關於JPA集成compass的例子很少,有些也是基於 xml的,基於注解形式的甚是少,沒有辦法只有去compass的官網下載英文文檔自己研究一下,花費了一下午時間調試出來,集成到項目中!
在這裡給大家分享下,希望大家可以少走些彎路!
1.去官方網站下載compass的jar包,我用的的2.1版本
http://www.compass-project.org/
ProductInfo.java
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
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
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的配置文件中加入以下代碼
Java代碼
<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
Java代碼
/**
* 通過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 層代碼
Java代碼
@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;
}
});
}
控制層
Java代碼
@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測試
Java代碼
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("------------");
}
}