前言:
知識準備:首先你需要懂一些struts的基本知識,會用struts-menu,并理解站長對struts-menu的分析那篇文章,還要知道ibatis的基本知識,如果不懂,請去google或者站長的論壇里找相關的文章。
樹形結構在實際開發中很常用,但是樹形結構的開發往往也是難題,尤其是在顯示這一條上,很難做到通用。通常有兩種典型的樹型結構。一種是論壇的帖子,其結構往往通過父子ID號相連,數據在一張表里。一種是級別,比如論壇中的Category->Forum->Thread這種結構,數據放在不同的表里。因為論壇恰好包含了這兩種結構。因此。我們就能Jive的表結構來做這個例子。首先我們通過RsMetaDataTest來掃描數據庫,得到需要的XML配置文件。拿一個xml為例,解釋一下
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sql-map PUBLIC "-//iBATIS.com//DTD SQL Map 1.0//EN" "http://www.ibatis.com/dtd/sql-map.dtd"> <sql-map name="jivecategory"> <!-- ============================================= mapped-statement find ============================================= --> <dynamic-mapped-statement name="findjivecategoryDao" result-class="java.util.HashMap"> select $listfield$ from JIVECATEGORY <dynamic prepend="where"> <isPropertyAvailable prepend="and" property="CATEGORYID" > <isNotNull prepend="" property="CATEGORYID" > CATEGORYID=#CATEGORYID# </isNotNull> </isPropertyAvailable> <isPropertyAvailable prepend="and" property="NAME" > <isNotNull prepend="" property="NAME" > NAME=#NAME# </isNotNull> </isPropertyAvailable> <isPropertyAvailable prepend="and" property="DESCRIPTION" > <isNotNull prepend="" property="DESCRIPTION" > DESCRIPTION=#DESCRIPTION# </isNotNull> </isPropertyAvailable> <isPropertyAvailable prepend="and" property="CREATIONDATE" > <isNotNull prepend="" property="CREATIONDATE" > CREATIONDATE=#CREATIONDATE# </isNotNull> </isPropertyAvailable> <isPropertyAvailable prepend="and" property="MODIFIEDDATE" > <isNotNull prepend="" property="MODIFIEDDATE" > MODIFIEDDATE=#MODIFIEDDATE# </isNotNull> </isPropertyAvailable> <isPropertyAvailable prepend="and" property="LFT" > <isNotNull prepend="" property="LFT" > LFT=#LFT# </isNotNull> </isPropertyAvailable> <isPropertyAvailable prepend="and" property="RGT" > <isNotNull prepend="" property="RGT" > RGT=#RGT# </isNotNull> </isPropertyAvailable> </dynamic> </dynamic-mapped-statement>
</sql-map> |
可見有一個名叫findjivecategoryDao,這是一個典型的動態查詢。返回對象是HashMap。其中$listfield$表示動態讀取的字段。可以這么說,通過這個查詢。有關這樣表的任何方式的查詢都已經解決了。由于這個演示只用到四張表,因些我們在sql-map-config-storedb.xml也只加載了四張表的定義。
<sql-map resource="sqlmap/jivecategory.xml" /> <sql-map resource="sqlmap/jiveforum.xml" /> <sql-map resource="sqlmap/jivethread.xml" /> <sql-map resource="sqlmap/jivemessage.xml" /> |
然后定義MenuDefine類,這個類是一個通用的定義,其主要屬性如下。可以通過它建立一個四張表的樹形關系。
//sql Map的名稱 private String sqlMapName; //調用的查詢名稱 private String SqlName; //子菜單的名稱 private String submenuName;
//對應字段,其中key為主表的字段,value是從表的字段。 private HashMap keymap; //菜單的名稱 private String MenuName; //標題 private String Title; //標題字段 private String TitleField; //需要讀取的字段 private String listField; //是否需要顯示 private boolean needShow=true;
|
然后建立一個XML的文件(此處簡化了它的功能,就是把上面這個類序列化了一下)。把它放在classes目錄下。
<?xml version="1.0" encoding="UTF-8"?> <java version="1.4.2_03" class="java.beans.XMLDecoder"> <object class="java.util.HashMap"> <void method="put"> <string>message</string> <object class="com.ewuxi.champion.MenuDefine"> <void property="keymap"> <object class="java.util.HashMap"> <void method="put"> <string>MESSAGEID</string> <string>PARENTMESSAGEID</string> </void> </object> </void> <void property="listField"> <string>MESSAGEID,SUBJECT</string> </void> <void property="menuName"> <string>message</string> </void> <void property="sqlMapName"> <string>jivemessage</string> </void> <void property="sqlName"> <string>findjivemessageDao</string> </void> <void property="submenuName"> <string>message</string> </void> <void property="title"> <string>文章</string> </void> <void property="titleField"> <string>SUBJECT</string> </void> </object> </void> <void method="put"> <string>category</string> <object class="com.ewuxi.champion.MenuDefine"> <void property="keymap"> <object class="java.util.HashMap"> <void method="put"> <string>CATEGORYID</string> <string>CATEGORYID</string> </void> </object> </void> <void property="listField"> <string>CATEGORYID,NAME</string> </void> <void property="menuName"> <string>category</string> </void> <void property="sqlMapName"> <string>jivecategory</string> </void> <void property="sqlName"> <string>findjivecategoryDao</string> </void> <void property="submenuName"> <string>forum</string> </void> <void property="title"> <string>大分類</string> </void> <void property="titleField"> <string>NAME</string> </void> </object> </void> <void method="put"> <string>forum</string> <object class="com.ewuxi.champion.MenuDefine"> <void property="keymap"> <object class="java.util.HashMap"> <void method="put"> <string>FORUMID</string> <string>FORUMID</string> </void> </object> </void> <void property="listField"> <string>FORUMID,NAME</string> </void> <void property="menuName"> <string>forum</string> </void> <void property="sqlMapName"> <string>jiveforum</string> </void> <void property="sqlName"> <string>findjiveforumDao</string> </void> <void property="submenuName"> <string>thread</string> </void> <void property="title"> <string>子分類</string> </void> <void property="titleField"> <string>NAME</string> </void> </object> </void> <void method="put"> <string>thread</string> <object class="com.ewuxi.champion.MenuDefine"> <void property="keymap"> <object class="java.util.HashMap"> <void method="put"> <string>THREADID</string> <string>THREADID</string> </void> <void method="put"> <string>FORUMID</string> <string>FORUMID</string> </void> <void method="put"> <string>ROOTMESSAGEID</string> <string>MESSAGEID</string> </void> </object> </void> <void property="listField"> <string>THREADID,ROOTMESSAGEID</string> </void> <void property="menuName"> <string>thread</string> </void> <void property="needShow"> <boolean>false</boolean> </void> <void property="sqlMapName"> <string>jivethread</string> </void> <void property="sqlName"> <string>findjivethreadDao</string> </void> <void property="submenuName"> <string>message</string> </void> <void property="title"> <string>欄目</string> </void> <void property="titleField"> <string>ROOTMESSAGEID</string> </void> </object> </void> </object> </java>
|
關聯關系是category表通過CATEGORYID與forum關聯,forum通過FORUMID與thread關聯,thread是一張特殊的表。它將不顯示在樹中,只是一個過渡關聯,用于讀出新建的文章。
thread通過FORUMID、FORUMID、ROOTMESSAGEID與message表關聯(FORUMID、FORUMID、MESSAGEID)。而message表是一個自關聯的表。MESSAGEID與PARENTMESSAGEID關聯建立父子關系。
然后我們建立一個session類作為主要類
public class TreeDemoSession {
//通過名稱和參數來得到樹
public MenuComponent getMenu(String name, Map keys) throws Exception {
Map menuMap =
(Map) (new XmlUtils().read(Service.getPath() + "/menu.xml"));
MenuComponent menu = new MenuComponent();
if (menuMap.get(name) != null) {
MenuDefine rootMenudefine = (MenuDefine) menuMap.get(name);
menu.setTitle(rootMenudefine.getTitle());
menu.setName(rootMenudefine.getMenuName());
menu = submenuAdd(menu, keys, menuMap, name);
}
return menu;
}
/**一個典型的遞歸函數。用以組織樹。
* @param menu
* @param map
* @param menuMap
* @param menuName
* @return
* @throws DaoException
* @throws Exception
*/
private MenuComponent submenuAdd(
MenuComponent menu,
Map map,
final Map menuMap,
String menuName)
throws DaoException, Exception {
try {
//得到菜單定義
MenuDefine menudefine = (MenuDefine) menuMap.get(menuName);
//listfield,表示需要讀取哪幾個字段
map.put("listfield", menudefine.getListField());
//查詢,返回列表。
List list = DaoCommon.findbyName(map, menudefine.getSqlName());
int namei = 0;
for (Iterator iter = list.iterator(); iter.hasNext();) {
Map element = (Map) iter.next();
//建立當前節點
MenuComponent submenu = new MenuComponent();
submenu.setName(menu.getName() + String.valueOf(namei++));
submenu.setTitle(
String.valueOf(element.get(menudefine.getTitleField())));
//如果不需要顯示,則使用父節點作為當前節點
if (!menudefine.isNeedShow())
submenu = menu;
//如果有子菜單,則遞歸調用。
if (menudefine.getSubmenuName() != null) {
submenu =
submenuAdd(
submenu,
getSubMenuInfo(menudefine, element),
menuMap,
menudefine.getSubmenuName());
}
//將當前節點放到樹中。(如果不需要顯示就不用放)
if (menudefine.isNeedShow())
menu.addMenuComponent(submenu);
}
return menu;
} catch (DaoException e) {
throw e;
} catch (Exception e) {
throw e;
}
}
/**將父菜單的關鍵字段的值作為參數給子菜單
* @param menudefine
* @param element
* @return
*/
private HashMap getSubMenuInfo(MenuDefine menudefine, Map element) {
HashMap map = new HashMap();
for (Iterator iter = menudefine.getKeymap().keySet().iterator();
iter.hasNext();
) {
String key = (String) iter.next();
map.put(menudefine.getKeymap().get(key), element.get(key));
}
return map;
}
} |
三個函數,非常簡單,主函數讀取配置文件的內容。一個遞歸函數用來建立樹形結構。這棵樹只有兩個屬性被設置。一個是名字和標題。其中標題采用從數據庫里讀出的字段。名字則采用流水號。讀取數據庫只有一句,其中map是參數的一個列表。后面是sql的名字。
List list = DaoCommon.findbyName(map, menudefine.getSqlName()); |
而真正的實現代碼也非常簡單
public static List findbyName(Object vo,String name) throws DaoException { try { SqlMap sqlMap = DaoCommon.getSqlMap(DaoCommon.getDefautDao()); return (List) sqlMap.executeQueryForList(name, vo); } catch (Exception e) { throw new DaoException(e); } } |
下面我們來做Action的工作
public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
Service.initSet(); DaoCommon.startTransaction();
HashMap parMap = new HashMap(); Enumeration enumeration = request.getParameterNames(); while (enumeration.hasMoreElements()) { String element = (String) enumeration.nextElement(); parMap.put(element, request.getParameter(element)); } TreeDemoSession session=new TreeDemoSession(); request.setAttribute("com.ewuxi.champion.menu",session.getMenu(request.getParameter("menuName"),parMap));
DaoCommon.rollBack();
return mapping.findForward(request.getParameter("type")); }
|
這個函數也非常簡單,就是把從request傳來的內容生成一個Map對象。然后調用session,將返回結果以com.ewuxi.champion.menu為名字保存到request中去。
最后我們需要生成一個自定義的taglib。實際上很簡單。只是因為struts-menu自身的taglib是寫死了,我們不能利用,不過只要改一個地方就可以了,copy UseMenuDisplayerTag到我們的目錄下。
MenuRepository repository = (MenuRepository) pageContext.getServletContext().getAttribute(MenuRepository.MENU_REPOSITORY_KEY);
if (repository == null) { throw new JspException("Could not obtain the menu repository"); }
MenuComponent menu = repository.getMenu(this.name); |
找到上面這一段,改成
MenuComponent menu = (MenuComponent) pageContext.findAttribute(this.name);
|
就OK了。然后需要建立一個JSP文件。我們把xtree.jsp借用過來。唯一需要改的就是<cp:displayMenu name="com.ewuxi.champion.menu"/>,當然還有幾個link的路徑。因為此處用treeDemo來所以就是href="/treeDemo/styles/xtree.css"
<head> <title>XTree (with Velocity) Example</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" media="screen" href="/treeDemo/styles/global.css" /> <link rel="stylesheet" type="text/css" media="screen" href="/treeDemo/styles/xtree.css" />
<script type="text/javascript" src="/treeDemo/scripts/xtree.js"></script>
</head> <body>
<div class="container"> Simple menu with Velocity:<br /> <script type="text/javascript"> <menu:useMenuDisplayer name="Velocity" config="/templates/xtree.html" bundle="org.apache.struts.action.MESSAGE"> if (document.getElementById) { <cp:displayMenu name="com.ewuxi.champion.menu"/> } else { var msg = "Your browser does not support document.getElementById().\n"; msg += "You must use a modern browser for this menu."; alert(msg); }
</menu:useMenuDisplayer> </script> </div>
|
下面就可以自由的看效果了。
<p><a href="demo.do?type=demo&menuName=category" target="_blank">大分類列表</a> </p> <p><a href="demo.do?type=demo&menuName=forum" target="_blank">子分類列表</a> </p> <p><a href="demo.do?type=demo&menuName=forum&FORUMID=1" target="_blank">只看java分類</a> </p> <p><a href="demo.do?type=demo&menuName=thread" target="_blank">所有文章</a> </p> |
上面是幾種不同的參數。主要的差別是menuName不同。然后也可以加數據庫需要的參數,比如java分類的forumId=1。就在參數中加FORUMID=1,注意大小寫要跟XML中的動態參數相同,此處全是大寫。
在線演示看這樣http://demo.ewuxi.com:8000/treejivedemo/,
源碼下載
http://champion.ewuxi.com/old/opensource/struts-new/treeDemo.rar