經過追蹤程式碼及上網搜尋相關資料後,找到了bug的產生原因,特別在這邊做個紀錄並提供一個個人的解決方法。
bug發生原因:
JForum使用了Lucene來實做文章(Post)檢索搜尋功能,Lucene的實做是沒有問題的,在search時選擇以date來做ASC及DESC排序時,Lucene的確是返回正確排序的post_id列表,但是JForum在用它自己寫的Sql語法去找對應這些post_id的Post detail詳細資料時,使用了IN [post_id]的寫法,造成了找出Post detail後,排序又變成以主鍵排序的順序了。
我們先來看一下出問題的地方,首先是
GenericLuceneDAO.java的getPostData(int[] postIds)方法,輸入參數postIds是Lucene返回的以正確排序的post_id,但在要取得對應的Post detail時,使用了SearchModel.getPostsDataForLucene這個Sql語法,這個語法寫在generic_queries.sql,內容為
SearchModel.getPostsDataForLucene = SELECT p.post_id, p.forum_id, p.topic_id, p.user_id, u.username, p.enable_bbcode, p.enable_smilies, p.post_time, pt.post_subject, pt.post_text, t.topic_title \ FROM jforum_posts p, jforum_posts_text pt, jforum_users u, jforum_topics t \ WHERE p.post_id IN (:posts:) \ AND p.post_id = pt.post_id \ AND p.topic_id = t.topic_id \ AND p.user_id = u.user_Id
可以看到在Sql語法中用了 p.post_id IN (:posts:) 這樣的語句( :posts: 字串會被替換掉),所以不管post_ids怎麼排序,Sql語法回傳的結果還是照著主鍵排。
我的解法是修改GenericLuceneDAO.java的getPostsData()方法,在Sql語法查完post detail後,再將它照著postIds的順序重新丟到一個新的ArrayList回傳,這樣這個方法回傳的結果就可以被修正成正確的了。
原本有bug的程式
public List getPostsData(int[] postIds) { if (postIds.length == 0) { return new ArrayList(); } List l = new ArrayList(); PreparedStatement p = null; ResultSet rs = null; try { String sql = SystemGlobals.getSql("SearchModel.getPostsDataForLucene"); sql = sql.replaceAll(":posts:", this.buildInClause(postIds)); p = JForumExecutionContext.getConnection().prepareStatement(sql); rs = p.executeQuery(); while (rs.next()) { Post post = this.makePost(rs); post.setPostUsername(rs.getString("username")); l.add(post); } } catch (SQLException e) { throw new DatabaseException(e); } finally { DbUtils.close(rs, p); } return l; }
修正後的程式
public List getPostsData(int[] postIds) { if (postIds.length == 0) { return new ArrayList(); } List l = new ArrayList(); Post[] posts_orderByLucene = new Post[postIds.length]; PreparedStatement p = null; ResultSet rs = null; try { String sql = SystemGlobals.getSql("SearchModel.getPostsDataForLucene"); sql = sql.replaceAll(":posts:", this.buildInClause(postIds)); p = JForumExecutionContext.getConnection().prepareStatement(sql); rs = p.executeQuery(); while (rs.next()) { Post post = this.makePost(rs); post.setPostUsername(rs.getString("username")); for (int i = 0; i< postIds.length ; i++){ if (postIds[i] == post.getId()){ posts_orderByLucene[i] = post; break; } } } l = Arrays.asList(posts_orderByLucene); } catch (SQLException e) { throw new DatabaseException(e); } finally { DbUtils.close(rs, p); } return l; }
參考資料: