Solr的defType有dismax/edismax兩種,這兩種的區別,可參見:http://blog.csdn.net/duck_genuine/article/details/8060026java
下面示例用於演示以下場景:node
有一網站,在用戶查詢的結果中,須要按這樣排序:apache
這樣的查詢排序使用普通的查詢結果的Order by是作不到的,必需使用solr的defType。app
作法:socket
一、先看schema.xml的定義:函數
<?xml version="1.0" ?> <schema name="sample5" version="1.1"> <fieldtype name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/> <fieldType name="long" class="solr.TrieLongField" precisionStep="0" positionIncrementGap="0"/> <fieldType name="tdate" class="solr.TrieDateField" precisionStep="6" positionIncrementGap="0"/> <fieldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/> <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" positionIncrementGap="0"/> <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" positionIncrementGap="0"/> <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/> <fieldtype name="binary" class="solr.BinaryField"/> <fieldType name="text_cn" class="solr.TextField"> <analyzer type="index" class="org.wltea.analyzer.lucene.IKAnalyzer" useSmart="false" /> <analyzer type="query" class="org.wltea.analyzer.lucene.IKAnalyzer" useSmart="true" /> <analyzer> <tokenizer class="solr.KeywordTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory" ignoreCase="true"/> </analyzer> </fieldType> <!-- general --> <fields> <field name="id" type="long" indexed="true" stored="true" multiValued="false" required="true"/> <field name="subject" type="text_cn" indexed="true" stored="true" /> <field name="content" type="text_cn" indexed="true" stored="true" /> <field name="regionId" type="int" indexed="true" stored="true" /> <field name="region" type="text_cn" indexed="true" stored="true" /> <field name="categoryId" type="int" indexed="true" stored="true" /> <field name="category" type="text_cn" indexed="true" stored="true" /> <field name="price" type="float" indexed="true" stored="true" /> <field name="createTime" type="tdate" indexed="true" stored="true" /> <field name="point" type="long" indexed="true" stored="true" /> <field name="vip" type="boolean" indexed="true" stored="true" /> <field name="_version_" type="long" indexed="true" stored="true"/> <field name="searchText" type="text_cn" indexed="true" stored="false" multiValued="true" /> </fields> <copyField source="subject" dest="searchText" /> <copyField source="content" dest="searchText" /> <copyField source="region" dest="searchText" /> <copyField source="category" dest="searchText" /> <!-- field to use to determine and enforce document uniqueness. --> <uniqueKey>id</uniqueKey> <!-- field for the QueryParser to use when an explicit fieldname is absent --> <defaultSearchField>searchText</defaultSearchField> <!-- SolrQueryParser configuration: defaultOperator="AND|OR" --> <solrQueryParser defaultOperator="AND"/> </schema>
說明:測試
a)裏頭定義了一個copyField:searchText,此字段爲:subject+content+region+category,並把這個字段設置爲默認查詢字段。意思是查詢時,默認查詢四個字段的內容。網站
b)把solrQueryParser設置爲AND,事實上,大多狀況下,咱們是習慣使用AND爲條件查詢,而非ORui
c)text_cn字段類型中的:useSmartthis
<analyzer type="index" class="org.wltea.analyzer.lucene.IKAnalyzer" useSmart="false" /> <analyzer type="query" class="org.wltea.analyzer.lucene.IKAnalyzer" useSmart="true" />
意思是:useSmart =true ,分詞器使用智能切分策略, =false則使用細粒度切分。詳細,可下載IK分詞器的源碼看看。
二、加入一個查詢Handler到solrconfig.xml的<config/>當中:
<requestHandler name="/browse" class="solr.SearchHandler" default="true" > <lst name="defaults"> <str name="defType">edismax</str> <str name="bf"> sum(linear(vip,1000,0),linear(sqrt(log(linear(point,1,2))),100,0),sqrt(log(ms(createTime)))) </str> <!--<str name="pf"> searchText </str> <str name="qf"> subject^1 content^0.8 </str>--> </lst> </requestHandler>
說明:
a)上面的default="true"意思爲設置爲默認的查詢handler(記得把原standard中的default="true"刪除掉)
b)見已經被註釋的這段:
<!--<str name="pf"> searchText </str> <str name="qf"> subject^1 content^0.8 </str>-->
這是簡單的不使用bf的排序加權方式,能夠用於應付簡單的排序,具體pf/qf的使用,能夠上網上搜搜應用。這裏演示的功能相對「複雜」,不適用它。
c)見這句公式:
sum(linear(vip,1000,0),linear(sqrt(log(linear(point,1,2))),100,0),sqrt(log(ms(createTime))))
公式中的函數定義和意思,能夠參考:
官方文檔:
http://wiki.apache.org/solr/FunctionQuery
中文說明:
http://mxsfengg.iteye.com/blog/352191
這裏的函數意思是:
以上三個值相加得出最統權重分從高到低排序
三、Java bean:
package com.my.entity; import java.util.Date; import org.apache.solr.client.solrj.beans.Field; public class Item { @Field private long id; @Field private String subject; @Field private String content; @Field private int regionId; @Field private int categoryId; @Field private float price; @Field private Date createTime; @Field private long point; @Field private boolean vip; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public int getRegionId() { return regionId; } public void setRegionId(int regionId) { this.regionId = regionId; } public int getCategoryId() { return categoryId; } public void setCategoryId(int categoryId) { this.categoryId = categoryId; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public long getPoint() { return point; } public void setPoint(long point) { this.point = point; } public boolean isVip() { return vip; } public void setVip(boolean vip) { this.vip = vip; } }
四、Java測試代碼:
package com.my.solr; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrQuery.ORDER; import org.apache.solr.client.solrj.SolrQuery.SortClause; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.apache.solr.client.solrj.impl.XMLResponseParser; import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.FacetField.Count; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.params.AnalysisParams; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.FacetParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SimpleOrderedMap; import com.my.entity.Item; public class TestSolr { private static HashMap<Integer, String> mapRegion = new HashMap<Integer, String>(); private static HashMap<Integer, String> mapCategory = new HashMap<Integer, String>(); @SuppressWarnings("unchecked") public static void main(String[] args) throws IOException, SolrServerException { // ------------------------------------------------------ // Set map // ------------------------------------------------------ mapRegion.put(1, "羅湖區"); mapRegion.put(2, "南山區"); mapRegion.put(3, "龍崗區"); mapRegion.put(4, "福田區"); mapCategory.put(1, "單間"); mapCategory.put(2, "2房1廳"); mapCategory.put(3, "3房2廳"); mapCategory.put(4, "1房1廳"); String url = "http://localhost:8983/solr/sample5"; HttpSolrServer core = new HttpSolrServer(url); core.setMaxRetries(1); core.setConnectionTimeout(5000); core.setParser(new XMLResponseParser()); // binary parser is used by // default core.setSoTimeout(1000); // socket read timeout core.setDefaultMaxConnectionsPerHost(100); core.setMaxTotalConnections(100); core.setFollowRedirects(false); // defaults to false core.setAllowCompression(true); // ------------------------------------------------------ // remove all data // ------------------------------------------------------ core.deleteByQuery("*:*"); List<Item> items = new ArrayList<Item>(); items.add(makeItem(items.size() + 1, "龍城公寓一房一廳", "豪華城城公寓1房1廳,擰包入住", 1, 1, 1200f, 10, false)); items.add(makeItem(items.size() + 1, "興新宿舍樓 1室0廳", " 中等裝修 招女性合租", 1, 1, 1000f, 11, false)); items.add(makeItem(items.size() + 1, "西麗新屋村新宿舍樓單間", " 無敵裝修只招女性", 2, 1, 1000f, 2, true)); items.add(makeItem(items.size() + 1, "大芬村信和愛琴居地鐵口2房1廳", " 地鐵口 + 出行便利=居家首選", 3, 2, 2000f, 5, false)); items.add(makeItem(items.size() + 1, "龍崗富豪花園3房2廳出租", " 離地鐵口只要5分鐘,快來秒殺吧", 3, 3, 4500f, 21, true)); items.add(makeItem(items.size() + 1, "海景房園3房2廳出租", "海景房園出租,無敵海景,能夠看到倫敦", 4, 3, 8500f, 12, false)); items.add(makeItem(items.size() + 1, "天域花園1房1廳", "天域花園,男女不限,入住免水電一月", 2, 4, 1500f, 13, true)); items.add(makeItem(items.size() + 1, "神同樣的漂亮,玉馨山莊3房2廳", "心動不如行動,擰包便可入住,來吧!", 1, 3, 9500f, 8, false)); items.add(makeItem(items.size() + 1, "玉馨山莊2房1廳,情侶最愛", "宅男宅女快來吧只要2500,走過路過,別再錯過", 1, 2, 2500f, 5, false)); items.add(makeItem(items.size() + 1, "天域花園3房2廳", "天域花園出租,都來看看,都來瞄瞄,3房出租只要7500.", 4, 3, 7500f, 6, true)); items.add(makeItem(items.size() + 1, "深都花園出租3房2廳", "找愛乾淨的人氏,全新裝修", 4, 3, 5200f, 31, false)); items.add(makeItem(items.size() + 1, "This is Mobile test", "haha Hello world!", 4, 3, 1200f, 31, false)); core.addBeans(items); // commit core.commit(); // ------------------------------------------------------ // Set search text // ------------------------------------------------------ String searchText = AnalysisSearchText(core, "出租花園"); //subject:*出租* && price:[1000 TO 8000] System.out.println("Search Text:" + searchText); // ------------------------------------------------------ // Set query text // ------------------------------------------------------ String queryText = searchText + "&& price:[1000 TO 8000]"; System.out.println("Query Text:" + queryText); // ------------------------------------------------------ // search // ------------------------------------------------------ SolrQuery query = new SolrQuery(); query.setQuery(queryText); query.setStart(0); // query的開始行數(分頁使用) query.setRows(100); // query的返回行數(分頁使用) query.setFacet(true); // 設置使用facet query.setFacetMinCount(0); // 設置facet最少的統計數量 query.setFacetLimit(10); // facet結果的返回行數 query.addFacetField("categoryId", "regionId"); // facet的字段 query.setFacetSort(FacetParams.FACET_SORT_COUNT); //query.addSort(new SortClause("id", ORDER.asc)); // 排序 query.setRequestHandler("/browse"); QueryResponse response = core.query(query); List<Item> items_rep = response.getBeans(Item.class); List<FacetField> facetFields = response.getFacetFields(); // 由於上面的start和rows均設置爲0,因此這裏不會有query結果輸出 System.out.println("--------------------"); System.out.println("Search result:"); for (Item item : items_rep) { System.out.println("id=" + item.getId() + "\tsubject=" + item.getSubject() + "\tregion=" + mapRegion.get(item.getRegionId()) + "\tcategory=" + mapCategory.get(item.getCategoryId()) + "\tprice=" + item.getPrice()); } // 打印全部facet for (FacetField ff : facetFields) { System.out.println("--------------------"); System.out.println("name=" + ff.getName() + "\tcount=" + ff.getValueCount()); System.out.println("--------------------"); switch (ff.getName()) { case "regionId": printOut(mapRegion, ff.getValues()); break; case "categoryId": printOut(mapCategory, ff.getValues()); break; } } } @SuppressWarnings({ "rawtypes" }) private static void printOut(HashMap map, List<Count> counts) { for (Count count : counts) { System.out.println("name=" + map.get(Integer.parseInt(count.getName())) + "\tcount=" + count.getCount()); } System.out.println("--------------------"); } private static Item makeItem(long id, String subject, String content, int regionId, int categoryId, float price, long point, boolean vip) { Calendar cale = Calendar.getInstance(); cale.setTime(new Date()); cale.add(Calendar.DATE, (int)id); Item item = new Item(); item.setId(id); item.setSubject(subject); item.setContent(content); item.setRegionId(regionId); item.setCategoryId(categoryId); item.setPrice(price); item.setCreateTime(cale.getTime()); item.setPoint(point); item.setVip(vip); return item; } @SuppressWarnings("unchecked") /** * 從新將須要查詢的文本內容解析成分詞 * @param core * @param searchText * @return * @throws SolrServerException */ private static String AnalysisSearchText(HttpSolrServer core, String searchText) throws SolrServerException { StringBuilder strSearchText = new StringBuilder(); final String STR_FIELD_TYPE = "text_cn"; SolrQuery queryAnalysis = new SolrQuery(); queryAnalysis.add(CommonParams.QT, "/analysis/field"); // query type queryAnalysis.add(AnalysisParams.FIELD_VALUE, searchText); queryAnalysis.add(AnalysisParams.FIELD_TYPE, STR_FIELD_TYPE); QueryResponse responseAnalysis = core.query(queryAnalysis); //對響應進行解析 NamedList<Object> analysis = (NamedList<Object>) responseAnalysis.getResponse().get("analysis");// analysis node NamedList<Object> field_types = (NamedList<Object>) analysis.get("field_types");// field_types node NamedList<Object> fieldType = (NamedList<Object>) field_types.get(STR_FIELD_TYPE);// text_cn node NamedList<Object> index = (NamedList<Object>) fieldType.get("index");// index node List<SimpleOrderedMap<String>> list = (ArrayList<SimpleOrderedMap<String>>)index.get("org.wltea.analyzer.lucene.IKTokenizer");// tokenizer node // 在每一個詞條中間加上空格,爲每一個詞條進行或運算 for(Iterator<SimpleOrderedMap<String>> iter = list.iterator(); iter.hasNext();) { strSearchText.append(iter.next().get("text") + " "); } return strSearchText.toString(); } }
說明:
a)AnalysisSearchText(...)方法:此方法會把須要查詢的語句先使用分詞分析,如上例子「出租花園」,調用AnalysisSearchText(...)後,會獲得「出租 花園」,會把兩個詞分拆成以空格分隔的字符串。否則solr會以「出租花園」總體作爲詞作查詢而得不到結果。
b)使用自定義的Handler,須要在代碼中加入這句:
query.setRequestHandler("/browse");
對應的是solrconfig.xml中的requestHandler的:/browse
五、運行結果:
或者使用solr的query查詢查看結果: