首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Compass + JPA 註解整合站內搜索完整實戰:從實體映射到索引重建服務

     編輯:Java 相關知識

專案背景

基於 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`**,否則關聯物件不會被索引。

Spring 整合配置 (compass-context.xml)







    
    
    
        
            d:/compass  
            org.compass.spring.transaction.SpringSyncTransactionFactory
            
            org.mira.lucene.analysis.IK_CAnalyzer
        
    
    
        classpath:com/example/entity 
    




    
    
     



    
    
        
            
                
            
        
    




    




    
    
     

索引重建服務 (CompassIndexBuilder)


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 層實現


@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 層調用


@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");
    }
}

JUnit 整合測試


@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()));
    }
}

避坑總結

  • 雙向關聯雙邊 `@SearchableReference`** 缺一不可。
  • 索引目錄權限**:進程需讀寫權限,Windows 路徑用正斜杠或雙反斜杠。
  • IK 分詞器 Jar** 需放入 Classpath,`compass.engine.analyzer` 鍵名固定格式。
  • 事務同步**:`mirrorDataChanges=true` 依賴 Spring 事務管理器,確保 `@Transactional` 生效。
  • 大數據量全量重建** 耗時長,生產環境建議**離線/藍綠部署**重建,或用增量 GPS 設備。

總結

Compass 以註解驅動映射、GPS 自動同步、Template 簡化操作,實現了 JPA 實體到 Lucene 索引的無縫橋接。掌握實體註解、Spring 配置、Service 封裝三層,即可快速落地站內搜索。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved