|
最近利用晚上下班还有周末的时间自己捣腾的封装了一个我自己的搜索引擎(基于lucene和solr).现在将大概的思路给写出来,分享下:
1.首先是索引对象,也可以说是查询的VO对象.封装了几个常用字段(如:主键,所属者ID,所属者姓名,进入详情页面的link,创建时间等),其他各个模块的字段(如:标题,内容,邮箱等)
SearchBean.java
字段的代码如下:
1/** *//********以下 共有字段***********/ 2 /** *//** 3 * 检索的内容 4 */ 5 protected String keyword; 6 /** *//** 7 * 拥有者ID 8 */ 9 protected String owerId; 10 /** *//** 11 * 拥有者name 12 */ 13 protected String owerName; 14 /** *//** 15 * 检索对象的唯一标识位的值 16 */ 17 protected String id; 18 /** *//** 19 * 检索出对象后进入详情页面的链接 20 */ 21 protected String link; 22 /** *//** 23 * 创建时间 24 */ 25 protected String createDate; 26 /** *//** 27 * index类型 28 */ 29 protected String indexType; 30 31 //setter,getter方法省略 32/** *//********以上 共有字段***********/ 33 34/** *//*************以下 其他字段************/ 35 /** *//** 36 * 需要检索出来的字段及其值的对应map 37 */ 38 private Map<String, String> searchValues; 39 40 /** *//** 41 * 值对象 42 */ 43 private Object object; 44 45 /** *//** 46 * 获取检索出来的doIndexFields字段的值 47 * 48 * @return 49 */ 50 public Map<String, String> getSearchValues() { 51 return searchValues; 52 } 53 54 /** *//** 55 * 设置检索出来的doIndexFields字段的值 56 * 57 * @param searchValues 58 */ 59 public void setSearchValues(Map<String, String> searchValues) { 60 this.searchValues = searchValues; 61 } 62 /** *//********************以上 其他字段*******************/<span></span>
抽象方法代码如下:
1/** *//*****************以下 抽象方法******************/ 2 /** *//** 3 * 返回需要进行检索的字段 4 * 5 * @return 6 */ 7 public abstract String[] getDoSearchFields(); 8 9 /** *//** 10 * 进行索引的字段 11 * 12 * @return 13 */ 14 public abstract String[] getDoIndexFields(); 15 16 /** *//** 17 * 初始化searchBean中的公共字段(每个对象都必须创建的索引字段) 18 * @throws Exception 19 */ 20 public abstract void initPublicFields() throws Exception; 21 22 /** *//** 23 * 返回索引类型 24 * 25 * @return 26 */ 27 public abstract String getIndexType(); 28 /** *//*****************以上 抽象方法********************/
共有的方法:
1/** *//*******************以下 公共方法**********************/ 2 /** *//** 3 * 获取需要创建索引字段的键值对map 4 * 5 * @return 6 */ 7 public Map<String, String> getIndexFieldValues() { 8 if(this.object == null){ 9 logger.warn("given object is null!"); 10 return Collections.emptyMap(); 11 } 12 13 String[] doIndexFields = this.getDoIndexFields(); 14 if(doIndexFields == null || doIndexFields.length < 1){ 15 logger.debug("given no doIndexFields!"); 16 return Collections.emptyMap(); 17 } 18 19 Map<String, String> extInfo = new HashMap<String, String>(); 20 for(String f : doIndexFields){ 21 String value = getValue(f, object); 22 extInfo.put(f, value); 23 } 24 25 return extInfo; 26 } 27 28 /** *//** 29 * 获取一个对象中的某个字段的值,结果转化成string类型 30 * 31 * @param field 字段名称 32 * @param obj 对象 33 * @return 34 */ 35 private String getValue(String field, Object obj){ 36 if(StringUtils.isEmpty(field)){ 37 logger.warn("field is empty!"); 38 return StringUtils.EMPTY; 39 } 40 41 String result = StringUtils.EMPTY; 42 try { 43 Object value = ObjectUtils.getFieldValue(object, field); 44 if (value == null) 45 result = StringUtils.EMPTY; 46 else if (value instanceof String) 47 result = (String) value; 48 else if (value instanceof Collections || value instanceof Map) 49 result = ToStringBuilder.reflectionToString(object); 50 else if (value instanceof Date) 51 result = DateUtils.formatDate((Date) value); 52 else 53 result = value.toString(); 54 55 } catch (IllegalAccessException e) { 56 logger.error("can not find a value for field '{}' in object class '{}'!", field, object.getClass()); 57 } 58 59 return result; 60 } 61 62 /** *//** 63 * you must use this method when you create the index, set what object you will to be created its index! 64 * 65 * @param object the object which you will want to be create index 66 */ 67 public void setObject(Object object){ 68 this.object = object; 69 } 70 71 /** *//** 72 * get what object you want to be created index! 73 * 74 * @return 75 */ 76 public Object getObject(){ 77 return this.object; 78 } 79 /** *//***************以上 公共方法*************/
2.现在有很多开源或者闭源的索引引擎可以用在项目上使用,所以我写了一个接口和一个抽取了一些公共方法的抽象类,只需要将你选择的搜索引擎的具体创建索引,检索等功能的实现代码写在一个继承上面这个抽象类的子类中,就可以随意的切换使用的目标引擎.贴上接口和抽象类
SearchEngine.java
1package com.message.base.search.engine; 2 3import com.message.base.pagination.PaginationSupport; 4import com.message.base.search.SearchBean; 5 6import java.util.List; 7 8/** *//** 9 * 索引引擎实现构建索引.删除索引.更新索引.检索等操作. 10 * 11 * @author sunhao(sunhao.java@gmail.com) 12 * @version V1.0 13 * @createTime 13-5-5 上午1:38 14 */ 15public interface SearchEngine { 16 17 /** *//** 18 * 创建索引(考虑线程安全) 19 * 20 * @param searchBeans 对象 21 * @throws Exception 22 */ 23 public void doIndex(List<SearchBean> searchBeans) throws Exception; 24 25 /** *//** 26 * 删除索引 27 * 28 * @param bean 对象 29 * @throws Exception 30 */ 31 public void deleteIndex(SearchBean bean) throws Exception; 32 33 /** *//** 34 * 删除索引(删除多个) 35 * 36 * @param beans 对象 37 * @throws Exception 38 */ 39 public void deleteIndexs(List<SearchBean> beans) throws Exception; 40 41 /** *//** 42 * 进行检索 43 * 44 * @param bean 检索对象(一般只需要放入值keyword,即用来检索的关键字) 45 * @param isHighlighter 是否高亮 46 * @param start 开始值 47 * @param num 偏移量 48 * @return 49 * @throws Exception 50 */ 51 public PaginationSupport doSearch(SearchBean bean, boolean isHighlighter, int start, int num) throws Exception; 52 53 /** *//** 54 * 进行多个检索对象的检索 55 * 56 * @param beans 多个检索对象(一般只需要放入值keyword,即用来检索的关键字) 57 * @param isHighlighter 是否高亮 58 * @param start 开始值 59 * @param num 偏移量 60 * @return 61 * @throws Exception 62 */ 63 public PaginationSupport doSearch(List<SearchBean> beans, boolean isHighlighter, int start, int num) throws Exception; 64 65 /** *//** 66 * 删除某个类型的所有索引(考虑线程安全) 67 * 68 * @param clazz 索引类型 69 * @throws Exception 70 */ 71 public void deleteIndexsByIndexType(Class<? extends SearchBean> clazz) throws Exception; 72 73 /** *//** 74 * 删除某个类型的所有索引(考虑线程安全) 75 * 76 * @param indexType 索引类型 77 * @throws Exception 78 */ 79 public void deleteIndexsByIndexType(String indexType) throws Exception; 80 81 /** *//** 82 * 删除所有的索引 83 * 84 * @throws Exception 85 */ 86 public void deleteAllIndexs() throws Exception; 87 88 /** *//** 89 * 更新索引 90 * 91 * @param searchBean 需要更新的bean 92 * @throws Exception 93 */ 94 public void updateIndex(SearchBean searchBean) throws Exception; 95 96 /** *//** 97 * 批量更新索引 98 * 99 * @param searchBeans 需要更新的beans 100 * @throws Exception 101 */ 102 public void updateIndexs(List<SearchBean> searchBeans) throws Exception; 103}
AbstractSearchEngine.java
1package com.message.base.search.engine; 2 3import com.message.base.pagination.PaginationSupport; 4import com.message.base.pagination.PaginationUtils; 5import com.message.base.search.SearchBean; 6import com.message.base.utils.StringUtils; 7import org.slf4j.Logger; 8import org.slf4j.LoggerFactory; 9 10import java.util.Collections; 11 12/** *//** 13 * 搜索引擎的公用方法. 14 * 15 * @author sunhao(sunhao.java@gmail.com) 16 * @version V1.0 17 * @createTime 13-5-8 下午10:53 18 */ 19public abstract class AbstractSearchEngine implements SearchEngine { 20 private static final Logger logger = LoggerFactory.getLogger(AbstractSearchEngine.class); 21 22 /** *//** 23 * 进行高亮处理时,html片段的前缀 24 */ 25 private String htmlPrefix = "<p>"; 26 /** *//** 27 * 进行高亮处理时,html片段的后缀 28 */ 29 private String htmlSuffix = "</p>"; 30 31 public String getHtmlPrefix() { 32 return htmlPrefix; 33 } 34 35 public void setHtmlPrefix(String htmlPrefix) { 36 this.htmlPrefix = htmlPrefix; 37 } 38 39 public String getHtmlSuffix() { 40 return htmlSuffix; 41 } 42 43 public void setHtmlSuffix(String htmlSuffix) { 44 this.htmlSuffix = htmlSuffix; 45 } 46 47 public PaginationSupport doSearch(SearchBean bean, boolean isHighlighter, int start, int num) throws Exception { 48 if(bean == null){ 49 logger.debug("given search bean is empty!"); 50 return PaginationUtils.getNullPagination(); 51 } 52 53 return doSearch(Collections.singletonList(bean), isHighlighter, start, num); 54 } 55 56 /** *//** 57 * 获取index类型 58 * 59 * @param bean 60 * @return 61 */ 62 public String getIndexType(SearchBean bean){ 63 return StringUtils.isNotEmpty(bean.getIndexType()) ? bean.getIndexType() : bean.getClass().getSimpleName(); 64 } 65}
3.开始谈谈lucene
贴上代码先:
LuceneSearchEngine.java
1package com.message.base.search.engine; 2 3import com.message.base.pagination.PaginationSupport; 4import com.message.base.pagination.PaginationUtils; 5import com.message.base.search.SearchBean; 6import com.message.base.search.SearchInitException; 7import com.message.base.utils.StringUtils; 8import org.apache.lucene.analysis.Analyzer; 9import org.apache.lucene.analysis.SimpleAnalyzer; 10import org.apache.lucene.document.Document; 11import org.apache.lucene.document.Field; 12import org.apache.lucene.index.IndexReader; 13import org.apache.lucene.index.IndexWriter; 14import org.apache.lucene.index.Term; 15import org.apache.lucene.queryParser.MultiFieldQueryParser; 16import org.apache.lucene.search.BooleanClause; 17import org.apache.lucene.search.IndexSearcher; 18import org.apache.lucene.search.Query; 19import org.apache.lucene.search.ScoreDoc; 20import org.apache.lucene.search.highlight.Highlighter; 21import org.apache.lucene.search.highlight.QueryScorer; 22import org.apache.lucene.search.highlight.SimpleHTMLFormatter; 23import org.apache.lucene.store.Directory; 24import org.apache.lucene.store.FSDirectory; 25import org.apache.lucene.util.Version; 26import org.slf4j.Logger; 27import org.slf4j.LoggerFactory; 28import org.springframework.beans.BeanUtils; 29 30import java.io.File; 31import java.io.IOException; 32import java.util.*; 33 34/** *//** 35 * 基于lucene实现的索引引擎. 36 * 37 * @author sunhao(sunhao.java@gmail.com) 38 * @version V1.0 39 * @createTime 13-5-5 上午10:38 40 */ 41public class LuceneSearchEngine extends AbstractSearchEngine { 42 private static final Logger logger = LoggerFactory.getLogger(LuceneSearchEngine.class); 43 /** *//** 44 * 索引存放路径 45 */ 46 private String indexPath; 47 /** *//** 48 * 分词器 49 */ 50 private Analyzer analyzer = new SimpleAnalyzer(); 51 52 public synchronized void doIndex(List<SearchBean> searchBeans) throws Exception { 53 this.createOrUpdateIndex(searchBeans, true); 54 } 55 56 public synchronized void deleteIndex(SearchBean bean) throws Exception { 57 if(bean == null){ 58 logger.warn("Get search bean is empty!"); 59 return; 60 } 61 62 String id = bean.getId(); 63 64 if(StringUtils.isEmpty(id)){ 65 logger.warn("get id and id value from bean is empty!"); 66 return; 67 } 68 String indexType = getIndexType(bean); 69 Directory indexDir = this.getIndexDir(indexType); 70 IndexWriter writer = this.getWriter(indexDir); 71 72 writer.deleteDocuments(new Term("pkId", id)); 73 writer.commit(); 74 this.destroy(writer); 75 } 76 77 public synchronized void deleteIndexs(List<SearchBean> beans) throws Exception { 78 if(beans == null){ 79 logger.warn("Get beans is empty!"); 80 return; 81 } 82 83 for(SearchBean bean : beans){ 84 this.deleteIndex(bean); 85 } 86 } 87 88 public PaginationSupport doSearch(List<SearchBean> beans, boolean isHighlighter, int start, int num) throws Exception { 89 if(beans == null || beans.isEmpty()){ 90 logger.debug("given search beans is empty!"); 91 return PaginationUtils.getNullPagination(); 92 } 93 94 List queryResults = new ArrayList(); 95 int count = 0; 96 for(SearchBean bean : beans){ 97 String indexType = getIndexType(bean); 98 99 IndexReader reader = IndexReader.open(this.getIndexDir(indexType)); 100 101 List<String> fieldNames = new ArrayList<String>(); //查询的字段名 102 List<String> queryValue = new ArrayList<String>(); //待查询字段的值 103 List<BooleanClause.Occur> flags = new ArrayList<BooleanClause.Occur>(); 104 105 //要进行检索的字段 106 String[] doSearchFields = bean.getDoSearchFields(); 107 if(doSearchFields == null || doSearchFields.length == 0) 108 return PaginationUtils.getNullPagination(); 109 110 //默认字段 111 if(StringUtils.isNotEmpty(bean.getKeyword())){ 112 for(String field : doSearchFields){ 113 fieldNames.add(field); 114 queryValue.add(bean.getKeyword()); 115 flags.add(BooleanClause.Occur.SHOULD); 116 } 117 } 118 119 Query query = MultiFieldQueryParser.parse(Version.LUCENE_CURRENT, queryValue.toArray(new String[]{}), fieldNames.toArray(new String[]{}), 120 flags.toArray(new BooleanClause.Occur[]{}), analyzer); 121 122 logger.debug("make query string is '{}'!", query.toString()); 123 IndexSearcher searcher = new IndexSearcher(reader); 124 ScoreDoc[] scoreDocs = searcher.search(query, 1000000).scoreDocs; 125 126 //查询起始记录位置 127 int begin = (start == -1 && num == -1) ? 0 : start; 128 //查询终止记录位置 129 int end = (start == -1 && num == -1) ? scoreDocs.length : Math.min(begin + num, scoreDocs.length); 130 131 //高亮处理 132 Highlighter highlighter = null; 133 if(isHighlighter){ 134 SimpleHTMLFormatter formatter = new SimpleHTMLFormatter(this.getHtmlPrefix(), this.getHtmlSuffix()); 135 highlighter = new Highlighter(formatter, new QueryScorer(query)); 136 } 137 138 List<SearchBean> results = new ArrayList<SearchBean>(); 139 for (int i = begin; i < end; i++) { 140 SearchBean result = BeanUtils.instantiate(bean.getClass()); 141 142 int docID = scoreDocs[i].doc; 143 Document hitDoc = searcher.doc(docID); 144 145 result.setId(hitDoc.get("pkId")); 146 result.setLink(hitDoc.get("link")); 147 result.setOwerId(hitDoc.get("owerId")); 148 result.setOwerName(hitDoc.get("owerName")); 149 result.setCreateDate(hitDoc.get("createDate")); 150 result.setIndexType(indexType); 151 152 String keyword = StringUtils.EMPTY; 153 if(isHighlighter && highlighter != null) 154 keyword = highlighter.getBestFragment(analyzer, "keyword", hitDoc.get("keyword")); 155 156 if(StringUtils.isEmpty(keyword)) 157 keyword = hitDoc.get("keyword"); 158 159 result.setKeyword(keyword); 160 161 Map<String, String> extendValues = new HashMap<String, String>(); 162 for(String field : doSearchFields){ 163 String value = hitDoc.get(field); 164 if(isHighlighter && highlighter != null) 165 value = highlighter.getBestFragment(analyzer, field, hitDoc.get(field)); 166 167 if(StringUtils.isEmpty(value)) 168 value = hitDoc.get(field); 169 170 extendValues.put(field, value); 171 } 172 173 result.setSearchValues(extendValues); 174 175 results.add(result); 176 } 177 178 queryResults.addAll(results); 179 count += scoreDocs.length; 180 searcher.close(); 181 reader.close(); 182 } 183 184 PaginationSupport paginationSupport = PaginationUtils.makePagination(queryResults, count, num, start); 185 return paginationSupport; 186 } 187 188 public synchronized void deleteIndexsByIndexType(Class<? extends SearchBean> clazz) throws Exception { 189 String indexType = getIndexType(BeanUtils.instantiate(clazz)); 190 this.deleteIndexsByIndexType(indexType); 191 } 192 193 public synchronized void deleteIndexsByIndexType(String indexType) throws Exception { 194 //传入readOnly的参数,默认是只读的 195 IndexReader reader = IndexReader.open(this.getIndexDir(indexType), false); 196 int result = reader.deleteDocuments(new Term("indexType", indexType)); 197 reader.close(); 198 logger.debug("the rows of delete index is '{}'! index type is '{}'!", result, indexType); 199 } 200 201 public synchronized void deleteAllIndexs() throws Exception { 202 File indexFolder = new File(this.indexPath); 203 if(indexFolder == null || !indexFolder.isDirectory()){ 204 //不存在或者不是文件夹 205 logger.debug("indexPath is not a folder! indexPath: '{}'!", indexPath); 206 return; 207 } 208 209 File[] children = indexFolder.listFiles(); 210 for(File child : children){ 211 if(child == null || !child.isDirectory()) continue; 212 213 String indexType = child.getName(); 214 logger.debug("Get indexType is '{}'!", indexType); 215 216 this.deleteIndexsByIndexType(indexType); 217 } 218 } 219 220 public void updateIndex(SearchBean searchBean) throws Exception { 221 this.updateIndexs(Collections.singletonList(searchBean)); 222 } 223 224 public void updateIndexs(List<SearchBean> searchBeans) throws Exception { 225 this.createOrUpdateIndex(searchBeans, false); 226 } 227 228 /** *//** 229 * 创建或者更新索引 230 * 231 * @param searchBeans 需要创建或者更新的对象 232 * @param isCreate 是否是创建索引;true创建索引,false更新索引 233 * @throws Exception 234 */ 235 private synchronized void createOrUpdateIndex(List<SearchBean> searchBeans, boolean isCreate) throws Exception { 236 if(searchBeans == null || searchBeans.isEmpty()){ 237 logger.debug("do no index!"); 238 return; 239 } 240 241 Directory indexDir = null; 242 IndexWriter writer = null; 243 for(Iterator<SearchBean> it = searchBeans.iterator(); it.hasNext(); ){ 244 SearchBean sb = it.next(); 245 String indexType = getIndexType(sb); 246 if(sb == null){ 247 logger.debug("give SearchBean is null!"); 248 return; 249 } 250 boolean anotherSearchBean = indexDir != null && !indexType.equals(((FSDirectory) indexDir).getFile().getName()); 251 if(indexDir == null || anotherSearchBean){ 252 indexDir = this.getIndexDir(indexType); 253 } 254 if(writer == null || anotherSearchBean){ 255 this.destroy(writer); 256 writer = this.getWriter(indexDir); 257 } 258 259 Document doc = new Document(); 260 261 //初始化一些字段 262 sb.initPublicFields(); 263 String id = sb.getId(); 264 265 //主键的索引,不作为搜索字段,并且也不进行分词 266 Field idField = new Field("pkId", id, Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); 267 doc.add(idField); 268 269 logger.debug("create id index for '{}', value is '{}'! index is '{}'!", new Object[]{"pkId", id, idField}); 270 271 String owerId = sb.getOwerId(); 272 if(StringUtils.isEmpty(owerId)){ 273 throw new SearchInitException("you must give a owerId"); 274 } 275 Field owerId_ = new Field("owerId", owerId, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); 276 doc.add(owerId_); 277 278 String owerName = sb.getOwerName(); 279 if(StringUtils.isEmpty(owerName)){ 280 throw new SearchInitException("you must give a owerName"); 281 } 282 Field owerName_ = new Field("owerName", owerName, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); 283 doc.add(owerName_); 284 285 String link = sb.getLink(); 286 if(StringUtils.isEmpty(link)){ 287 throw new SearchInitException("you must give a link"); 288 } 289 Field link_ = new Field("link", link, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); 290 doc.add(link_); 291 292 String keyword = sb.getKeyword(); 293 if(StringUtils.isEmpty(keyword)){ 294 throw new SearchInitException("you must give a keyword"); 295 } 296 Field keyword_ = new Field("keyword", keyword, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); 297 doc.add(keyword_); 298 299 String createDate = sb.getCreateDate(); 300 if(StringUtils.isEmpty(createDate)){ 301 throw new SearchInitException("you must give a createDate"); 302 } 303 Field createDate_ = new Field("createDate", createDate, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); 304 doc.add(createDate_); 305 306 //索引类型字段 307 Field indexType_ = new Field("indexType", indexType, Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); 308 doc.add(indexType_); 309 310 //进行索引的字段 311 String[] doIndexFields = sb.getDoIndexFields(); 312 Map<String, String> indexFieldValues = sb.getIndexFieldValues(); 313 if(doIndexFields != null && doIndexFields.length > 0){ 314 for(String field : doIndexFields){ 315 Field extInfoField = new Field(field, indexFieldValues.get(field), Field.Store.YES, Field.Index.ANALYZED, 316 Field.TermVector.WITH_POSITIONS_OFFSETS); 317 318 doc.add(extInfoField); 319 } 320 } 321 322 if(isCreate) 323 writer.addDocument(doc); 324 else 325 writer.updateDocument(new Term("pkId", sb.getId()), doc); 326 327 writer.optimize(); 328 } 329 330 this.destroy(writer); 331 logger.debug("create or update index success!"); 332 } 333 334 public Directory getIndexDir(String suffix) throws Exception { 335 return FSDirectory.open(new File(indexPath + File.separator + suffix)); 336 } 337 338 public IndexWriter getWriter(Directory indexDir) throws IOException { 339 return new IndexWriter(indexDir, analyzer, IndexWriter.MaxFieldLength.UNLIMITED); 340 } 341 342 public void destroy(IndexWriter writer) throws Exception { 343 if(writer != null) 344 writer.close(); 345 } 346 347 public void setIndexPath(String indexPath) { 348 this.indexPath = indexPath; 349 } 350 351 public void setAnalyzer(Analyzer analyzer) { 352 this.analyzer = analyzer; 353 } 354 355}
关于如何使用lucene这里我就不再重复了,网上一大堆这方面的资料,有什么不懂得可以谷歌一下.下面谈谈我的一些想法,有不对的,尽管拍砖,来吧:
....
也没啥好说的,等想到再补充吧,就是觉得有一点比较操蛋,窝心:
1FSDirectory.open(new File("D:\index\xxx"/** *//**一个不存在的目录,或者是一个不是索引的目录**/));
使用上面一段取到索引Directory的时候,如果目录不存在会报错.可以有人认为这没什么,就是应该,我封装的这代码里面,确实对这玩意有要求的.
上面的SearchBean.java中有一个字段叫indexType,当没有指定的时候,默认为类名,如MessageSerarchBean,如果我没有对Message进行创建索引操作,在检索的时候就报错了.我得想想用什么方法给解决掉.
|