struts-menu+ibatis+少量的代碼=通用的自定義菜單和動(dòng)態(tài)加載的樹(shù)
前言:
知識(shí)準(zhǔn)備:首先你需要懂一些struts的基本知識(shí),會(huì)用struts-menu,并理解站長(zhǎng)對(duì)struts-menu的分析那篇文章,還要知道ibatis的基本知識(shí),如果不懂,請(qǐng)去google或者站長(zhǎng)的論壇里找相關(guān)的文章。
樹(shù)形結(jié)構(gòu)在實(shí)際開(kāi)發(fā)中很常用,但是樹(shù)形結(jié)構(gòu)的開(kāi)發(fā)往往也是難題,尤其是在顯示這一條上,很難做到通用。通常有兩種典型的樹(shù)型結(jié)構(gòu)。一種是論壇的帖子,其結(jié)構(gòu)往往通過(guò)父子ID號(hào)相連,數(shù)據(jù)在一張表里。一種是級(jí)別,比如論壇中的Category->Forum->Thread這種結(jié)構(gòu),數(shù)據(jù)放在不同的表里。因?yàn)檎搲『冒诉@兩種結(jié)構(gòu)。因此。我們就能Jive的表結(jié)構(gòu)來(lái)做這個(gè)例子。首先我們通過(guò)RsMetaDataTest來(lái)掃描數(shù)據(jù)庫(kù),得到需要的XML配置文件。拿一個(gè)xml為例,解釋一下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sql-map
PUBLIC "-//iBATIS.com//DTD SQL Map 1.0//EN"
"<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>
可見(jiàn)有一個(gè)名叫findjivecategoryDao,這是一個(gè)典型的動(dòng)態(tài)查詢。返回對(duì)象是HashMap。其中$listfield$表示動(dòng)態(tài)讀取的字段。可以這么說(shuō),通過(guò)這個(gè)查詢。有關(guān)這樣表的任何方式的查詢都已經(jīng)解決了。由于這個(gè)演示只用到四張表,因些我們?cè)趕ql-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類(lèi),這個(gè)類(lèi)是一個(gè)通用的定義,其主要屬性如下。可以通過(guò)它建立一個(gè)四張表的樹(shù)形關(guān)系。
//sql Map的名稱
private String sqlMapName;
//調(diào)用的查詢名稱
private String SqlName;
//子菜單的名稱
private String submenuName; //對(duì)應(yīng)字段,其中key為主表的字段,value是從表的字段。
private HashMap keymap;
//菜單的名稱
private String MenuName;
//標(biāo)題
private String Title;
//標(biāo)題字段
private String TitleField;
//需要讀取的字段
private String listField;
//是否需要顯示
private boolean needShow=true;
然后建立一個(gè)XML的文件(此處簡(jiǎn)化了它的功能,就是把上面這個(gè)類(lèi)序列化了一下)。把它放在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>大分類(lèi)</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>子分類(lèi)</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>
關(guān)聯(lián)關(guān)系是category表通過(guò)CATEGORYID與forum關(guān)聯(lián),forum通過(guò)FORUMID與thread關(guān)聯(lián),thread是一張?zhí)厥獾谋怼K鼘⒉伙@示在樹(shù)中,只是一個(gè)過(guò)渡關(guān)聯(lián),用于讀出新建的文章。
thread通過(guò)FORUMID、FORUMID、ROOTMESSAGEID與message表關(guān)聯(lián)(FORUMID、FORUMID、MESSAGEID)。而message表是一個(gè)自關(guān)聯(lián)的表。MESSAGEID與PARENTMESSAGEID關(guān)聯(lián)建立父子關(guān)系。
然后我們建立一個(gè)session類(lèi)作為主要類(lèi)
public class TreeDemoSession {
//通過(guò)名稱和參數(shù)來(lái)得到樹(shù)
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;
}
/**一個(gè)典型的遞歸函數(shù)。用以組織樹(shù)。
* @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,表示需要讀取哪幾個(gè)字段
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();
//建立當(dāng)前節(jié)點(diǎn)
MenuComponent submenu = new MenuComponent();
submenu.setName(menu.getName() + String.valueOf(namei++));
submenu.setTitle(
String.valueOf(element.get(menudefine.getTitleField())));
//如果不需要顯示,則使用父節(jié)點(diǎn)作為當(dāng)前節(jié)點(diǎn)
if (!menudefine.isNeedShow())
submenu = menu;
//如果有子菜單,則遞歸調(diào)用。
if (menudefine.getSubmenuName() != null) {
submenu =
submenuAdd(
submenu,
getSubMenuInfo(menudefine, element),
menuMap,
menudefine.getSubmenuName());
}
//將當(dāng)前節(jié)點(diǎn)放到樹(shù)中。(如果不需要顯示就不用放)
if (menudefine.isNeedShow())
menu.addMenuComponent(submenu);
}
return menu;
} catch (DaoException e) {
throw e;
} catch (Exception e) {
throw e;
}
}
/**將父菜單的關(guān)鍵字段的值作為參數(shù)給子菜單
* @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;
}
}
三個(gè)函數(shù),非常簡(jiǎn)單,主函數(shù)讀取配置文件的內(nèi)容。一個(gè)遞歸函數(shù)用來(lái)建立樹(shù)形結(jié)構(gòu)。這棵樹(shù)只有兩個(gè)屬性被設(shè)置。一個(gè)是名字和標(biāo)題。其中標(biāo)題采用從數(shù)據(jù)庫(kù)里讀出的字段。名字則采用流水號(hào)。讀取數(shù)據(jù)庫(kù)只有一句,其中map是參數(shù)的一個(gè)列表。后面是sql的名字。
List list = DaoCommon.findbyName(map, menudefine.getSqlName());
而真正的實(shí)現(xiàn)代碼也非常簡(jiǎn)單
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);
}
}
下面我們來(lái)做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"));
}
這個(gè)函數(shù)也非常簡(jiǎn)單,就是把從request傳來(lái)的內(nèi)容生成一個(gè)Map對(duì)象。然后調(diào)用session,將返回結(jié)果以com.ewuxi.champion.menu為名字保存到request中去。
最后我們需要生成一個(gè)自定義的taglib。實(shí)際上很簡(jiǎn)單。只是因?yàn)閟truts-menu自身的taglib是寫(xiě)死了,我們不能利用,不過(guò)只要改一個(gè)地方就可以了,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了。然后需要建立一個(gè)JSP文件。我們把xtree.jsp借用過(guò)來(lái)。唯一需要改的就是<cp:displayMenu name="com.ewuxi.champion.menu"/>,當(dāng)然還有幾個(gè)link的路徑。因?yàn)榇颂幱胻reeDemo來(lái)所以就是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">大分類(lèi)列表</a>
</p>
<p><a href="demo.do?type=demo&menuName=forum" target="_blank">子分類(lèi)列表</a> </p>
<p><a href="demo.do?type=demo&menuName=forum&FORUMID=1" target="_blank">只看java分類(lèi)</a> </p>
<p><a href="demo.do?type=demo&menuName=thread" target="_blank">所有文章</a> </p>
上面是幾種不同的參數(shù)。主要的差別是menuName不同。然后也可以加數(shù)據(jù)庫(kù)需要的參數(shù),比如java分類(lèi)的forumId=1。就在參數(shù)中加FORUMID=1,注意大小寫(xiě)要跟XML中的動(dòng)態(tài)參數(shù)相同,此處全是大寫(xiě)。
在線演示看這樣http://demo.ewuxi.com:8000/treejivedemo/,源碼下載