1. 程式人生 > >mybatis查詢樹形資料的兩種方法

mybatis查詢樹形資料的兩種方法

原貼地址:https://www.cnblogs.com/nick-guo-sdly/p/7668462.html

最近開發中遇到了很多樹形結構資料的需要,利用mybatis提供巢狀查詢功能,基本上可以完美解決,但是對於其中的原理並不理解,導致在使用的時候像瞎貓碰死耗子一樣,照著先前成功的例子copy,後來遇到了莫名奇怪的報錯遲遲不能解決,於是百度了一番,大致瞭解了背後的原理,整理如下。

  以簡單的角色-選單為例

 表結構

 

其中menu為選單表,role為角色表,roleandmenu是中間表,角色和選單為多對多的關係,現在我們需要下圖所示的實體類

複製程式碼

 1 import java.util.List;
 2 
 3 public class RoleInfo {
 4     
 5     private Integer roleid;
 6     private String rolename;
 7     private List<Menu> menulist;
 8     public Integer getRoleid() {
 9         return roleid;
10     }
11     public void setRoleid(Integer roleid) {
12         this.roleid = roleid;
13     }
14     public String getRolename() {
15         return rolename;
16     }
17     public void setRolename(String rolename) {
18         this.rolename = rolename;
19     }
20     public List<Menu> getMenulist() {
21         return menulist;
22     }
23     public void setMenulist(List<Menu> menulist) {
24         this.menulist = menulist;
25     }
26     
27     @Override
28     public String toString() {
29         return "RoleInfo [roleid=" + roleid + ", rolename=" + rolename + ", menulist=" + menulist + "]";
30     }
31     
32 }

複製程式碼

 

 第一種方法:利用巢狀語句查詢

複製程式碼

 1 <resultMap type="com.test.mybatis.model.RoleInfo" id="roleModel">
 2         <id column="id" property="roleid"/>
 3         <result column="name" property="rolename"/>
 4         <collection property="menulist" select="getMenu" column="id">
 5             
 6         </collection>
 7     </resultMap>
 8     
 9     <select id="getRoleInfo" resultMap="roleModel">
10         select id,name from role
11     </select>
12     
13     <select id="getMenu" resultType="com.test.mybatis.model.Menu">
14         select m.id,m.name 
15         from menu m join roleandmenu ram on m.id=ram.menuId
16         where ram.roleId=#{id}
17     </select>

複製程式碼

複製程式碼

 1     @Test
 2     public void testRoleAndMenu() throws IOException {
 4         Reader reader = Resources.getResourceAsReader("mybatis.xml");
 5         SqlSessionFactory sqlsessionfac = new SqlSessionFactoryBuilder().build(reader);
 6         SqlSession sqlsession = sqlsessionfac.openSession();
 7         try {
 8             RoleAndMenuMapper mapper = sqlsession.getMapper(RoleAndMenuMapper.class);
 9             System.out.println(JSONObject.toJSON(mapper.getRoleInfo()));
10         } catch (Exception e) {
11             // TODO: handle exception
12             e.printStackTrace();
13         } finally {
14             sqlsession.close();
15         }
17     }

複製程式碼

結果:

 

原理如下:

  1.mybatis先執行getRoleInfo這個查詢,獲取結果集

  2.從ResultSet中逐一取出記錄,構建RoleInfo物件併為對映屬性賦值

  3.賦值過程中發現目標menulist屬性配置了一個關聯集合(collection),此時執行id為collection標籤中select屬性值(getMenu)的查詢,並將當前記錄中的id屬性作為此查詢的引數。(association標籤同理)

  4.將關聯查詢返回的結果對映到meunlist屬性

  5.執行步驟2,直至ResultSet.next=false

  6.返回查詢結果

  這種方式的好處在於簡單易懂,通過簡單的配置就可以達到目標效果。不足之處在於如果結果集記錄條數過大,會造成較大的資料庫訪問消耗,因為在從ResultSet中取出記錄的時候每取一條,便執行一次關聯查詢,假設一次查詢的結果集有10條記錄,則資料庫的訪問次數為:關聯查詢次數(10)+返回結果集的查詢(1)=11次。

需要注意的地方

 1.collection/association標籤的column屬性:當向關聯查詢傳遞的引數個數為1時,column的值應為結果集中的列名,而不是對映屬性名(property),上面的例子中,向關聯查詢傳遞id值,column的值應為id而不是roleid。

  可以向關聯查詢傳遞多個引數,此時column的值為多個鍵值對,如下圖

  此時向關聯查詢傳遞了兩個引數id和name,此時還應該將關聯的查詢的parameterType改為java.util.Map,否則關聯查詢無法接受引數

 

  2.在進行單一型別樹形結構查詢的時候,需要注意關聯查詢的結果集中的列是否有作為查詢條件的列

  這樣說可能比較彆扭,以上面的menu表為例,有一個parent列用於儲存父部門的ID,使用巢狀查詢獲取以下實體類

複製程式碼

 1 import java.util.List;
 2 
 3 public class MenuTree {
 4     
 5     private Integer id;
 6     private String name;
 7     private List<Menu> children;
 8     public Integer getId() {
 9         return id;
10     }
11     public void setId(Integer id) {
12         this.id = id;
13     }
14     public String getName() {
15         return name;
16     }
17     public void setName(String name) {
18         this.name = name;
19     }
20     public List<Menu> getChildren() {
21         return children;
22     }
23     public void setChildren(List<Menu> children) {
24         this.children = children;
25     }
26     
27     
28 }

複製程式碼

  此時會產生一個問題:每個頂級選單(parent為空的選單)的子菜單隻有一個查詢結果,這是因為在關聯查詢getSubMenu中沒有將id查詢出來,而關聯查詢和主查詢的resultMap一樣,所以關聯查詢在對映結果集的時候就會再次去執行關聯查詢,而由於本次關聯查詢並沒有取出id這個作為引數的屬性,所以實際上只執行了N(結果集記錄數)次關聯查詢

  因此,在這種情況中,必須在關聯查詢中查詢出id這個列,否則會查詢不出預期結果。

 

 第二種方法:使用巢狀結果集

複製程式碼

 1 <resultMap type="com.test.mybatis.model.RoleInfo" id="roleModel">
 2         <id column="id" property="roleid"/>
 3         <result column="name" property="rolename"/>
 4         <collection property="menulist" ofType="com.test.mybatis.model.Menu">
 5             <id column="menuid" property="id"/>
 6             <result column="menuname" property="name"/>
 7             <result column="description" property="description"/>
 8             <result column="parent" property="parent"/>
 9             <result column="createdate" property="createdate"/>
10             <result column="modifydate" property="modifydate"/>
11         </collection>
12     </resultMap>
13     
14     <select id="getRoleInfo" resultMap="roleModel">
15         select 
16             ram.roleid as id,
17             ro.name as name,
18             me.id as menuid,
19             me.name as menuname,
20             me.description,
21             me.parent,
22             me.createdate,
23             me.modifydate 
24         from roleandmenu ram
25             left outer join role ro on ram.roleid=ro.id
26             left outer join menu me on ram.menuid=me.id
27     </select>

複製程式碼

  原理是通過關聯查詢,一次性將資料查詢出來,然後根據resultMap的配置進行轉換,構建目標實體類。

  顯然,這種方法更為直接,只需要訪問一次資料庫就可以了,不會造成嚴重的資料庫訪問消耗。

   此外,我還發現了一個無解的情況,如果把巢狀結果集的返回值型別全部改成HashMap的話,會導致menulist裡只有一行資料

  

  解決的辦法是,給collection標籤的javaType賦值為目標集合型別

  

  找了很久,終於在官方文件裡找到了解釋