Bootstrap

SpringBoot开发——使用MyBatis实现三级树查询

文章目录

  • 引言
  • 数据库设计
  • 实体类设计
  • Mapper接口设计
  • XML映射文件实现
  • Service层实现
  • Controller层实现
  • 性能优化建议
    • 1、缓存优化
    • 2.SQL优化
    • 3.内存优化
  • 总结

引言

在实际项目开发中,树形结构的数据查询是一个非常常见的需求。比如组织架构、菜单管理、地区选择等场景都需要处理树形数据。本文将详细讲解如何使用MyBatis实现三级树形数据的查询,从数据库设计到具体代码实现,帮助大家掌握树形数据处理的核心要点。

数据库设计

首先,我们需要设计一个合适的数据库表结构来存储树形数据。以下是一个典型的树形表结构:

CREATE TABLE `sys_area` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父级ID',
  `name` varchar(50) NOT NULL COMMENT '地区名称',
  `level` int(11) NOT NULL COMMENT '层级(1:省份 2:城市 3:区县)',
  `sort` int(11) DEFAULT 0 COMMENT '排序号',
  `status` tinyint(4) DEFAULT 1 COMMENT '状态(0:禁用 1:启用)',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='地区表';

实体类设计

接下来,我们需要创建对应的实体类。为了支持树形结构,我们需要添加一个children属性来存储子节点:

@Data
public class Area implements Serializable {
    private static final long serialVersionUID = 1L;
    
    // 主键ID
    private Long id;
    
    // 父级ID
    private Long parentId;
    
    // 地区名称
    private String name;
    
    // 层级
    private Integer level;
    
    // 排序号
    private Integer sort;
    
    // 状态
    private Integer status;
    
    // 创建时间
    private Date createTime;
    
    // 更新时间
    private Date updateTime;
    
    // 子节点列表
    private List<Area> children;
}

Mapper接口设计

创建AreaMapper接口,定义必要的查询方法:

@Mapper
public interface AreaMapper {
    /**
     * 查询所有地区数据
     * @return 地区列表
     */
    List<Area> selectAllAreas();
    
    /**
     * 根据父ID查询子地区
     * @param parentId 父级ID
     * @return 子地区列表
     */
    List<Area> selectAreasByParentId(Long parentId);
    
    /**
     * 查询指定层级的地区
     * @param level 层级
     * @return 地区列表
     */
    List<Area> selectAreasByLevel(Integer level);
}

XML映射文件实现

resources目录下创建AreaMapper.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.AreaMapper">
    
    <!-- 基础列 -->
    <sql id="Base_Column_List">
        id, parent_id, name, level, sort, status, create_time, update_time
    </sql>
    
    <!-- 查询所有地区 -->
    <select id="selectAllAreas" resultType="com.example.entity.Area">
        SELECT 
        <include refid="Base_Column_List"/>
        FROM sys_area 
        WHERE status = 1 
        ORDER BY sort ASC, id ASC
    </select>
    
    <!-- 根据父ID查询子地区 -->
    <select id="selectAreasByParentId" resultType="com.example.entity.Area">
        SELECT 
        <include refid="Base_Column_List"/>
        FROM sys_area 
        WHERE status = 1 
        AND parent_id = #{parentId}
        ORDER BY sort ASC, id ASC
    </select>
    
    <!-- 查询指定层级的地区 -->
    <select id="selectAreasByLevel" resultType="com.example.entity.Area">
        SELECT 
        <include refid="Base_Column_List"/>
        FROM sys_area 
        WHERE status = 1 
        AND level = #{level}
        ORDER BY sort ASC, id ASC
    </select>
</mapper>

Service层实现

创建Service接口及其实现类:

@Service
@Slf4j
public class AreaServiceImpl implements AreaService {
    
    @Autowired
    private AreaMapper areaMapper;
    
    @Override
    public List<Area> buildAreaTree() {
        // 查询所有地区数据
        List<Area> allAreas = areaMapper.selectAllAreas();
        
        // 构建树形结构
        return buildTree(allAreas);
    }
    
    /**
     * 构建树形结构
     * @param areas 地区列表
     * @return 树形结构的地区列表
     */
    private List<Area> buildTree(List<Area> areas) {
        List<Area> trees = new ArrayList<>();
        
        // 获取所有根节点(省份)
        areas.stream()
            .filter(area -> area.getLevel() == 1)
            .forEach(province -> {
                // 设置省份的子节点(城市)
                List<Area> cities = getChildren(areas, province.getId());
                province.setChildren(cities);
                
                // 设置城市的子节点(区县)
                cities.forEach(city -> {
                    List<Area> districts = getChildren(areas, city.getId());
                    city.setChildren(districts);
                });
                
                trees.add(province);
            });
            
        return trees;
    }
    
    /**
     * 获取子节点
     * @param areas 所有地区列表
     * @param parentId 父级ID
     * @return 子节点列表
     */
    private List<Area> getChildren(List<Area> areas, Long parentId) {
        return areas.stream()
            .filter(area -> Objects.equals(area.getParentId(), parentId))
            .collect(Collectors.toList());
    }
}

Controller层实现

最后,创建Controller处理前端请求:


@RestController
@RequestMapping("/api/areas")
public class AreaController {
    
    @Autowired
    private AreaService areaService;
    
    /**
     * 获取地区树形数据
     */
    @GetMapping("/tree")
    public ResponseResult<List<Area>> getAreaTree() {
        try {
            List<Area> trees = areaService.buildAreaTree();
            return ResponseResult.success(trees);
        } catch (Exception e) {
            log.error("获取地区树形数据失败", e);
            return ResponseResult.error("获取地区树形数据失败");
        }
    }
}

性能优化建议

1、缓存优化

  • 考虑使用Redis缓存树形数据,因为地区数据变动频率较低
  • 可以设置合理的缓存过期时间,如24小时
@Service
public class AreaServiceImpl implements AreaService {
    
    @Autowired
    private RedisTemplate<String, List<Area>> redisTemplate;
    
    private static final String AREA_TREE_KEY = "AREA:TREE";
    private static final long CACHE_TIMEOUT = 24; // 小时
    
    @Override
    public List<Area> buildAreaTree() {
        // 先从缓存获取
        List<Area> cacheTree = redisTemplate.opsForValue().get(AREA_TREE_KEY);
        if (cacheTree != null) {
            return cacheTree;
        }
        
        // 缓存未命中,查询数据库并构建树
        List<Area> trees = buildTreeFromDb();
        
        // 放入缓存
        redisTemplate.opsForValue().set(AREA_TREE_KEY, trees, CACHE_TIMEOUT, TimeUnit.HOURS);
        
        return trees;
    }
}

2.SQL优化

  • 适当添加索引:parent_id, level, status等字段
  • 考虑使用批量查询替代循环查询

3.内存优化

  • 使用Stream API时注意及时关闭
  • 合理设置集合的初始容量
  • 及时释放不再使用的对象引用

总结

通过本文的讲解,我们实现了一个完整的三级树形数据查询功能。关键要点包括:

  1. 合理的数据库表设计,包含必要的字段和索引
  2. 清晰的实体类设计,支持树形结构
  3. MyBatis映射文件的编写,实现基础的数据查询
  4. Service层的树形构建算法实现
  5. 性能优化和缓存的使用
;