Java for Web學習筆記(一二三):搜尋(5)MySQL全文索引(下)
阿新 • • 發佈:2019-02-08
小例子
我們在表格Ticket和TicketComment中加入了fulltext key。小例子在Ticket的Subject或Body,以及在TicketComment的Body檢索內容,按分頁方式顯示出來,同時顯示關聯分數,並按關聯分數降序排列。
-- Ticket中隊Subject和Body這兩列進行全文檢索
FULLTEXT KEY `Ticket_Search` (`Subject`,`Body`),
-- TicketComment中對Body列進行全文檢索。
FULLTEXT KEY `TicketComment_Search` (`Body`),
增加一個搜尋結果,涵蓋TicketEntity和關聯分數
根據需求,我們將檢索兩個表格的內容,獲取分數,以檢索hello為例子,SQL語句如下-- 顯示Ticket表的內容 SELECT DISTINCT t.*, -- 顯示關聯分數(將兩個表格的關聯分數加起來),列名為 ft_scoreColumn (MATCH(t.Subject, t.Body) AGAINST("hello") + MATCH(c.Body) AGAINST("hello")) AS _ft_scoreColumn -- 將表格Ticket和TicketComment 根據ticket.id join在一起 FROM Ticket t LEFT OUTER JOIN TicketComment c ON c.TicketId = t.TicketId -- where存在關聯性 WHERE MATCH(t.Subject, t.Body) AGAINST("hello") OR MATCH(c.Body) AGAINST("hello") -- 設定排序 ORDER BY _ft_scoreColumn DESC, TicketId DESC;
在關聯表格的返回中,通常會包含多個內容,並不是只與某個Entity相對應,在本例中就含有分值,我們將學習如何對映這樣的結果。
建立存放結果的類SearchResult
public class SearchResult<T> { private final T entity; private final double relevance; public SearchResult(T entity, double relevance) { this.entity = entity; this.relevance = relevance; } public T getEntity() { return entity; } public double getRelevance() { return relevance; } }
@SqlResultSetMapping提供返回結果的對映關係
我們將這個對映關係命名為"searchResultMapping.ticket",放在TicketEntity中,當然也可以放在其他的Class,只要標記@SqlResultSetMapping即可。
@Entity
@Table(name = "Ticket")
@SqlResultSetMapping(
name = "searchResultMapping.ticket",
entities = { @EntityResult(entityClass = TicketEntity.class) },
columns = { @ColumnResult(name = "_ft_scoreColumn", type = Double.class)}
)
public class TicketEntity implements Serializable
這種方式等同與xml的配置。
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
version="2.1">
<sql-result-set-mapping name="searchResultMapping.ticket">
<entity-result entity-class="com.wrox.site.entities.TicketEntity" />
<column-result name="_ft_scoreColumn" class="java.lang.Double" />
</sql-result-set-mapping>
</entity-mappings>
這個配置一般位於/META-INF/orm.xml。也可以在persistence.xml中通過<mapping-file>來執行位置,或者通過下面的程式碼來執行位置。
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(){
...
factory.setJpaPropertyMap(properties);
factory.setMappingResources("com/example/config/mappings.xml");
return factory;
}
在createNativeQuery中將結果進行對映
我們使用了原生的SQL語句,對映方式如下,例子的具體實現在後面介紹
Query query = entityManager.createNativeQuery(sql, "searchResultMapping.ticket");
List<Object[]> results = query.getResultList();
for(Object[] result : results){
//result[0]對應searchResultMapping.ticket的第一項entites裡面的TicketEntity.class
//result[1]對應searchResultMapping.ticket的第二項,因為entities只有一項,因此為columns中的name = "_ft_scoreColumn", type = Double.class
}
在倉庫中增加查詢介面
public interface SearchableRepository<T>{
Page<SearchResult<T>> search(String query, boolean useBooleanMode, Pageable pageable);
}
public interface TicketRepository extends CrudRepository<TicketEntity, Long>,SearchableRepository<TicketEntity>{
}
介面實現
public class TicketRepositoryImpl implements SearchableRepository<TicketEntity>{
@PersistenceContext EntityManager entityManager;
@Override
public Page<SearchResult<TicketEntity>> search(String query, boolean useBooleanMode, Pageable pageable) {
String mode = useBooleanMode ? "IN BOOLEAN MODE" : "IN NATURAL LANGUAGE MODE";
String matchTicket = "MATCH(t.Subject, t.Body) AGAINST(?1 " + mode + ")";
String matchComment = "MATCH(c.Body) AGAINST(?1 " + mode + ")";
//1】分頁需要獲得總數以及該頁的資料,顯示獲取總數。請參考前面對sql的說明。
String sql = "SELECT COUNT(DISTINCT t.TicketId) FROM Ticket t " +
"LEFT OUTER JOIN TicketComment c ON c.TicketId = " +
"t.TicketId WHERE " + matchTicket + " OR " + matchComment;
//對於原生SQL的方式,返回結果是BigInteger不能直接轉換為Long。採用了Number來進行。
long total = ((Number) this.entityManager.createNativeQuery(sql).setParameter(1, query).getSingleResult())
.longValue();
//2】獲取該頁的資訊,
sql = "SELECT DISTINCT t.*, (" + matchTicket + " + " + matchComment +") AS _ft_scoreColumn " +
"FROM Ticket t LEFT OUTER JOIN TicketComment c ON c.TicketId = t.TicketId " +
"WHERE " + matchTicket + " OR " + matchComment + " " +
"ORDER BY _ft_scoreColumn DESC, TicketId DESC";
@SuppressWarnings("unchecked")
List<Object[]> results = this.entityManager.createNativeQuery(sql, "searchResultMapping.ticket")
.setParameter(1, query)
.setFirstResult(pageable.getOffset())
.setMaxResults(pageable.getPageSize())
.getResultList();
//3】將結果轉為我們定義SearchResult。
List<SearchResult<TicketEntity>> list = new ArrayList<>();
results.forEach(o -> list.add(
new SearchResult<TicketEntity>((TicketEntity)o[0], (Double)o[1])));
return new PageImpl<>(list,pageable,total);
}
}
使用createNativeQuery而不是criteria JPA介面意味著實現和底層和資料庫相關,如果更換為其他資料庫,需要重新編寫程式碼,而有些資料庫支援fulltext key有些不支援。