Solr高亮查詢案例
阿新 • • 發佈:2018-12-10
本例程使用Solr高亮查詢商品名稱,使用FreeMarker生成靜態頁面,Solr與FreeMarker都是使用直接呼叫的方式,沒有使用與SpringBoot整合方案。
資料庫SQL
create table t_good (id int auto_increment primary key,
name varchar(100),price float,dt date);
create table t_shop(id int auto_increment primary key,
name varchar(100));
create table t_g2s(gid int ,sid int);
insert into t_shop(name) values('海淀');
insert into t_shop(name) values('朝陽');
insert into t_shop(name) values('豐臺');
insert into t_good(name,price) values('七匹狼短袖T恤',200);
insert into t_good(name,price) values('花花公子短袖T恤',100);
insert into t_good(name,price) values('森馬短袖T恤',300);
insert into t_g2s(gid,sid) values (1,1);
insert into t_g2s(gid,sid) values(1,2);
insert into t_g2s(gid,sid) values(2,2);
insert into t_g2s(gid,sid) values(2,3);
select * from t_shop s,t_g2s g2s where s.id=g2s.sid;
select g2s.gid,group_concat(s.name) sname,group_concat(s.id) sid from t_shop s,t_g2s g2s
where s.id=g2s.sid group by g2s.gid;
select * from t_good a left join
(select g2s.gid,group_concat(s.name) sname,group_concat(s.id) sid from t_shop s,t_g2s g2s
where s.id=g2s.sid group by g2s.gid) b
on a.id=b.gid where a.id=8;
資料庫實體類設計
package com.test.bean;
import java.io.Serializable;
import java.sql.Date;
import java.sql.Timestamp;
import org.apache.solr.client.solrj.beans.Field;
public class GoodInfo implements Serializable{
private Integer id = null;
private String name = null;
private Float price = null;
private Date dt = null;
private Timestamp ts = null;
private String shopId = null;//id,id,id
private String shopName = null;//name,name,name
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
public Date getDt() {
return dt;
}
public void setDt(Date dt) {
this.dt = dt;
}
public String getShopId() {
return shopId;
}
public void setShopId(String shopId) {
this.shopId = shopId;
}
public String getShopName() {
return shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
}
package com.test.bean;
import java.io.Serializable;
public class ShopInfo implements Serializable{
private Integer id = null;
private String name = null;
private String checked = "";
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getChecked() {
return checked;
}
public void setChecked(String checked) {
this.checked = checked;
}
}
package com.test.bean;
import java.io.Serializable;
public class G2SInfo implements Serializable{
private Integer gid = null;
private Integer sid = null;
public Integer getGid() {
return gid;
}
public void setGid(Integer gid) {
this.gid = gid;
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
}
Mybatis Xml配置檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.GoodMapper">
<select id="getGoodById" resultType="com.test.bean.GoodInfo">
select a.*,b.shopName,b.shopId from t_good a left join
(select g2s.gid,group_concat(s.name) shopName,group_concat(s.id) shopId from t_shop s,t_g2s g2s
where s.id=g2s.sid group by g2s.gid) b
on a.id=b.gid where a.id=#{id}
</select>
<insert id="saveGood" parameterType="com.test.bean.GoodInfo"
useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into t_good(name,price,dt) values (#{name},#{price},#{dt})
</insert>
<update id="updateGood" parameterType="com.test.bean.GoodInfo">
update t_good set name=#{name},price=#{price},dt=#{dt} where id=#{id}
</update>
<delete id="deleteGood" parameterType="Integer">
delete from t_good where id=#{id}
</delete>
<select id="getShop" resultType="com.test.bean.ShopInfo">
select * from t_shop
</select>
<insert id="saveG2S" parameterType="com.test.bean.G2SInfo">
insert into t_g2s(gid,sid) values (#{gid},#{sid})
</insert>
<delete id="deleteG2S" parameterType="Integer">
delete from t_g2s where gid=#{gid}
</delete>
</mapper>
Mybatis Mapper介面
package com.test.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.test.bean.G2SInfo;
import com.test.bean.GoodInfo;
import com.test.bean.ShopInfo;
@Mapper
public interface GoodMapper {
public GoodInfo getGoodById(@Param("id") Integer id);
public void saveGood(GoodInfo gi);
public void updateGood(GoodInfo gi);
public void deleteGood(@Param("id") Integer id);
public List<ShopInfo> getShop();
public void saveG2S(G2SInfo g2s);
public void deleteG2S(@Param("gid") Integer gid);
}
Good服務介面與實現類
package com.test.service;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.test.bean.G2SInfo;
import com.test.bean.GoodInfo;
import com.test.bean.ShopInfo;
public interface IGoodService {
public GoodInfo getGoodById(Integer id);
public void saveGood(GoodInfo gi);
public void updateGood(GoodInfo gi);
public void deleteGood(Integer id);
public List<ShopInfo> getShop();
public void saveG2S(G2SInfo g2s);
public void deleteG2S(Integer gid);
}
package com.test.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.test.bean.G2SInfo;
import com.test.bean.GoodInfo;
import com.test.bean.ShopInfo;
import com.test.mapper.GoodMapper;
import com.test.service.IGoodService;
@Service
public class GoodServiceImpl implements IGoodService{
@Autowired
private GoodMapper mapper;
@Override
public GoodInfo getGoodById(Integer id) {
try
{
return mapper.getGoodById(id);
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
@Override
public void saveGood(GoodInfo gi) {
try
{
mapper.saveGood(gi);
}
catch(Exception e)
{
e.printStackTrace();
}
}
@Override
public void updateGood(GoodInfo gi) {
try
{
mapper.updateGood(gi);
}
catch(Exception e)
{
e.printStackTrace();
}
}
@Override
public void deleteGood(Integer id) {
try
{
mapper.deleteGood(id);
}
catch(Exception e)
{
e.printStackTrace();
}
}
@Override
public void saveG2S(G2SInfo g2s) {
try
{
mapper.saveG2S(g2s);
}
catch(Exception e)
{
e.printStackTrace();
}
}
@Override
public void deleteG2S(Integer gid) {
try
{
mapper.deleteG2S(gid);
}
catch(Exception e)
{
e.printStackTrace();
}
}
@Override
public List<ShopInfo> getShop() {
try
{
return mapper.getShop();
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
}
GoodController設計
package com.test.ctrl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.test.bean.G2SInfo;
import com.test.bean.GoodInfo;
import com.test.bean.ShopInfo;
import com.test.service.IGoodService;
import com.test.util.FreeMarkerUtil;
import com.test.util.PageUtil;
import com.test.util.ResultInfo;
import com.test.util.SolrUtil;
@Controller
public class GoodCtrl {
@Autowired
private IGoodService serv;
//使用直連Solr的方式,沒有使用整合的SolrClient
private String url = "http://localhost:8984/solr/new_core2";
/**
* 列表頁面
* @param req
* @param page 當前頁碼
* @param rows 每頁顯示記錄數
* @return
*/
@RequestMapping("/list")
public String list(HttpServletRequest req,Integer page,Integer rows)
{
if(page == null)
page = 1;
if(rows == null)
rows = 4;
String name = req.getParameter("name");
//定義查詢Solr的字串
String query = null;
if(name == null || "".equals(name))
query = "*:*";
else
query = "name:"+name;
System.out.println("query="+query);
List<String> flList = new ArrayList<String>();
flList.add("name");
Integer starts = (page-1)*rows;
//使用SolrUtil工具類高亮查詢
SolrUtil solr = new SolrUtil(url);
//高亮查詢返回自定義物件ResultInfo
ResultInfo<GoodInfo> rtn = solr.queryHL(GoodInfo.class,query,
flList,starts,rows);
String listUrl = "/list";
Long total = rtn.getTotal();
//實現分頁導航
PageUtil pu = new PageUtil(listUrl,page,rows,total);
List<GoodInfo> list = rtn.getList();
String pageHtml = pu.toHtml();
//新增Request上下文資料,前臺頁面使用JSTL標籤展示
req.setAttribute("list", list);
req.setAttribute("pageHtml", pageHtml);
req.setAttribute("query", name);
return "good";
}
/**
* 前臺點選跳轉對應方法
*/
@RequestMapping("/add")
public String add(HttpServletRequest req)
{
//檢索店鋪列表,存放到Request物件中,以備前臺頁面展示
List<ShopInfo> shopList = serv.getShop();
req.setAttribute("shopList", shopList);
return "add";
}
/**
* 儲存和更新對應的方法
* @param req
* @param gi 通過Request中的引數封裝物件GoodInfo
* @return
*/
@RequestMapping("/save")
public String save(HttpServletRequest req,GoodInfo gi)
{
Integer id = gi.getId();
//必須在儲存GoodInfo之前,獲取店鋪ID
String shopId = gi.getShopId();
System.out.println("shopId="+shopId);
boolean isNew = false;
if(id == null)
{
//新增
serv.saveGood(gi);
isNew = true;
}
else
{
//更新
serv.updateGood(gi);
//刪除原來此商品關聯的店鋪資訊
serv.deleteG2S(id);
isNew = false;
}
//根據頁面傳入店鋪ID資訊,新增到關聯表
if(shopId != null)
{
String[] dim = shopId.split(",");
for(String sid:dim)
{
G2SInfo g2s = new G2SInfo();
//物件gi儲存後記錄了生成的ID,需要在Mybatis後臺XML檔案配置useGenerateKeys=true
g2s.setGid(gi.getId());
g2s.setSid(Integer.parseInt(sid));
serv.saveG2S(g2s);
}
}
//重新關聯查詢GoodInfo物件,需要從中間表裝載店鋪相關資訊
gi = serv.getGoodById(gi.getId());
//定義Map物件,將資料儲存到Solr中
Map m = new HashMap();
m.put("id", gi.getId().toString());
m.put("name", gi.getName());
m.put("price", gi.getPrice());
m.put("dt", gi.getDt());
m.put("shopId", gi.getShopId());
m.put("shopName", gi.getShopName());
System.out.println("map="+m);
SolrUtil solr = new SolrUtil(url);
if(isNew)
solr.addMap(m);//新增
else
{
solr.update(m);//更新
//根據Map物件和FreeMarker模板生成靜態頁面
String ftlDir = "flt";
String ftlName = "good.html";
String htmlDir = "html";
FreeMarkerUtil.genHtml(req.getServletContext(), m, ftlDir, ftlName, htmlDir);
}
return "redirect:list";
}
/**
* 根據前臺傳入的ID刪除資料庫及Solr中的資料,也刪除靜態頁面
* @param req
* @param chkid
* @return
*/
@RequestMapping("/delete")
public String delete(HttpServletRequest req,Integer[] chkid)
{
if(chkid != null)
{
SolrUtil solr = new SolrUtil(url);
for(Integer id:chkid)
{
System.out.println("id="+id);
serv.deleteGood(id);
serv.deleteG2S(id);
solr.deleteById(id.toString());
//刪除靜態頁面
String htmlDir = "html";
FreeMarkerUtil.deleteHtml(req.getServletContext(), htmlDir, id);
}
}
//跳轉到列表頁面
return "redirect:list";
}
/**
* 修改GoodInfo頁面對應的方法
* @param req
* @param id
* @return
*/
@RequestMapping("/modify")
public String modify(HttpServletRequest req,Integer id)
{
//根據前臺傳入的ID,檢索GoodInfo物件
GoodInfo gi = serv.getGoodById(id);
String shopId = gi.getShopId();
//根據物件包含的店鋪ID資訊,選中Checkbox狀態
List<ShopInfo> shopList = serv.getShop();
for(ShopInfo si:shopList)
{
System.out.println(shopId+","+si.getId());
if(shopId.indexOf(si.getId().toString())>=0)
{
si.setChecked("checked");
}
}
req.setAttribute("shopList", shopList);
req.setAttribute("good", gi);
return "add";
}
/**
* 檢視靜態頁面,根據Map與Freemarker模板生成靜態頁面,並跳轉到靜態頁面
* @param req
* @param id
* @return
*/
@RequestMapping("/view")
public String view(HttpServletRequest req,Integer id)
{
GoodInfo gi = serv.getGoodById(id);
Map m = new HashMap();
m.put("id", gi.getId().toString());
m.put("name", gi.getName());
m.put("price", gi.getPrice());
m.put("dt", gi.getDt());
m.put("shopId", gi.getShopId());
m.put("shopName", gi.getShopName());
System.out.println("map="+m);
//使用FreeMarkerUtil工具類生成靜態頁面
String ftlDir = "flt";
String ftlName = "good.html";
String htmlDir = "html";
FreeMarkerUtil.genHtml(req.getServletContext(), m, ftlDir, ftlName, htmlDir);
String htmlFile = "/html/"+id+".html";
return "redirect:"+htmlFile;
}
}
SolrUtil工具類
package com.test.util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
public class SolrUtil {
private HttpSolrClient client = null;
public SolrUtil(String url)
{
//SolrJ-7.3.1
//client = new HttpSolrClient.Builder(url).build();
//SolrJ-5.5.5
client = new HttpSolrClient(url);
}
/**
* 將Map物件新增到Solr中
* @param m
*/
public boolean addMap(Map m)
{
//Key-Value,
//Key Set;Value Collection
Set kset = m.keySet();
SolrInputDocument doc = new SolrInputDocument();
for(Object k:kset)
{
Object val = m.get(k);
if(val != null)
doc.addField(k.toString(), val);
}
try
{
client.add(doc);
UpdateResponse resp = client.commit();
if(resp.getStatus() == 0)
return true;
}
catch(Exception e)
{
e.printStackTrace();
}
return false;
}
/**
* 更新Solr中的文件,Map物件中必須存在id鍵用於定位doc文件
* Map中其他的鍵值對是修改的內容,Key<String>代表資料域名稱,
* Value<Object>代表修改值
* @param map
*/
public boolean update(Map<String,Object> map)
{
try
{
String id = (String)map.get("id");
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", id);
for(String k:map.keySet())
{
if(!"id".equals(k))
{
Map map2 = new HashMap();
map2.put("set", map.get(k));
doc.addField(k, map2);
}
}
client.add(doc);
UpdateResponse resp = client.commit();
if(resp.getStatus() == 0)
return true;
}
catch(Exception e)
{
e.printStackTrace();
}
return false;
}
/**
* 通過文件ID刪除Solr中的文件
* @param id
*/
public boolean deleteById(String id)
{
try
{
client.deleteById(id);
UpdateResponse resp = client.commit();
if(resp.getStatus() == 0)
return true;
}
catch(Exception e)
{
e.printStackTrace();
}
return false;
}
/**
* 通過查詢刪除Solr中對應的資料集合
* @param query
*/
public boolean deleteByQuery(String query)
{
try
{
client.deleteByQuery(query);
UpdateResponse resp = client.commit();
if(resp.getStatus() == 0)
return true;
}
catch(Exception e)
{
e.printStackTrace();
}
return false;
}
/**
* 通過泛型獲取Solr中的物件集合
* @param clz 泛型類對應java.lang.Class
* @param query 資料域名稱:資料域的值;查詢全部*:*;多條件查詢 name:Java AND age:20
* @param flList 高亮顯示資料域名稱,是List<String>集合
* @param page 分頁查詢時,開始記錄數
* @param rows 本次查詢檢索記錄數
* @return
*/
public <T> ResultInfo<T> queryHL(Class<T> clz,String query,List<String> flList,
Integer page,Integer rows)
{
try
{
//定義返回自定義資料結構物件
ResultInfo<T> rslt = new ResultInfo<T>();
SolrQuery q = new SolrQuery();
q.set("q", query);
q.set("fl","*");
q.setHighlight(true);
//高亮顯示欄位
String hlField = "";
for(String s:flList)
hlField = hlField + s + ",";
if(hlField.endsWith(","))
hlField = hlField.substring(0,hlField.length()-1);
q.set("hl.fl",hlField);
//
q.setHighlightSimplePre("<font color=\"red\">");
q.setHighlightSimplePost("</font>");
q.setStart(page);
q.setRows(rows);
QueryResponse qr = client.query(q);
Map<String,Map<String,List<String>>> hlMap = qr.getHighlighting();
System.out.println(hlMap);
//Map<ID,Map<FieldName,[MultiValue]>>
SolrDocumentList lst = qr.getResults();
List<T> rtn = new ArrayList<T>();
Long total = qr.getResults().getNumFound();
for(SolrDocument doc:lst)
{
String id = (String)doc.getFieldValue("id");
T t = clz.newInstance();
//獲取自定義類所有屬性名稱
Field[] flds = getField(clz);
for(Field field:flds)
{
String fname = field.getName();
String solrFldName = getSolrFieldName(clz,field);
String fObj = getSingleValue(doc.getFieldValue(solrFldName));
if(fObj == null)
continue;
if(field.getType() == java.sql.Date.class)
{
java.util.Date dt = new java.util.Date(fObj);
fObj = new java.sql.Date(dt.getTime()).toString();
}
if(field.getType() == java.sql.Timestamp.class)
{
java.util.Date dt = new java.util.Date(fObj);
fObj = new java.sql.Timestamp(dt.getTime()).toString();
}
if(field.getType() == java.sql.Time.class)
{
java.util.Date dt = new java.util.Date(fObj);
fObj = new java.sql.Time(dt.getTime()).toString();
}
//高亮顯示資料形式
////Map<ID,Map<FieldName,[MultiValue]>>
if(flList.contains(fname))
{
//Map<FieldName,List<MultiValue>>
Map<String,List<String>> fldMap = hlMap.get(id);
Object hlObj = fldMap.get(fname);
String hlVal = getSingleValue(hlObj);
if(hlVal != null)
fObj = hlVal;
}
if(fObj != null)
BeanUtils.setProperty(t, fname, fObj);
}
rtn.add(t);
}
rslt.setList(rtn);
rslt.setTotal(total);
return rslt;
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
/**
* 轉化多值域為單值
* @param obj
* @return
*/
private String getSingleValue(Object obj)
{
if(obj == null)
return null;
String val = obj.toString();
if(val.startsWith("[") && val.endsWith("]"))
{
return val.substring(1,val.length()-1);
}
return val;
}
/**
* 根據Class物件獲取此型別的定義屬性資料
* @param clz
* @return
*/
private Field[] getField(Class clz)
{
Field[] flds = clz.getDeclaredFields();
return flds;
}
/**
* 通過Field物件取得其上定義的註解名稱
* @param clz
* @param fld
* @return
*/
private String getSolrFieldName(Class clz,Field fld)
{
org.apache.solr.client.solrj.beans.Field fld2 =
fld.getAnnotation(org.apache.solr.client.solrj.beans.Field.class);
if(fld2 == null)
return fld.getName();
if(fld2.value().equals("#default"))
return fld.getName();
else
return fld2.value();
}
public static void main(String[] args)
{
System.out.println(new java.util.Date());
Map m = new HashMap();
m.put("sid", "10");
m.put("name","Java");
String url = "http://localhost:8984/solr/new_core2/";
SolrUtil solr = new SolrUtil(url);
solr.addMap(m);
}
}
package com.test.util;
import java.util.List;
/**
* 查詢Solr返回的物件,物件型別為T的集合,還包含Solr中符合條件記錄總數
* @param <T>
*/
public class ResultInfo<T> {
private List<T> list = null;
private Long total = null;
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
}
分頁工具類
package com.test.util;
public class PageUtil {
private Integer page = 1;//預設顯示第一頁
private Integer rows = 4;//每頁顯示記錄數
private Long total = null;//總行數
private String url = null;//點選頁碼跳轉url
public PageUtil(String url,Integer page,Integer rows,Long total)
{
this.url = url;
this.page = page;
this.rows = rows;
this.total = total;
}
public String toHtml()
{
StringBuffer sb = new StringBuffer();
//計算總頁數
int pages = 0;
if(total % rows == 0)
pages = total.intValue() / rows;
else
pages = (total.intValue() / rows) + 1;
sb.append("<div id='pagediv'>\r\n");
String firstUrl = null;
if(url.indexOf("?")>0)
firstUrl = url + "&page=1&rows="+rows;
else
firstUrl = url + "?page=1&rows="+rows;
sb.append("<a href='"+firstUrl+"'>首頁</a>\r\n");
String backUrl = null;
if(url.indexOf("?")>0)
backUrl = url + "&page="+(page==1?1:(page-1))+"&rows="+rows;
else
backUrl = url + "?page="+(page==1?1:(page-1))+"&rows="+rows;
sb.append("<a href='"+backUrl+"'>上一頁</a>\r\n");
for(int i=1;i<=pages;i++)
{
String pageUrl = null;
if(url.indexOf("?")>0)
pageUrl = url + "&page="+i+"&rows="+rows;
else
pageUrl = url + "?page="+i+"&rows="+rows;
if(i == page)
sb.append("<a href='"+pageUrl+"'><b><font color='red'>"+i+"</font></b></a>\r\n");
else
sb.append("<a href='"+pageUrl+"'>"+i+"</a>\r\n");
}
String nextUrl = null;
if(url.indexOf("?")>0)
nextUrl = url + "&page="+(page==pages?pages:(page+1))+"&rows="+rows;
else
nextUrl = url + "?page="+(page==pages?pages:(page+1))+"&rows="+rows;
sb.append("<a href='"+nextUrl+"'>下一頁</a>\r\n");
String lastUrl = null;
if(url.indexOf("?")>0)
lastUrl = url + "&page="+pages+"&rows="+rows;
else
lastUrl = url + "?page="+pages+"&rows="+rows;
sb.append("<a href='"+lastUrl+"'>尾頁</a>\r\n");
sb.append(" 第"+p