需求:項目中想實現無限下拉子菜單功能,後臺就需要返回包括子節點的所有數據
數據庫表結構如下:
上次是通過在java程序中遞歸不斷通過父級id查詢子節點實現的:
https://blog.csdn.net/lianzhang861/article/details/83783796
但這樣需要不斷連接和斷開數據庫,比較費時
mybatis可以在數據庫內部實現遞歸查詢被自動裝填,由於省去了數據庫連接步驟,訪問速度會更快,但會增加數據庫服務器壓力,使用時根據實際情況定使用程序遞歸還是數據庫遞歸
方式是使用 resultMap中的collection,此標籤可以一對多級聯
<resultMap>
<constructor> //適用於不存在沒有參數的構造方法
<idArg></idArg>
<arg></arg>
</constructor>
<id/>//這個對象的主鍵
<association/>//一對一級聯
<collection/>//一對多級聯
<discriminator>//鑑別器
<case/>
</discriminator>
</resultMap>
實現方法:
1.首先添加該表的實體類ArticleCategory
public class ArticleCategory {
private String categoryId;
private String categoryName;
private String categoryParentId;
//存放子節點
private List<ArticleCategory> subList;
public String getCategoryId() {
return categoryId;
}
public void setCategoryId(String categoryId) {
this.categoryId = categoryId;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public List<ArticleCategory> getSubList() {
return subList;
}
public void setSubList(List<ArticleCategory> subList) {
this.subList = subList;
}
public String getCategoryParentId() {
return categoryParentId;
}
public void setCategoryParentId(String categoryParentId) {
this.categoryParentId = categoryParentId;
}
}
2.controller調用
@ResponseBody
@RequestMapping(value = "getArticleCategoryTree2", produces = "text/plain;charset=UTF-8")
public String getArticleCategory2(@RequestParam Map<String,String> params) {
RetBase ret=new RetBase();
params.put("categoryId","0");
//List<Map<String,Object>> list=this.getSubCategory(params);
List<ArticleCategory> list=articleService.getArticleCategory1(params);
ret.setData(list);
ret.setSuccess(true);
return JSON.toJSONString(ret);
}
3.dao
public List<Map<String,Object>> getArticleCategory(Map<String,String> params);
4.xml
<resultMap id="Category" type="com.zhuhuixin.common.entity.ArticleCategory">
<id column="category_id" property="categoryId"/>
<result column="category_name" property="categoryName"></result>
<result column="category_parent_id" property="categoryParentId"></result>
<collection column="category_id" property="subList"
ofType="com.zhuhuixin.common.entity.ArticleCategory"
select="getArticleCategory"></collection>
</resultMap>
<select id="getArticleCategory" resultMap="Category">
select
*
from article_category t
where t.category_status='YES'
and t.category_parent_id = #{categoryId}
order by t.category_name
</select>
5.查出的數據格式
注意:
1.collection 的column與id的column相同,property爲實體類中子集合的名字,select與查詢方法名字相同
2.查詢時一定要將id和parentId都查出來,否則mybaits無法完成遞歸,我用*查就更沒問題了。實體類中也要有父id那個屬性
3.我從controller調用的時候已經傳入了一個父id參數,然後mybatis遞歸時又自動將父id傳給查詢。但這個參數的名字可以和resultMap中的id名字不相同,也就是說如果我controller裏面傳的父id名字爲 cc,select中改爲
and t.category_parent_id = #{cc}
mybatis照樣能查出子集合,所以這個參數名可以隨便寫
不過前臺js解讀數據的時候也得用遞歸方法解析數據,有點麻煩~~~
========== 華麗分割線 ==============
時間來到了2019.12.19
最近搞springboot項目中的菜單管理中又用到了這個,不過菜單由於有權限限制啥的,傳入的參數比較多,查詢就會出現問題,要不查不出來,要不就是子查詢參數不生效,因爲先前只用到一個父id參數,而且網上能找到的大部分例子都是一個參數例子,這次經過試驗總結到collection多參數傳入的方法
<resultMap>
<id/>//這個對象的主鍵
<collection column="
{menuParent=MENU_ID,menuType=MENU_TYPE,menuStatus=MENU_STATUS,userId=USER_ID}"
property="subList"
ofType="com.bomc.enterprise.entry.SysMenu"
select="getMenuList">
</collection>
</resultMap>
首先collection是用來遞歸查詢一對多的,其次是參數傳入的問題,column是用來傳參數的,如果只有一個參數,直接寫一個用來關聯的字段名就行了,不管你傳的參數叫啥,它都會把上次查出的結果的這個字段自動放到下一次查詢 這個參數的位置,所以就會發現如果只有一個參數,你參數傳啥名字都沒事。
<resultMap id="Category" type="com.zhuhuixin.common.entity.ArticleCategory">
<id column="category_id" property="categoryId"/>
<result column="category_name" property="categoryName"></result>
<result column="category_parent_id" property="categoryParentId"></result>
<collection column="category_id" property="subList"
ofType="com.zhuhuixin.common.entity.ArticleCategory"
select="getArticleCategory"></collection>
</resultMap>
<select id="getArticleCategory" resultMap="Category">
select
*
from article_category t
where t.category_status='YES'
and t.category_parent_id = #{categoryId}
order by t.category_name
</select>
但如果你的查詢有多個參數就不一樣了,需要用這種寫法了,前面是傳入參數名,等號 後面是字段名字
<collection column="
{menuParent=MENU_ID,menuType=MENU_TYPE,menuStatus=MENU_STATUS,userId=USER_ID}"
例子:
1.傳入的參數
params.put("menuParent", "0");
params.put("menuType", "menu");
params.put("userId", USER.getUserId());
List<SysMenu> menuList = menuService.getMenuList(params);
2.xml
<resultMap id="SysMenu" type="com.bomc.enterprise.entry.SysMenu" >
<id column="MENU_ID" property="menuId"/>
<!--<result column="MENU_ID" property="menuId" jdbcType="VARCHAR" />-->
<result column="MENU_PARENT" property="menuParent" jdbcType="VARCHAR" />
<result column="MENU_CODE" property="menuCode" jdbcType="VARCHAR" />
<result column="MENU_NAME" property="menuName" jdbcType="VARCHAR" />
<result column="MENU_TYPE" property="menuType" jdbcType="VARCHAR" />
<result column="MENU_LEAF" property="menuLeaf" jdbcType="VARCHAR" />
<result column="MENU_METHOD" property="menuMethod" jdbcType="VARCHAR" />
<result column="MENU_ICON" property="menuIcon" jdbcType="VARCHAR" />
<result column="MENU_URL" property="menuUrl" jdbcType="VARCHAR" />
<result column="MENU_DESC" property="menuDesc" jdbcType="VARCHAR" />
<result column="MENU_STATUS" property="menuStatus" jdbcType="VARCHAR" />
<result column="MENU_SORT" property="menuSort" jdbcType="DECIMAL" />
<result column="CREATE_TIME" property="createTime" jdbcType="DATE" />
<result column="MODIFY_TIME" property="modifyTime" jdbcType="DATE" />
<result column="AVAILABLE_START_TIME" property="availableStartTime" jdbcType="DATE" />
<result column="AVAILABLE_END_TIME" property="availableEndTime" jdbcType="DATE" />
<collection column="{menuParent=MENU_ID,menuType=MENU_TYPE,userId=USER_ID}" property="subList"
ofType="com.bomc.enterprise.entry.SysMenu"
select="getMenuList">
</collection>
</resultMap>
<select id="getMenuList" resultMap="SysMenu">
select
t.*,case t.menu_type when 'menu' then '按鈕' when 'href' then '鏈接' else '菜單' end as menu_type1,
case t.menu_status when 'YES' then '是' else '否' end as menu_status1,
#{userId,jdbcType=VARCHAR} USER_ID
from sys_menu t
where
1=1
<if test="menuParent!=null and menuParent!=''">
and t.menu_parent =#{menuParent,jdbcType=VARCHAR}
</if>
<choose>
<when test="menuStatus!=null and menuStatus!=''">
and t.menu_status=#{menuStatus,jdbcType=VARCHAR}
</when>
<otherwise>
and t.menu_status='1'
</otherwise>
</choose>
<if test="menuType!=null and menuType!=''">
and t.menu_type = #{menuType,jdbcType=VARCHAR}
</if>
<if test="menuName!=null and menuName!=''">
and t.menu_name like '%'||#{menuName,jdbcType=VARCHAR}||'%'
</if>
<if test="roleId!=null and roleId!=''">
and t.menu_Id in (
select t1.menu_Id from sys_role_menu t1
where t1.role_id = #{roleId,jdbcType=VARCHAR}
)
</if>
<if test="userId!=null and userId!=''">
and t.menu_Id in (
select t1.menu_Id from sys_role_menu t1
where t1.role_id in(
select t2.role_id from sys_user_role t2
where t2.user_id = #{userId,jdbcType=VARCHAR}
)
)
</if>
order by t.menu_sort
</select>
上面例子我傳入了三個參數,想要正常查詢,就得在column中配置三個參數,雖然我的sql中還判斷其他的參數,但實際沒有用到的話就不必在column中配
每次查詢到父級後就會把查到結果中對應的字段值 匹配 配置的 參數名,一一對應的設置爲下一次查詢的參數值。
這也就意味着你的參數必須存在於上一次的查詢結果集中,但是對於菜單管理,需要傳入userId 去查詢菜單權限這種情況,菜單表中肯定沒有userId這個字段,這就需要你在查詢第一次的時候就手動加上這個字段:
按理說應該在實體類和resultMap映射也添加這個字段纔對,但我測試的是即使 實體類和resultMap映射中沒有這個字段也可以正常使用。
如果參數是常量似乎可以直接寫,但是不能直接把#{參數}放到column中:
column="{menuParent=MENU_ID,menuType=MENU_TYPE,userId='111'}"
總之多參數傳入這麼可以解決,暫時還沒發現有啥正統的解決方法,其次關於是否分頁有問題還沒有測試,暫時先這樣