springmvc+mybatis 无极限树形结构 Mapperxml 映射方法

简介: 项目中我们可能经常有这样的需求,需要返回二级或三级的菜单,返回一个树形结构,面试中也可能经常被问到。最近的项目中就用到了,这里整理分享一下。应用场景:地区树,国家,省,市,区县共4级。表结构:建表语句:SET NAMES utf8;SET FOREIGN_KEY_CHECKS ...

项目中我们可能经常有这样的需求,需要返回二级或三级的菜单,返回一个树形结构,面试中也可能经常被问到。

最近的项目中就用到了,这里整理分享一下。

应用场景:

地区树,国家,省,市,区县共4级。

表结构:


建表语句:

SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
--  Table structure for `base_area`
-- ----------------------------
DROP TABLE IF EXISTS `base_area`;
CREATE TABLE `base_area` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `AREA_NAME` varchar(255) DEFAULT NULL COMMENT '地区名称',
  `AREA_CODE` varchar(255) DEFAULT NULL COMMENT '地区编码',
  `PARENT_ID` bigint(20) DEFAULT NULL,
  `PLAT_MARK` bigint(20) DEFAULT NULL COMMENT '区域标识,也就是平台标识',
  `LEVEL` tinyint(4) DEFAULT '1' COMMENT '层',
  `STATUS` tinyint(4) DEFAULT '1' COMMENT '是否可用、是否显示',
  `EXPAND` tinyint(4) DEFAULT '0' COMMENT '是否展开子节点,非0为展开。',
  PRIMARY KEY (`ID`),
  KEY `index2` (`PLAT_MARK`)
) ENGINE=InnoDB AUTO_INCREMENT=3514 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
返回JSON结果示例:

数据比较多,这里只展示一部分,有需要完整表数据的小伙伴,稍后提供下载地址。

{"result":[{"createTimeString":"","updateTimeString":"","level":3,"platMark":100001001000000,"parentId":1,"areaCode":"110000","expand":0,"areaName":"北京市","id":2,"childrenList":[{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001001000,"parentId":2,"areaCode":"110101","expand":0,"areaName":"东城区","id":4,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001002000,"parentId":2,"areaCode":"110102","expand":0,"areaName":"西城区","id":5,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001003000,"parentId":2,"areaCode":"110105","expand":0,"areaName":"朝阳区","id":6,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001004000,"parentId":2,"areaCode":"110106","expand":0,"areaName":"丰台区","id":7,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001005000,"parentId":2,"areaCode":"110107","expand":0,"areaName":"石景山区","id":8,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001006000,"parentId":2,"areaCode":"110108","expand":0,"areaName":"海淀区","id":9,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001007000,"parentId":2,"areaCode":"110109","expand":0,"areaName":"门头沟区","id":10,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001008000,"parentId":2,"areaCode":"110111","expand":0,"areaName":"房山区","id":11,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001009000,"parentId":2,"areaCode":"110112","expand":0,"areaName":"通州区","id":12,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001010000,"parentId":2,"areaCode":"110113","expand":0,"areaName":"顺义区","id":13,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001011000,"parentId":2,"areaCode":"110114","expand":0,"areaName":"昌平区","id":14,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001012000,"parentId":2,"areaCode":"110115","expand":0,"areaName":"大兴区","id":15,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001013000,"parentId":2,"areaCode":"110116","expand":0,"areaName":"怀柔区","id":16,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001014000,"parentId":2,"areaCode":"110117","expand":0,"areaName":"平谷区","id":17,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001015000,"parentId":2,"areaCode":"110228","expand":0,"areaName":"密云县","id":18,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100001001016000,"parentId":2,"areaCode":"110229","expand":0,"areaName":"延庆县","id":19,"childrenList":[]}]},{"createTimeString":"","updateTimeString":"","level":3,"platMark":100002001000000,"parentId":1,"areaCode":"120000","expand":0,"areaName":"天津市","id":20,"childrenList":[{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001001000,"parentId":20,"areaCode":"120101","expand":0,"areaName":"和平区","id":22,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001002000,"parentId":20,"areaCode":"120102","expand":0,"areaName":"河东区","id":23,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001003000,"parentId":20,"areaCode":"120103","expand":0,"areaName":"河西区","id":24,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001004000,"parentId":20,"areaCode":"120104","expand":0,"areaName":"南开区","id":25,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001005000,"parentId":20,"areaCode":"120105","expand":0,"areaName":"河北区","id":26,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001006000,"parentId":20,"areaCode":"120106","expand":0,"areaName":"红桥区","id":27,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001007000,"parentId":20,"areaCode":"120110","expand":0,"areaName":"东丽区","id":28,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001008000,"parentId":20,"areaCode":"120111","expand":0,"areaName":"西青区","id":29,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001009000,"parentId":20,"areaCode":"120112","expand":0,"areaName":"津南区","id":30,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001010000,"parentId":20,"areaCode":"120113","expand":0,"areaName":"北辰区","id":31,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001011000,"parentId":20,"areaCode":"120114","expand":0,"areaName":"武清区","id":32,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001012000,"parentId":20,"areaCode":"120115","expand":0,"areaName":"宝坻区","id":33,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001013000,"parentId":20,"areaCode":"120116","expand":0,"areaName":"滨海新区","id":34,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001014000,"parentId":20,"areaCode":"120221","expand":0,"areaName":"宁河县","id":35,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001015000,"parentId":20,"areaCode":"120223","expand":0,"areaName":"静海县","id":36,"childrenList":[]},{"createTimeString":"","updateTimeString":"","level":4,"platMark":100002001016000,"parentId":20,"areaCode":"120225","expand":0,"areaName":"蓟县","id":37,"childrenList":[]}]}]}

实体类:

/**
 * 表名:base_area
 * 备注:BaseArea
 */
@SuppressWarnings("serial")
public class BaseArea {
		
	//date formats
	
	//columns START
	//id
	private Long id;
	//地区名称
	private String areaName;
	//地区编码
	private String areaCode;
	//parentId
	private Long parentId;
	//区域标识,也就是平台标识
	private Long platMark;
	//层
	private Byte level;
	//是否可用、是否显示
	private Integer status;
	//是否展开子节点,非0为展开。
	private Byte expand;
	//columns END
	
	//extend columns START
	//子节点列表
	List<BaseArea> childrenList;
	//extend columns END

	public List<BaseArea> getChildrenList() {
		return childrenList;
	}

	public void setChildrenList(List<BaseArea> childrenList) {
		this.childrenList = childrenList;
	}

	public BaseArea(){
	}

	public BaseArea(Long id){
		this.id = id;
	}

	/**
	 * id
	 * @return
	 */
	public Long getId() {
		return this.id;
	}
	
	/**
	 * id
	 * @param value
	 */
	public void setId(Long value) {
		this.id = value;
	}
	/**
	 * 地区名称
	 * @return
	 */
	public String getAreaName() {
		return this.areaName;
	}
	
	/**
	 * 地区名称
	 * @param value
	 */
	public void setAreaName(String value) {
		this.areaName = value;
	}
	/**
	 * 地区编码
	 * @return
	 */
	public String getAreaCode() {
		return this.areaCode;
	}
	
	/**
	 * 地区编码
	 * @param value
	 */
	public void setAreaCode(String value) {
		this.areaCode = value;
	}
	/**
	 * parentId
	 * @return
	 */
	public Long getParentId() {
		return this.parentId;
	}
	
	/**
	 * parentId
	 * @param value
	 */
	public void setParentId(Long value) {
		this.parentId = value;
	}
	/**
	 * 区域标识,也就是平台标识
	 * @return
	 */
	public Long getPlatMark() {
		return this.platMark;
	}
	
	/**
	 * 区域标识,也就是平台标识
	 * @param value
	 */
	public void setPlatMark(Long value) {
		this.platMark = value;
	}
	/**
	 * 层
	 * @return
	 */
	public Byte getLevel() {
		return this.level;
	}
	
	/**
	 * 层
	 * @param value
	 */
	public void setLevel(Byte value) {
		this.level = value;
	}
	/**
	 * 是否可用、是否显示
	 * @return
	 */
	public Integer getStatus() {
		return this.status;
	}
	
	/**
	 * 是否可用、是否显示
	 * @param value
	 */
	public void setStatus(Integer value) {
		this.status = value;
	}
	/**
	 * 是否展开子节点,非0为展开。
	 * @return
	 */
	public Byte getExpand() {
		return this.expand;
	}
	
	/**
	 * 是否展开子节点,非0为展开。
	 * @param value
	 */
	public void setExpand(Byte value) {
		this.expand = value;
	}
}

Mapperxml 映射方法

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">


<!-- 不使用namespace的话sql搜索定位会比较方便 -->
<!-- BaseArea -->
<mapper namespace="BaseArea">
	
	<resultMap id="baseAreaResult" type="com.wanyu.smarthome.model.BaseArea">
        <id property="id" column="ID"/>
        <result property="areaName" column="AREA_NAME"/>
        <result property="areaCode" column="AREA_CODE"/>
        <result property="parentId" column="PARENT_ID"/>
        <result property="platMark" column="PLAT_MARK"/>
        <result property="level" column="LEVEL"/>
        <result property="status" column="STATUS"/>
        <result property="expand" column="EXPAND"/>
        <collection property="childrenList" javaType="java.util.ArrayList" column="id"
                    ofType="com.wanyu.smarthome.model.BaseArea" select="childrenSelect"></collection>
	</resultMap>
	
	<resultMap id="childrenResult" type="com.wanyu.smarthome.model.BaseArea">
        <id property="id" column="ID"/>
        <result property="areaName" column="AREA_NAME"/>
        <result property="areaCode" column="AREA_CODE"/>
        <result property="parentId" column="PARENT_ID"/>
        <result property="platMark" column="PLAT_MARK"/>
        <result property="level" column="LEVEL"/>
        <result property="status" column="STATUS"/>
        <result property="expand" column="EXPAND"/>
        <collection property="childrenList" javaType="java.util.ArrayList" column="id"
                    ofType="com.wanyu.smarthome.model.BaseArea" select="childrenSelect"></collection>
	</resultMap>
	
	<!-- 用于select查询公用抽取的列 -->
	<sql id="commonColumns">
	    <![CDATA[
        	ID,
        	AREA_NAME,
        	AREA_CODE,
        	PARENT_ID,
        	PLAT_MARK,
        	LEVEL,
        	STATUS,
        	EXPAND
	    ]]>
	</sql>
	
	<sql id="commonAliasColumns">
	    <![CDATA[
        	x.ID,
        	x.AREA_NAME,
        	x.AREA_CODE,
        	x.PARENT_ID,
        	x.PLAT_MARK,
        	x.LEVEL,
        	x.STATUS,
        	x.EXPAND
	    ]]>
	</sql>

	<!-- useGeneratedKeys="true" keyProperty="xxx" for sqlserver and mysql -->
	<insert id="insert" useGeneratedKeys="true" keyProperty="id" parameterType="com.wanyu.smarthome.model.BaseArea">
    <![CDATA[
        INSERT INTO base_area (
        	ID,
        	AREA_NAME,
        	AREA_CODE,
        	PARENT_ID,
        	PLAT_MARK,
        	LEVEL,
        	STATUS,
        	EXPAND
        ) VALUES (
        	#{id},
        	#{areaName},
        	#{areaCode},
        	#{parentId},
        	#{platMark},
        	#{level},
        	#{status},
        	#{expand}
        )
    ]]>
	</insert>
    
	<update id="update" parameterType="com.wanyu.smarthome.model.BaseArea">
    <![CDATA[
        UPDATE base_area SET
	        AREA_NAME = #{areaName},
	        AREA_CODE = #{areaCode},
	        PARENT_ID = #{parentId},
	        PLAT_MARK = #{platMark},
	        LEVEL = #{level},
	        STATUS = #{status},
	        EXPAND = #{expand}
        WHERE 
	        ID = #{id}
    ]]>
	</update>
	
	<delete id="delete" parameterType="map">
    <![CDATA[
        delete from base_area
    ]]>
		<include refid="dynamicWhere"/>
    </delete>
    
    <delete id="batchDelete" parameterType="list">
        delete from base_area where ID IN 
        <foreach collection="list" item="ids"  open="(" separator="," close=")">   
#{ids}        </foreach> 
    </delete>
    
    <delete id="batchDeleteByLocalId" parameterType="list">
        delete from base_area where LOCAL_ID in
        <foreach collection="list" item="ids"  open="(" separator="," close=")">   
#{ids}		</foreach> 
    </delete>
    
    <select id="getById" resultMap="baseAreaResult">
		SELECT <include refid="commonColumns" />
	    <![CDATA[
		    FROM base_area 
	        WHERE 
		        ID = #{id}
	    ]]>
	</select>
	
	<select id="getByIds" resultMap="baseAreaResult">
		SELECT <include refid="commonColumns" />
		FROM base_area WHERE ID IN 
		<foreach collection="list" item="ids"  open="(" separator="," close=")">   
#{ids}        </foreach>  
	</select>
	
	<sql id="dynamicWhere">
		<!-- ognl访问静态方法的表达式 为@class@method(args),以下为调用rapid中的Ognl.isNotEmpty()方法,还有其它方法如isNotBlank()可以使用,具体请查看Ognl类 -->
		<where>	      				
	       <if test="@Ognl@isNotEmpty(id)">
				AND ID = #{id}
			</if>
	       <if test="@Ognl@isNotEmpty(areaName)">
				AND AREA_NAME = #{areaName}
			</if>
	       <if test="@Ognl@isNotEmpty(areaCode)">
				AND AREA_CODE = #{areaCode}
			</if>
	       <if test="@Ognl@isNotEmpty(parentId)">
				AND PARENT_ID = #{parentId}
			</if>
	       <if test="@Ognl@isNotEmpty(platMark)">
				AND PLAT_MARK = #{platMark}
			</if>
	       <if test="@Ognl@isNotEmpty(level)">
				AND LEVEL = #{level}
			</if>
	       <if test="@Ognl@isNotEmpty(status)">
				AND STATUS = #{status}
			</if>
	       <if test="@Ognl@isNotEmpty(expand)">
				AND EXPAND = #{expand}
			</if>
		</where>
	</sql>
		
    <select id="count" resultType="long">
        SELECT count(*) FROM base_area 
		<include refid="dynamicWhere"/>    
    </select>
    
    <!--
    	分页查询已经使用Dialect进行分页,也可以不使用Dialect直接编写分页
    	因为分页查询将传 offset,pageSize,lastRows 三个参数,不同的数据库可以根于此三个参数属性应用不同的分页实现
    -->
    <select id="pageSelect" resultMap="baseAreaResult">
    	SELECT <include refid="commonColumns" />
	    FROM base_area 
		<include refid="dynamicWhere"/>
		
		<if test="@Ognl@isNotEmpty(sortColumns)">
			ORDER BY ${sortColumns}
		</if>
    </select>
    
    <select id="childrenSelect" resultMap="childrenResult">
    	SELECT <include refid="commonColumns" />
	    FROM base_area 
		WHERE PARENT_ID = #{id}
		ORDER BY ID ASC
    </select>
	
</mapper>

Mapperxml 解析

1、主查询语句为: pageSelect

2、结果映射 resultMap id="baseAreaResult" 

这里的关键在于:

<id property="id" column="ID"/>
<collection property="childrenList" javaType="java.util.ArrayList" column="id"
                    ofType="com.wanyu.smarthome.model.BaseArea" select="childrenSelect"></collection>

collection 标签中定义属性名称为 childrenList,对应实体类中的:childrenList

属性类型为:java.util.ArrayList

column="id" 将 id 列的值做为参数传递给子查询

ofType 定义List 中保存的数据类型

select 定义子查询

3、注意子查询 childrenSelect 对应的结果映射 childrenResult ,又包含了 collection 标签,形成了循环递归调用

小结

不知道我有没有描述清楚,有不明白的地方请留言。

强烈不推荐这种循环递归调用的写法,因为性能非常差。

最好是有几级就写几级,也就是写几个 resultMap。

本粟中是演示同一个表相同实体类的实现方式,同样也可以不同的表不同的实体类,只要把子节点类型修改一下就可以了。

为了性能,这种查询最好配合缓存使用。

======================文档信息======================

版权声明:非商用自由转载-保持署名-注明出处

署名(BY) :testcs_dn(微wx笑)

文章出处:[无知人生,记录点滴](http://blog.csdn.NET/testcs_dn)

==============本文首发于个人微信订阅号(微wx笑)============

目录
相关文章
|
18天前
|
SQL XML Java
mybatis复习04高级查询 一对多,多对一的映射处理,collection和association标签的使用
文章介绍了MyBatis中高级查询的一对多和多对一映射处理,包括创建数据库表、抽象对应的实体类、使用resultMap中的association和collection标签进行映射处理,以及如何实现级联查询和分步查询。此外,还补充了延迟加载的设置和用法。
mybatis复习04高级查询 一对多,多对一的映射处理,collection和association标签的使用
|
4天前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
17 2
|
6天前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
27 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
1月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
18天前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
2月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
2月前
|
XML JSON 数据库
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
这篇文章详细介绍了RESTful的概念、实现方式,以及如何在SpringMVC中使用HiddenHttpMethodFilter来处理PUT和DELETE请求,并通过具体代码案例分析了RESTful的使用。
SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)
|
2月前
|
前端开发 Java Spring
SpringMVC种通过追踪源码查看是哪种类型的视图渲染器(一般流程方法)
这篇文章通过示例代码展示了如何在Spring MVC中编写和注册拦截器,以及如何在拦截器的不同阶段添加业务逻辑。
SpringMVC种通过追踪源码查看是哪种类型的视图渲染器(一般流程方法)
|
2月前
|
前端开发 应用服务中间件 数据库
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
这篇文章通过一个具体的项目案例,详细讲解了如何使用SpringMVC、Thymeleaf、Bootstrap以及RESTful风格接口来实现员工信息的增删改查功能。文章提供了项目结构、配置文件、控制器、数据访问对象、实体类和前端页面的完整源码,并展示了实现效果的截图。项目的目的是锻炼使用RESTful风格的接口开发,虽然数据是假数据并未连接数据库,但提供了一个很好的实践机会。文章最后强调了这一章节主要是为了练习RESTful,其他方面暂不考虑。
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
|
2月前
|
JSON 前端开发 Java
Spring MVC返回JSON数据
综上所述,Spring MVC提供了灵活、强大的方式来支持返回JSON数据,从直接使用 `@ResponseBody`及 `@RestController`注解,到通过配置消息转换器和异常处理器,开发人员可以根据具体需求选择合适的实现方式。
109 4