海鸥航际

JAVA站
posts - 11, comments - 53, trackbacks - 1, articles - 102

struts-menu+ibatis+少量的代码=通用的自定义菜单和动态加载的树

前言:
知识准备:首先你需要懂一些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/,源码下载

 


只有注册用户登录后才能发表评论。


网站导航: