如何仅用MyBatis注解实现嵌套结果解决N+1查询问题
解决方案:使用MyBatis注解实现嵌套结果映射(避免N+1查询)
1. 修正实体类定义
首先修正Question类中错误的构造函数,给集合字段添加默认值以简化映射:
data class Question( val id: Long, val title: String, val content: String, val skillNames: Set<String> = emptySet() // 添加默认值,无需额外构造函数 )
2. 重构QuestionMapper(移除SkillMapper)
通过单表关联查询+嵌套结果映射实现一次SQL获取所有数据,避免N+1问题:
@Mapper interface QuestionMapper { @Select(""" SELECT q.id AS question_id, q.title, q.content, s.name AS skill_name FROM question q LEFT JOIN skill s ON q.id = s.question_id """) @Results( id = "QuestionWithSkillsResultMap", value = [ // 标记主键字段,MyBatis将通过该字段合并重复的Question记录 Result(property = "id", column = "question_id", id = true), Result(property = "title", column = "title"), Result(property = "content", column = "content"), // 映射关联的技能名称集合 Result( property = "skillNames", javaType = Set::class, column = "question_id", // 关联主键,确保同一问题的技能被收集 many = Many(resultMap = "SkillNameResultMap") ) ] ) fun getQuestions(): List<Question> // 定义技能名称的单独映射(仅用于结果映射,无需调用) @Suppress("unused") @Results( id = "SkillNameResultMap", value = [Result(property = "", column = "skill_name")] ) fun mapSkillName(): String }
核心原理说明
- 关联查询:通过
LEFT JOIN一次性查询出所有问题及其关联技能,仅触发1次SQL请求,彻底解决N+1问题。 - 结果合并:标记
question_id为id=true,MyBatis会自动将同一问题的多条关联记录合并为一个Question对象。 - 集合映射:通过
@Many关联SkillNameResultMap,MyBatis会将同一问题的所有skill_name自动收集到skillNames集合中。
额外注意事项
- 若确认所有问题都有1-5个技能,可将
LEFT JOIN改为INNER JOIN以减少返回的空技能记录。 - 确保MyBatis配置中
mapUnderscoreToCamelCase参数为true(可选,若使用下划线命名的字段会自动映射为驼峰属性)。
内容的提问来源于stack exchange,提问作者SleepyX667
相关产品推荐
相关产品推荐

