在網站開發的時候我們會對網站的欄目進行分類,一個欄目可以有多個子分類,一個子分類又可以有分裂,例如:新聞欄目下有每日早報和每日晚報兩個欄目,其中每日早報下面又分為上海早報,北京早報,杭州早報,下面是京東首頁的分類圖。
數據庫設計
我們在設計數據庫的時候僅僅使用一張表就可以把上面的關系給捋清楚,就是通過一個parentid字段,讓我們開看一下這張表的表結構
各位看官可以看一下建表語句
DROP TABLE IF EXISTS `menu`; CREATE TABLE `menu` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵遞增', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '分類名稱', `parentid` int(11) NULL DEFAULT NULL COMMENT '父節點id', `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '分類鏈接', `icon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '分類圖標', `order` int(11) NULL DEFAULT NULL COMMENT '分類排序權重', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
再讓我們來插入一點測試數據
INSERT INTO `menu` VALUES (1, '新聞', 0, '/www/xinwen', 'xxx', 1); INSERT INTO `menu` VALUES (2, '每日日報', 1, '/www/meiriribao', 'xxx', 2); INSERT INTO `menu` VALUES (3, '每日晚報', 1, '/www/zaobao', 'xxx', 1); INSERT INTO `menu` VALUES (4, '河南日報', 2, '/www/henan', 'xxx', 2); INSERT INTO `menu` VALUES (5, '上海日報', 2, '/www/shanghai', 'xxx', 1); INSERT INTO `menu` VALUES (6, '南京日報', 2, '/www/nanjing', 'xxx', 3); INSERT INTO `menu` VALUES (7, '開封日報', 4, '/www/zhoukou', 'xxx', 2); INSERT INTO `menu` VALUES (8, '鄭州日報', 4, '/www/zhenghzou', 'xxx', 3);
現在我們來看一下Java程序中該如何從數據庫中讀取數據這樣的數據返回頁面。在下在項目中用的是SSM框架因為並不是SSM框架教程,這里僅僅貼出Dao層,簡單至極
List<Menu> findAll();
<select id="findAll" resultMap="BaseResultMap"> select * from menu; </select>
看一下菜單對應的實體

package com.sys.domain; import java.util.List; public class Menu implements Comparable<Menu> { private Integer id; private String name; private Integer parentid; private String url; private String icon; private Integer order; //子菜單列表 private List<Menu> children; public List<Menu> getChildren() { return children; } public void setChildren(List<Menu> children) { this.children = children; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public Integer getParentid() { return parentid; } public void setParentid(Integer parentid) { this.parentid = parentid; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url == null ? null : url.trim(); } public String getIcon() { return icon; } public void setIcon(String icon) { this.icon = icon == null ? null : icon.trim(); } public Integer getOrder() { return order; } public void setOrder(Integer order) { this.order = order; } @Override public int compareTo(Menu o) { if (this.order != o.order) { return this.order - o.order; } return 0; } }
實體關鍵點1:實體Menu實現了Comparable接口並實現了compareTo方法
這樣就可以直接使用Collections.sort()方法進行List排序
實體關鍵點2:實體中新增一個屬性List<Menu> children,用於存儲返回頁面的子節點
實體較介紹完了,我們就可以直接看代碼啦,都加上注釋了很簡單

package com.sys.menutree; import com.sys.dao.MenuMapper; import com.sys.domain.Menu; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import java.util.*; /** * @Author:jimisun * @Description: * @Date:Created in 18:02 2018/8/8 * @Modified By: */ @Controller @RequestMapping("/menu") public class MenuController { @Autowired private MenuMapper menuMapper; @RequestMapping(value = "/findTree", method = RequestMethod.POST) @ResponseBody public Map<String, Object> findTree(@RequestBody(required = false) Menu menu) { //構建返回數據 Map<String, Object> data = new HashMap<String, Object>(); try { //查詢到的所有菜單 List<Menu> allMenu = menuMapper.findAll(); //根節點 List<Menu> rootMenu = new ArrayList<Menu>(); //根據傳遞的參數設置根節點 if (menu != null && menu.getId() != null) { //父節點為傳遞的id為根節點 for (Menu nav : allMenu) { if (nav.getParentid().equals(menu.getId())) { rootMenu.add(nav); } } } else { //父節點是0的,為根節點。 for (Menu nav : allMenu) { if (nav.getParentid().equals(0)) { rootMenu.add(nav); } } } // 根據Menu類的order排序 Collections.sort(rootMenu); //為根菜單設置子菜單,getClild是遞歸調用的 for (Menu nav : rootMenu) { //獲取根節點下的所有子節點 使用getChild方法 List<Menu> childList = getChild(nav.getId(), allMenu); //給根節點設置子節點 nav.setChildren(childList); } data.put("success", "true"); data.put("list", rootMenu); return data; } catch (Exception e) { data.put("success", "false"); data.put("list", new ArrayList()); return data; } } /** * 遞歸設置欄目的子節點 * * @param id 父節點id * @param allMenu 節點列表 * @return */ private List<Menu> getChild(Integer id, List<Menu> allMenu) { //子菜單 List<Menu> childList = new ArrayList<Menu>(); for (Menu nav : allMenu) { // 遍歷所有節點,將所有菜單的父id與傳過來的根節點的id比較 //相等說明:為該根節點的子節點。 if (nav.getParentid().equals(id)) { childList.add(nav); } } //遞歸設置子節點 for (Menu nav : childList) { nav.setChildren(getChild(nav.getId(), allMenu)); } //排序 Collections.sort(childList); //如果節點下沒有子節點,返回一個空List(遞歸退出) if (childList.size() == 0) { return new ArrayList<Menu>(); } return childList; } }
最后我們來看一下功能演示
直接發送直接請求接口,獲取的是所有分類
請求結果

{ "success": "true", "list": [ { "id": 5, "name": "上海日報", "parentid": 2, "url": "/www/shanghai", "icon": "xxx", "order": 1, "children": [] }, { "id": 4, "name": "河南日報", "parentid": 2, "url": "/www/henan", "icon": "xxx", "order": 2, "children": [ { "id": 7, "name": "開封日報", "parentid": 4, "url": "/www/zhoukou", "icon": "xxx", "order": 2, "children": [] }, { "id": 8, "name": "鄭州日報", "parentid": 4, "url": "/www/zhenghzou", "icon": "xxx", "order": 3, "children": [] } ] }, { "id": 6, "name": "南京日報", "parentid": 2, "url": "/www/nanjing", "icon": "xxx", "order": 3, "children": [] } ] }
發送請求接口,並附帶要查詢的分類id
結果如下

{ "success": "true", "list": [ { "id": 5, "name": "上海日報", "parentid": 2, "url": "/www/shanghai", "icon": "xxx", "order": 1, "children": [] }, { "id": 4, "name": "河南日報", "parentid": 2, "url": "/www/henan", "icon": "xxx", "order": 2, "children": [ { "id": 7, "name": "開封日報", "parentid": 4, "url": "/www/zhoukou", "icon": "xxx", "order": 2, "children": [] }, { "id": 8, "name": "鄭州日報", "parentid": 4, "url": "/www/zhenghzou", "icon": "xxx", "order": 3, "children": [] } ] }, { "id": 6, "name": "南京日報", "parentid": 2, "url": "/www/nanjing", "icon": "xxx", "order": 3, "children": [] } ] }