使用Mybatis生成樹形選單-適用於各種樹形場景

2023-06-14 12:00:22

開發中我們難免會遇到各種樹形結構展示的場景。比如使用者登入系統後選單的展示,某些大型購物網站商品的分類展示等等,反正開發中會遇到各種樹形展示的功能,這些功能大概處理的思路都是一樣的,所以本文就總結一下樹形結構的程式碼生成,在開發的時候套用這種結構就可以了。

好了正文開始,首先相關的SQL指令碼【MYSQL】提供給你(包吃包住包SQL)【如果巔峰留不住那就進廠包吃住】。

DDL語句:

CREATE TABLE `student`.`SYS_menu`(  
  `ID` INT(10) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(200) NOT NULL,
  `permissions` VARCHAR(1000),
  `url` VARCHAR(200),
  `description` VARCHAR(2000),
  `icon_cls` VARCHAR(2000),
  `pid` INT(10),
  `status` INT(2),
  `resource_type` INT(2),
  `sort` INT(6),
  `create_time` TIMESTAMP,
  `update_time` TIMESTAMP,
  PRIMARY KEY (`ID`)
) ENGINE=INNODB CHARSET=utf8;

初始化語句:

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('主選單',NULL,NULL,'資料主選單',NULL,0,1,1,NOW(),NOW(),NULL) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單1',NULL,NULL,'選單1',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='主選單') a ),1,1,NOW(),NOW(),1) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單2',NULL,NULL,'選單2',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='主選單') a ),1,1,NOW(),NOW(),2) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單3',NULL,NULL,'選單3',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='主選單') a ),1,1,NOW(),NOW(),3) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單1.1',NULL,NULL,'選單1的子選單',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='選單1') a ),1,1,NOW(),NOW(),1) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單1.2',NULL,NULL,'選單1的子選單',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='選單1') a ),1,1,NOW(),NOW(),2) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單1.3',NULL,NULL,'選單1的子選單',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='選單1') a ),1,1,NOW(),NOW(),3) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單2.1',NULL,NULL,'選單2的子選單',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='選單2') a ),1,1,NOW(),NOW(),1) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單2.2',NULL,NULL,'選單2的子選單',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='選單2') a ),1,1,NOW(),NOW(),2) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單2.3',NULL,NULL,'選單2的子選單',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='選單2') a ),1,1,NOW(),NOW(),3) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單3.1',NULL,NULL,'選單3的子選單',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='選單3') a ),1,1,NOW(),NOW(),1) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單3.2',NULL,NULL,'選單3的子選單',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='選單3') a ),1,1,NOW(),NOW(),2) ;

INSERT INTO SYS_MENU(NAME,permissions,url,description,icon_cls,pid,STATUS,resource_type,create_time,update_time,SORT)

VALUES('選單3.3',NULL,NULL,'選單3的子選單',NULL,(SELECT * FROM (SELECT id FROM SYS_MENU WHERE NAME='選單3') a ),1,1,NOW(),NOW(),3) 

資料結構一般就是上面得那樣,只是初始化得資料按照你開發得需求初始化。

然後就是建立簡單得專案,好了那我就貼出相關得程式碼,這些大家開發得時候可以根據需求進行巢狀使用。

// entity
@Data
@TableName("sys_menu")
public class SysMenu implements Serializable {
  private static final long serialVersionUID = 1L;
  @TableId
  private Integer id;
  private String name;
  private String permissions;
  private String url;
  private String description;
  private String iconCls;
  private Integer pid;
  private Integer status;
  private Integer resourceType;
  private Integer sort;
  private Date createTime;
  private Date updateTime;
  /**
   * 此處為了簡單我就不新建DTO物件了,
   * 加一個children屬性,注意如果不是資料庫的欄位一定要
   * 加下面d額那個註解
   */
  @TableField(exist=false)
  private List<SysMenu> children;

}
// mapper介面,很簡單沒有多餘程式碼
public interface SysMenuDao extends BaseMapper<SysMenu> {
  
}
// mapper 檔案也是沒有多餘的程式碼,使用Mybatis-Plus特有的就行
// 其中下面的resultMap也可以去掉。
<mapper namespace="io.renren.mapper.SysMenuDao">
  <!-- 可根據自己的需求,是否要使用 -->
    <resultMap type="io.renren.domain.SysMenu" id="sysMenuMap">
        <result property="id" column="ID"/>
        <result property="name" column="name"/>
        <result property="permissions" column="permissions"/>
        <result property="url" column="url"/>
        <result property="description" column="description"/>
        <result property="iconCls" column="icon_cls"/>
        <result property="pid" column="pid"/>
        <result property="status" column="status"/>
        <result property="resourceType" column="resource_type"/>
        <result property="sort" column="sort"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
    </resultMap>
</mapper>
// service 主要的邏輯程式碼就在這裡。
public interface SysMenuService extends IService<SysMenu> {

    List<SysMenu> getMenuTree();
}

@Service("sysMenuService")
public class SysMenuServiceImpl extends ServiceImpl<SysMenuDao, SysMenu> implements SysMenuService {

    @Autowired
    private SysMenuDao sysMenuDao ;
    @Override
    public List<SysMenu> getMenuTree() {
        //查詢出所有選單
        List<SysMenu> sysMenus = sysMenuDao.selectList(null);
        //2、組裝成樹形結構
        //2.1)、找到所有的一級選單
        List<SysMenu> level1Menus = sysMenus.stream().filter(sysMenu ->
                sysMenu.getPid() == 0
        ).map((menu) -> {
            menu.setChildren(getChildrens(menu, sysMenus));
            return menu;
            // 排序
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return level1Menus;
    }
    
       //遞迴查詢所有選單的子選單,主要就是用了這個遞迴查詢。
       // 我也都寫了相關的註釋。
    private List<SysMenu> getChildrens(SysMenu root, List<SysMenu> all) {

        List<SysMenu> children = all.stream().filter(sysMenu -> {
            return sysMenu.getPid() == root.getId();
        }).map(sysMenu -> {
            //1、找到子選單
            sysMenu.setChildren(getChildrens(sysMenu, all));
            return sysMenu;
        }).sorted((menu1, menu2) -> {
            //2、選單的排序
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());
        return children;
    }

有的小夥伴可能會說,博主我們專案使用的jdk7 。上面的程式碼使用不了啊,這篇文章對我來說不合適啊。好了安排上,既然寫文章我就要寫的明明白白,下面是不使用Stream流完成的功能。

List<SysMenu> getMenuTreeVerLowJava8() ;
 
   @Override
    public List<SysMenu> getMenuTreeVerLowJava8() {
        List<SysMenu> sysMenus = sysMenuDao.selectList(null);
        List<SysMenu> tree = new ArrayList<>();
        for (SysMenu sysmenu: sysMenus) {
            if (sysmenu.getPid()==0){
               tree.add(getChildrens02(sysmenu,sysMenus)) ;
            }
        }
        return tree;
    }
 
    /**
     * 不使用stream的遞迴呼叫
     * @param list
     * @return
     */
    private SysMenu getChildrens02(SysMenu sysMenu, List<SysMenu> list) {

        List<SysMenu> children = new ArrayList<SysMenu>();
        for (SysMenu sysMenu2 : list) {
            if (sysMenu2.getPid() == sysMenu.getId()) {
                // 遞迴呼叫
                SysMenu result = getChildrens02(sysMenu2, list);
                children.add(result);
            }
        }
        sysMenu.setChildren(children);
        return sysMenu;
    }

使用Java7的選單樹我沒有進行排序,Java7的排序使用起來也很簡單,相信大家開發的時候都使用過,大家可以自行完成排序。生成的樹形結構太多我就不貼出來了肯定是正確的。

開發中就是需要這種記錄,為什麼呢,當你沒看到這篇文章你寫一個樹形結構的程式碼可能需要一天,而你點一下關注,後面開發中你遇到這種功能的開發一個小時應該就能搞定並且還沒有問題,極大的提高了開發效率,領導看到你效率那麼高應該也會很高興,說不定升職加薪就在眼前。點點關注何樂而不為呢?生活中也一樣,你同樣需要記錄總結,這樣你應該也越走越順,比如你今天上班路上遇到一個坑,你記住了。下次走過這裡你就會避開這個坑,路也越走越順了。