MyBatis级联查询(一对多)复习巩固练习
为了帮助巩固MyBatis级联查询的知识点,设计了一个完整的练习方案,包含理论复习和实践操作。
一、理论知识点复习
1. 级联查询的核心概念
- 目的:在一个查询中获取关联表的数据
- 类型:一对一、一对多、多对多
- 实现方式:使用
<association>
和<collection>
标签
2. 核心标签解析
-
<association>
:用于处理一对一关系property
:Java对象中的属性名javaType
:关联对象的Java类型- 包含
<id>
和<result>
子标签
-
<collection>
:用于处理一对多关系property
:集合属性名ofType
:集合中元素的Java类型
3. SQL编写要点
- 必须使用别名(AS)区分不同表的相同列名
- JOIN查询比笛卡尔积更高效
- SELECT子句应包含所有需要映射的字段
二、实践练习任务
任务1:基础级联查询(学生→班级)
1. 数据库准备
CREATE DATABASE IF NOT EXISTS test11;
USE test11;
-- 班级表
CREATE TABLE class (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
-- 学生表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
cid INT,
FOREIGN KEY (cid) REFERENCES class(id)
);
-- 插入测试数据
INSERT INTO class (name) VALUES
('计算机科学与技术1班'),
('软件工程2班'),
('人工智能3班');
INSERT INTO student (name, cid) VALUES
('张三', 1),
('李四', 1),
('王五', 2),
('赵六', 3),
('钱七', 3);
2. 实体类实现
// Student.java
package com.test.entity;
public class Student {
private Integer id;
private String name;
private Class clazz; // 关联的班级对象
// 构造函数、getter/setter、toString
}
// Class.java
package com.test.entity;
public class Class {
private Integer id;
private String name;
// 构造函数、getter/setter、toString
}
3. Mapper接口
package com.test.repository;
import com.test.entity.Student;
public interface StudentRepository {
Student getStudentWithClass(Integer id);
}
4. Mapper XML配置
<!-- StudentRepository.xml -->
<resultMap id="studentClassMap" type="com.test.entity.Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="clazz" javaType="com.test.entity.Class">
<id property="id" column="cid"/>
<result property="name" column="cname"/>
</association>
</resultMap>
<select id="getStudentWithClass" parameterType="int" resultMap="studentClassMap">
SELECT
s.id AS sid,
s.name AS sname,
c.id AS cid,
c.name AS cname
FROM student s
JOIN class c ON s.cid = c.id
WHERE s.id = #{id}
</select>
5. 测试代码
public class StudentTest {
public static void main(String[] args) {
try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
StudentRepository mapper = sqlSession.getMapper(StudentRepository.class);
Student student = mapper.getStudentWithClass(1);
System.out.println(student);
System.out.println("班级信息: " + student.getClazz().getName());
}
}
}
任务2:反向级联查询(班级→学生)
1. 修改实体类
// Class.java
package com.test.entity;
import java.util.List;
public class Class {
private Integer id;
private String name;
private List<Student> students; // 班级下的学生集合
// 添加getter/setter
}
2. 新增Mapper接口
package com.test.repository;
import com.test.entity.Class;
public interface ClassRepository {
Class getClassWithStudents(Integer id);
}
3. Mapper XML配置
<!-- ClassRepository.xml -->
<resultMap id="classStudentMap" type="com.test.entity.Class">
<id property="id" column="cid"/>
<result property="name" column="cname"/>
<collection property="students" ofType="com.test.entity.Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
<select id="getClassWithStudents" parameterType="int" resultMap="classStudentMap">
SELECT
c.id AS cid,
c.name AS cname,
s.id AS sid,
s.name AS sname
FROM class c
LEFT JOIN student s ON c.id = s.cid
WHERE c.id = #{id}
</select>
4. 测试代码
public class ClassTest {
public static void main(String[] args) {
try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
ClassRepository mapper = sqlSession.getMapper(ClassRepository.class);
Class cls = mapper.getClassWithStudents(1);
System.out.println("班级: " + cls.getName());
System.out.println("学生列表:");
for (Student student : cls.getStudents()) {
System.out.println(" - " + student.getName());
}
}
}
}
任务3:扩展练习(课程-学生多对多关系)
1. 新增数据库表
-- 课程表
CREATE TABLE course (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
-- 学生选课表(多对多关系)
CREATE TABLE student_course (
student_id INT,
course_id INT,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES student(id),
FOREIGN KEY (course_id) REFERENCES course(id)
);
-- 插入课程数据
INSERT INTO course (name) VALUES
('Java程序设计'),
('数据库原理'),
('Web前端开发'),
('算法与数据结构');
-- 插入选课数据
INSERT INTO student_course (student_id, course_id) VALUES
(1, 1), (1, 2),
(2, 1), (2, 3),
(3, 2), (3, 4),
(4, 3), (4, 4),
(5, 1), (5, 4);
2. 实体类扩展
// Student.java 添加
private List<Course> courses;
// Course.java
package com.test.entity;
import java.util.List;
public class Course {
private Integer id;
private String name;
private List<Student> students;
// 构造函数、getter/setter、toString
}
3. 练习要求
- 实现查询学生时获取所选课程
- 实现查询课程时获取选课学生
- 使用
<collection>
处理多对多关系
4. 提示
<!-- 学生包含课程 -->
<collection property="courses" ofType="com.test.entity.Course">
<id property="id" column="course_id"/>
<result property="name" column="course_name"/>
</collection>
<!-- SQL示例 -->
SELECT
s.id, s.name,
c.id AS course_id, c.name AS course_name
FROM student s
JOIN student_course sc ON s.id = sc.student_id
JOIN course c ON sc.course_id = c.id
WHERE s.id = #{id}
三、调试技巧与常见问题
1. 常见错误排查
-
空指针异常:
- 检查getter/setter是否完整
- 确保SQL别名与resultMap中的column一致
-
映射失败:
- 使用MyBatis日志功能
- 在config.xml中添加:
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
-
SQL错误:
- 先在数据库客户端执行SQL验证正确性
- 检查表名、字段名大小写
2. 性能优化建议
-
延迟加载:
<setting name="lazyLoadingEnabled" value="true"/>
-
分页查询:
- 使用PageHelper插件
- 避免一次性加载大量数据
-
缓存策略:
<cache/>
四、综合练习
场景:学生管理系统
实现以下功能:
- 查询学生详情(包含班级信息)
- 查询班级详情(包含学生列表)
- 查询学生所选课程
- 按课程查询选课学生
要求:
- 使用MyBatis实现所有数据访问
- 合理设计实体类和Mapper
- 编写测试代码验证所有功能
- 使用日志记录SQL执行情况
扩展挑战:
- 添加分页功能
- 实现条件查询(如按班级查询学生)
- 添加事务管理
- 实现缓存优化
通过以上练习,您将能够:
- 熟练掌握MyBatis级联查询的配置
- 理解一对多、多对多关系的处理方式
- 掌握复杂SQL的编写技巧
- 提升MyBatis调试和优化能力
建议按照任务顺序逐步完成,遇到问题时回顾相关知识点,并使用调试技巧解决问题。完成基础练习后,尝试挑战综合练习以巩固所学知识。