基於 JPA 註解的電商專案,需實現商品站內搜索。Compass 2.1 整合 Spring + JPA,支援中文分詞 (IK Analyzer),實現增量/全量索引重建、關鍵字高亮、多字段查詢。
| Compass 註解 | 作用 | JPA 協同位置 |
|---|---|---|
| `@Searchable` | 標記實體可被索引 | 類上,配合 `@Entity` |
| `@SearchableId` | 標記主鍵屬性 (唯一) | `@Id` 欄位上 |
| `@SearchableProperty` | 標記普通屬性索引 | Getter 上,可指定 `index=TOKENIZED`, `store=YES` |
| `@SearchableReference` | 關聯實體索引 (雙向一致) | 關聯集合/單值 Getter 上 |
| `@SearchableComponent` | 嵌入式組件索引 | `@Embedded` 物件上 |
// ProductInfo.java - 商品主表
@Entity @Searchable
public class ProductInfo implements Serializable {
@Id @GeneratedValue @SearchableId
private Integer id;
@Column(length=30) @SearchableProperty(index=Index.TOKENIZED, store=Store.YES)
private String code; // 貨號
@Column(length=50) @SearchableProperty(index=Index.TOKENIZED, store=Store.YES)
private String name; // 商品名
@ManyToOne @JoinColumn(name="typeid") @SearchableReference
private ProductType type; // 分類
@OneToMany(mappedBy="product", cascade={REMOVE,PERSIST})
@OrderBy("visible desc, id asc") @SearchableReference // 雙向一致!
private Set styles = new HashSet<>();
// ... getter/setter/hashCode/equals
}
// ProductType.java - 分類
@Entity @Searchable
public class ProductType implements Serializable {
@Id @GeneratedValue @SearchableId
private Integer typeid;
@Column(length=36) @SearchableProperty(index=Index.TOKENIZED, store=Store.YES)
private String name;
@OneToMany(mappedBy="type") @SearchableReference
private Set products = new HashSet<>();
@ManyToOne @JoinColumn(name="parentid")
private ProductType parent; // 父分類 (不索引避免循環)
// ...
}
// ProductStyle.java - 樣式/SKU
@Entity @Searchable
public class ProductStyle implements Serializable {
@Id @GeneratedValue @SearchableId
private Integer id;
@Column(length=30) @SearchableProperty(index=Index.TOKENIZED, store=Store.YES)
private String name;
@Column(length=40) @SearchableProperty(index=Index.UN_TOKENIZED, store=Store.YES)
private String imagename; // 圖片名 (精確匹配)
@ManyToOne @JoinColumn(name="productid") @SearchableReference
private ProductInfo product; // 反向引用
// @Transient 衍生屬性不索引
}
關鍵點:雙向關聯雙邊**必須同時加 `@SearchableReference`**,否則關聯物件不會被索引。
d:/compass
org.compass.spring.transaction.SpringSyncTransactionFactory
org.mira.lucene.analysis.IK_CAnalyzer
classpath:com/example/entity
public class CompassIndexBuilder implements InitializingBean {
private boolean buildIndex = true;
private int lazyTime = 10;
private CompassGps compassGps;
private Thread indexThread = new Thread(() -> {
try {
Thread.sleep(lazyTime * 1000L);
System.out.println("begin compass index...");
long start = System.currentTimeMillis();
compassGps.index(); // 全量重建 (存在則建臨時索引後覆蓋)
System.out.println("compass index finished, cost " + (System.currentTimeMillis() - start) + " ms");
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});
@Override public void afterPropertiesSet() {
if (buildIndex) { indexThread.setDaemon(true); indexThread.setName("Compass Indexer"); indexThread.start(); }
}
// setter 省略
}
@Service @Transactional
public class SearchServiceBean implements SearchService {
@Resource Compass compass;
// 單條創建
public void index(ProductInfo p) {
CompassSession s = compass.openSession();
CompassTransaction tx = s.beginTransaction();
try { s.create(p); tx.commit(); }
catch (Exception e) { if (tx != null) tx.rollback(); throw new RuntimeException(e); }
finally { s.close(); }
}
// 單條刪除
public void delete(ProductInfo p) {
new CompassTemplate(compass).delete(p);
}
// 更新 = 刪除 + 創建
public void update(ProductInfo p) {
new CompassTemplate(compass).execute(session -> {
session.delete(p);
session.create(p);
return null;
});
}
// 關鍵字搜索 (全字段 + 高亮可擴展)
public List find(String keywords) {
return new CompassTemplate(compass).execute(session -> {
CompassQuery query = session.queryBuilder().queryString(keywords).toQuery();
CompassHits hits = query.hits();
List list = new ArrayList<>();
for (int i = 0; i < hits.length(); i++) {
list.add((ProductInfo) hits.data(i));
// 高亮片段獲取範例:
// String hlName = hits.highlighter(i).fragment("name");
// if (hlName != null) product.setName(hlName);
}
return list;
});
}
}
@Controller("/search/gosearch")
public class SearchAction extends Action {
@Resource SearchService searchService;
@Override
public ActionForward execute(ActionMapping m, ActionForm f,
HttpServletRequest req, HttpServletResponse resp) {
String kw = req.getParameter("word").trim();
if (kw.isEmpty()) return m.findForward("noproduct");
List list = searchService.find(kw);
req.setAttribute("word", kw);
req.setAttribute("product", list);
return list.isEmpty() ? m.findForward("noproduct") : m.findForward("list");
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans.xml")
public class SearchTest {
@Autowired SearchService searchService;
@Autowired ProductInfoService productService;
@Test public void fullIndex() {
productService.getAllProduct().forEach(searchService::index);
}
@Test public void search() {
searchService.find("手機").forEach(p -> System.out.println(p.getName()));
}
}
Compass 以註解驅動映射、GPS 自動同步、Template 簡化操作,實現了 JPA 實體到 Lucene 索引的無縫橋接。掌握實體註解、Spring 配置、Service 封裝三層,即可快速落地站內搜索。