MyBatis 关系映射完整指南
概述
在实际开发中,数据库表之间往往存在着各种关系。MyBatis 提供了强大的关系映射功能,可以优雅地处理表之间的一对一、一对多、多对多等复杂关系。本章将详细介绍如何在 MyBatis 中实现各种关系映射。
💡 核心概念:
- 一对一(One-to-One):一个实体对应另一个实体,如人与身份证
- 一对多(One-to-Many):一个实体对应多个其他实体,如部门与员工
- 多对多(Many-to-Many):多个实体对应多个其他实体,如学生与课程
关系映射基础
映射方式对比
MyBatis 提供了两种主要的关联查询方式:
| 映射方式 | 特点 | SQL 数量 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 嵌套查询 | 分步查询,支持延迟加载 | 多条简单SQL | 低 | 关联数据可选加载 |
| 嵌套结果 | 一次查询,立即加载 | 一条复杂SQL | 高 | 需要全部关联数据 |
核心标签说明
<!-- association: 用于一对一和多对一映射 -->
<association property="属性名" javaType="Java类型">
<!-- 映射配置 -->
</association>
<!-- collection: 用于一对多和多对多映射 -->
<collection property="属性名" ofType="集合元素类型">
<!-- 映射配置 -->
</collection>
一对一关系映射
场景描述
以人员(Person)和身份证(IdCard)的关系为例,一个人只有一个身份证,一个身份证只属于一个人。
数据库设计
-- 身份证表
CREATE TABLE tb_idcard (
id INT PRIMARY KEY AUTO_INCREMENT,
code VARCHAR(18) UNIQUE NOT NULL COMMENT '身份证号码'
);
-- 人员表
CREATE TABLE tb_person (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32) NOT NULL COMMENT '姓名',
age INT COMMENT '年龄',
sex VARCHAR(8) COMMENT '性别',
card_id INT UNIQUE COMMENT '身份证ID',
FOREIGN KEY(card_id) REFERENCES tb_idcard(id)
);
-- 插入测试数据
INSERT INTO tb_idcard(code) VALUES
('110101199001011234'),
('110102199002021234');
INSERT INTO tb_person(name, age, sex, card_id) VALUES
('张三', 25, '男', 1),
('李四', 23, '女', 2);
实体类设计
// IdCard.java
public class IdCard {
private Integer id;
private String code;
// getter/setter/toString 省略
}
// Person.java
public class Person {
private Integer id;
private String name;
private Integer age;
private String sex;
private IdCard card; // 一对一关系
// getter/setter/toString 省略
}
方式一:嵌套查询(分步查询)
<!-- IdCardMapper.xml -->
<mapper namespace="com.example.mapper.IdCardMapper">
<!-- 根据ID查询身份证信息 -->
<select id="selectIdCardById" resultType="IdCard">
SELECT * FROM tb_idcard WHERE id = #{id}
</select>
</mapper>
<!-- PersonMapper.xml -->
<mapper namespace="com.example.mapper.PersonMapper">
<resultMap id="personResultMap" type="Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<!-- 一对一关联:嵌套查询 -->
<association property="card" column="card_id"
javaType="IdCard"
select="com.example.mapper.IdCardMapper.selectIdCardById"/>
</resultMap>
<select id="selectPersonById" resultMap="personResultMap">
SELECT * FROM tb_person WHERE id = #{id}
</select>
</mapper>
方式二:嵌套结果(联合查询)
<!-- PersonMapper.xml -->
<mapper namespace="com.example.mapper.PersonMapper">
<resultMap id="personResultMap2" type="Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<!-- 一对一关联:嵌套结果 -->
<association property="card" javaType="IdCard">
<id property="id" column="card_id"/>
<result property="code" column="code"/>
</association>
</resultMap>
<select id="selectPersonWithCard" resultMap="personResultMap2">
SELECT p.*, c.code
FROM tb_person p
LEFT JOIN tb_idcard c ON p.card_id = c.id
WHERE p.id = #{id}
</select>
</mapper>
测试代码
@Test
public void testOneToOne() {
try (SqlSession session = sqlSessionFactory.openSession()) {
PersonMapper mapper = session.getMapper(PersonMapper.class);
// 测试嵌套查询
Person person1 = mapper.selectPersonById(1);
System.out.println("嵌套查询结果:" + person1);
// 测试嵌套结果
Person person2 = mapper.selectPersonWithCard(1);
System.out.println("嵌套结果查询:" + person2);
}
}
一对多关系映射
场景描述
以部门(Department)和员工(Employee)的关系为例,一个部门有多个员工,一个员工只属于一个部门。
数据库设计
-- 部门表
CREATE TABLE tb_department (
id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(50) NOT NULL COMMENT '部门名称',
location VARCHAR(100) COMMENT '部门位置'
);
-- 员工表
CREATE TABLE tb_employee (
id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(32) NOT NULL COMMENT '员工姓名',
position VARCHAR(50) COMMENT '职位',
salary DECIMAL(10,2) COMMENT '薪资',
dept_id INT COMMENT '部门ID',
FOREIGN KEY(dept_id) REFERENCES tb_department(id)
);
-- 插入测试数据
INSERT INTO tb_department(dept_name, location) VALUES
('技术部', '北京'),
('销售部', '上海'),
('人事部', '广州');
INSERT INTO tb_employee(emp_name, position, salary, dept_id) VALUES
('王五', 'Java工程师', 15000, 1),
('赵六', '前端工程师', 13000, 1),
('钱七', 'Python工程师', 16000, 1),
('孙八', '销售经理', 12000, 2),
('周九', '销售专员', 8000, 2);
实体类设计
// Department.java
public class Department {
private Integer id;
private String deptName;
private String location;
private List<Employee> employees; // 一对多关系
// getter/setter/toString 省略
}
// Employee.java
public class Employee {
private Integer id;
private String empName;
private String position;
private BigDecimal salary;
private Integer deptId;
private Department department; // 多对一关系
// getter/setter/toString 省略
}
一对多映射:嵌套查询
<!-- EmployeeMapper.xml -->
<mapper namespace="com.example.mapper.EmployeeMapper">
<!-- 根据部门ID查询员工列表 -->
<select id="selectEmployeesByDeptId" resultType="Employee">
SELECT * FROM tb_employee WHERE dept_id = #{deptId}
</select>
</mapper>
<!-- DepartmentMapper.xml -->
<mapper namespace="com.example.mapper.DepartmentMapper">
<resultMap id="deptWithEmployeesMap" type="Department">
<id property="id" column="id"/>
<result property="deptName" column="dept_name"/>
<result property="location" column="location"/>
<!-- 一对多关联:嵌套查询 -->
<collection property="employees" column="id"
ofType="Employee"
select="com.example.mapper.EmployeeMapper.selectEmployeesByDeptId"/>
</resultMap>
<select id="selectDeptWithEmployees" resultMap="deptWithEmployeesMap">
SELECT * FROM tb_department WHERE id = #{id}
</select>
</mapper>
一对多映射:嵌套结果
<!-- DepartmentMapper.xml -->
<mapper namespace="com.example.mapper.DepartmentMapper">
<resultMap id="deptWithEmployeesMap2" type="Department">
<id property="id" column="dept_id"/>
<result property="deptName" column="dept_name"/>
<result property="location" column="location"/>
<!-- 一对多关联:嵌套结果 -->
<collection property="employees" ofType="Employee">
<id property="id" column="emp_id"/>
<result property="empName" column="emp_name"/>
<result property="position" column="position"/>
<result property="salary" column="salary"/>
<result property="deptId" column="dept_id"/>
</collection>
</resultMap>
<select id="selectDeptWithEmployees2" resultMap="deptWithEmployeesMap2">
SELECT
d.id as dept_id,
d.dept_name,
d.location,
e.id as emp_id,
e.emp_name,
e.position,
e.salary
FROM tb_department d
LEFT JOIN tb_employee e ON d.id = e.dept_id
WHERE d.id = #{id}
</select>
</mapper>
多对一映射
<!-- EmployeeMapper.xml -->
<mapper namespace="com.example.mapper.EmployeeMapper">
<resultMap id="empWithDeptMap" type="Employee">
<id property="id" column="id"/>
<result property="empName" column="emp_name"/>
<result property="position" column="position"/>
<result property="salary" column="salary"/>
<!-- 多对一关联 -->
<association property="department" javaType="Department">
<id property="id" column="dept_id"/>
<result property="deptName" column="dept_name"/>
<result property="location" column="location"/>
</association>
</resultMap>
<select id="selectEmpWithDept" resultMap="empWithDeptMap">
SELECT
e.*,
d.dept_name,
d.location
FROM tb_employee e
LEFT JOIN tb_department d ON e.dept_id = d.id
WHERE e.id = #{id}
</select>
</mapper>
测试代码
@Test
public void testOneToMany() {
try (SqlSession session = sqlSessionFactory.openSession()) {
DepartmentMapper deptMapper = session.getMapper(DepartmentMapper.class);
// 测试一对多查询
Department dept = deptMapper.selectDeptWithEmployees(1);
System.out.println("部门:" + dept.getDeptName());
System.out.println("员工数量:" + dept.getEmployees().size());
dept.getEmployees().forEach(System.out::println);
}
}
@Test
public void testManyToOne() {
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper empMapper = session.getMapper(EmployeeMapper.class);
// 测试多对一查询
Employee emp = empMapper.selectEmpWithDept(1);
System.out.println("员工:" + emp.getEmpName());
System.out.println("所属部门:" + emp.getDepartment().getDeptName());
}
}
多对多关系映射
场景描述
以学生(Student)和课程(Course)的关系为例,一个学生可以选多门课程,一门课程可以被多个学生选择。
数据库设计
-- 学生表
CREATE TABLE tb_student (
id INT PRIMARY KEY AUTO_INCREMENT,
stu_name VARCHAR(32) NOT NULL COMMENT '学生姓名',
stu_no VARCHAR(20) UNIQUE COMMENT '学号',
grade VARCHAR(20) COMMENT '年级'
);
-- 课程表
CREATE TABLE tb_course (
id INT PRIMARY KEY AUTO_INCREMENT,
course_name VARCHAR(50) NOT NULL COMMENT '课程名称',
credit INT COMMENT '学分',
teacher VARCHAR(32) COMMENT '授课教师'
);
-- 选课表(中间表)
CREATE TABLE tb_student_course (
student_id INT NOT NULL,
course_id INT NOT NULL,
score DECIMAL(5,2) COMMENT '成绩',
PRIMARY KEY(student_id, course_id),
FOREIGN KEY(student_id) REFERENCES tb_student(id),
FOREIGN KEY(course_id) REFERENCES tb_course(id)
);
-- 插入测试数据
INSERT INTO tb_student(stu_name, stu_no, grade) VALUES
('小明', '2021001', '大一'),
('小红', '2021002', '大一'),
('小刚', '2021003', '大二');
INSERT INTO tb_course(course_name, credit, teacher) VALUES
('Java程序设计', 4, '张老师'),
('数据库原理', 3, '李老师'),
('Web开发', 3, '王老师'),
('数据结构', 4, '赵老师');
INSERT INTO tb_student_course(student_id, course_id, score) VALUES
(1, 1, 85.5),
(1, 2, 90.0),
(1, 3, 88.5),
(2, 1, 92.0),
(2, 3, 87.5),
(3, 2, 89.0),
(3, 4, 91.5);
实体类设计
// Student.java
public class Student {
private Integer id;
private String stuName;
private String stuNo;
private String grade;
private List<Course> courses; // 多对多关系
// getter/setter/toString 省略
}
// Course.java
public class Course {
private Integer id;
private String courseName;
private Integer credit;
private String teacher;
private List<Student> students; // 多对多关系
private BigDecimal score; // 中间表的成绩字段
// getter/setter/toString 省略
}
学生查询课程(多对多)
<!-- CourseMapper.xml -->
<mapper namespace="com.example.mapper.CourseMapper">
<!-- 根据学生ID查询课程列表 -->
<select id="selectCoursesByStudentId" resultType="Course">
SELECT
c.*,
sc.score
FROM tb_course c
INNER JOIN tb_student_course sc ON c.id = sc.course_id
WHERE sc.student_id = #{studentId}
</select>
</mapper>
<!-- StudentMapper.xml -->
<mapper namespace="com.example.mapper.StudentMapper">
<!-- 方式一:嵌套查询 -->
<resultMap id="studentWithCoursesMap" type="Student">
<id property="id" column="id"/>
<result property="stuName" column="stu_name"/>
<result property="stuNo" column="stu_no"/>
<result property="grade" column="grade"/>
<collection property="courses" column="id"
ofType="Course"
select="com.example.mapper.CourseMapper.selectCoursesByStudentId"/>
</resultMap>
<select id="selectStudentWithCourses" resultMap="studentWithCoursesMap">
SELECT * FROM tb_student WHERE id = #{id}
</select>
<!-- 方式二:嵌套结果 -->
<resultMap id="studentWithCoursesMap2" type="Student">
<id property="id" column="stu_id"/>
<result property="stuName" column="stu_name"/>
<result property="stuNo" column="stu_no"/>
<result property="grade" column="grade"/>
<collection property="courses" ofType="Course">
<id property="id" column="course_id"/>
<result property="courseName" column="course_name"/>
<result property="credit" column="credit"/>
<result property="teacher" column="teacher"/>
<result property="score" column="score"/>
</collection>
</resultMap>
<select id="selectStudentWithCourses2" resultMap="studentWithCoursesMap2">
SELECT
s.id as stu_id,
s.stu_name,
s.stu_no,
s.grade,
c.id as course_id,
c.course_name,
c.credit,
c.teacher,
sc.score
FROM tb_student s
LEFT JOIN tb_student_course sc ON s.id = sc.student_id
LEFT JOIN tb_course c ON sc.course_id = c.id
WHERE s.id = #{id}
</select>
</mapper>
课程查询学生(多对多)
<!-- CourseMapper.xml -->
<mapper namespace="com.example.mapper.CourseMapper">
<resultMap id="courseWithStudentsMap" type="Course">
<id property="id" column="course_id"/>
<result property="courseName" column="course_name"/>
<result property="credit" column="credit"/>
<result property="teacher" column="teacher"/>
<collection property="students" ofType="Student">
<id property="id" column="stu_id"/>
<result property="stuName" column="stu_name"/>
<result property="stuNo" column="stu_no"/>
<result property="grade" column="grade"/>
</collection>
</resultMap>
<select id="selectCourseWithStudents" resultMap="courseWithStudentsMap">
SELECT
c.id as course_id,
c.course_name,
c.credit,
c.teacher,
s.id as stu_id,
s.stu_name,
s.stu_no,
s.grade
FROM tb_course c
LEFT JOIN tb_student_course sc ON c.id = sc.course_id
LEFT JOIN tb_student s ON sc.student_id = s.id
WHERE c.id = #{id}
</select>
</mapper>
测试代码
@Test
public void testManyToMany() {
try (SqlSession session = sqlSessionFactory.openSession()) {
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
CourseMapper courseMapper = session.getMapper(CourseMapper.class);
// 测试学生查询课程
Student student = studentMapper.selectStudentWithCourses(1);
System.out.println("学生:" + student.getStuName());
System.out.println("选修课程数:" + student.getCourses().size());
student.getCourses().forEach(course ->
System.out.println(" " + course.getCourseName() + " - 成绩:" + course.getScore())
);
System.out.println("\n------------------------\n");
// 测试课程查询学生
Course course = courseMapper.selectCourseWithStudents(1);
System.out.println("课程:" + course.getCourseName());
System.out.println("选课学生数:" + course.getStudents().size());
course.getStudents().forEach(stu ->
System.out.println(" " + stu.getStuName() + " - " + stu.getGrade())
);
}
}
高级映射技巧
1. 延迟加载配置
<!-- mybatis-config.xml -->
<configuration>
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 触发延迟加载的方法 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
</configuration>
<!-- Mapper中配置 -->
<association property="department"
select="selectDepartment"
column="dept_id"
fetchType="lazy"/> <!-- 延迟加载 -->
2. 鉴别器(Discriminator)
用于根据某个字段的值来决定使用哪个映射规则。
<resultMap id="vehicleResultMap" type="Vehicle">
<id property="id" column="id"/>
<result property="brand" column="brand"/>
<discriminator javaType="string" column="vehicle_type">
<case value="CAR" resultType="Car">
<result property="doorCount" column="door_count"/>
</case>
<case value="TRUCK" resultType="Truck">
<result property="loadCapacity" column="load_capacity"/>
</case>
</discriminator>
</resultMap>
3. 多结果集处理
处理存储过程返回的多个结果集。
<select id="selectUserAndOrders" statementType="CALLABLE">
{call getUserAndOrders(#{userId, mode=IN, jdbcType=INTEGER})}
</select>
<resultMap id="userResult" type="User">
<!-- User映射 -->
</resultMap>
<resultMap id="orderResult" type="Order">
<!-- Order映射 -->
</resultMap>
<!-- 结果集映射 -->
<resultSets>
<resultSet name="users" resultMap="userResult"/>
<resultSet name="orders" resultMap="orderResult"/>
</resultSets>
4. 构造方法映射
使用构造方法创建实例。
<resultMap id="userConstructorMap" type="User">
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="string"/>
<arg column="email" javaType="string"/>
</constructor>
<association property="profile" javaType="UserProfile">
<!-- Profile映射 -->
</association>
</resultMap>
性能优化
1. N+1 查询问题
问题描述
使用嵌套查询时,查询N个主记录会额外执行N次关联查询。
解决方案
<!-- 方案1:使用嵌套结果代替嵌套查询 -->
<select id="selectDeptWithEmployees" resultMap="deptWithEmpResultMap">
SELECT d.*, e.*
FROM department d
LEFT JOIN employee e ON d.id = e.dept_id
</select>
<!-- 方案2:使用批量查询 -->
<select id="selectEmployeesByDeptIds" resultType="Employee">
SELECT * FROM employee
WHERE dept_id IN
<foreach collection="list" item="deptId" open="(" separator="," close=")">
#{deptId}
</foreach>
</select>
2. 合理使用缓存
<!-- 开启二级缓存 -->
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"/>
<!-- 针对特定查询关闭缓存 -->
<select id="selectRealTimeData" useCache="false">
SELECT * FROM real_time_table
</select>
3. 查询优化技巧
// 只查询需要的字段
@Select("SELECT id, name FROM user WHERE id = #{id}")
User selectUserBasicInfo(int id);
// 分页查询
@Select("SELECT * FROM user LIMIT #{offset}, #{limit}")
List<User> selectUsersByPage(@Param("offset") int offset,
@Param("limit") int limit);
// 使用索引字段查询
@Select("SELECT * FROM user WHERE email = #{email}") // email有索引
User selectUserByEmail(String email);
最佳实践
1. 选择合适的映射方式
// 推荐:关联数据总是需要时,使用嵌套结果
public interface UserMapper {
@Select("SELECT u.*, p.* FROM user u LEFT JOIN profile p ON u.id = p.user_id WHERE u.id = #{id}")
@ResultMap("userWithProfileMap")
User selectUserWithProfile(int id);
}
// 推荐:关联数据可选时,使用嵌套查询 + 延迟加载
public interface OrderMapper {
@Select("SELECT * FROM orders WHERE id = #{id}")
@ResultMap("orderWithLazyItemsMap")
Order selectOrderWithLazyItems(int id);
}
2. 避免循环引用
// User.java
public class User {
private List<Order> orders;
// 不要在Order中再引用User,避免循环
}
// Order.java
public class Order {
private Integer userId; // 只保存ID,不保存User对象
}
3. 合理设计ResultMap
<!-- 基础ResultMap,供继承使用 -->
<resultMap id="baseUserMap" type="User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
</resultMap>
<!-- 继承基础ResultMap -->
<resultMap id="userDetailMap" type="User" extends="baseUserMap">
<association property="profile" resultMap="profileMap"/>
<collection property="orders" resultMap="orderMap"/>
</resultMap>
4. 使用DTO优化传输
// UserDTO.java - 针对特定场景的数据传输对象
public class UserDTO {
private Integer id;
private String username;
private String departmentName; // 直接包含关联表的必要字段
private Integer orderCount; // 统计信息
}
// Mapper查询
@Select("SELECT u.id, u.username, d.name as departmentName, COUNT(o.id) as orderCount " +
"FROM user u " +
"LEFT JOIN department d ON u.dept_id = d.id " +
"LEFT JOIN orders o ON u.id = o.user_id " +
"GROUP BY u.id, u.username, d.name")
List<UserDTO> selectUserSummary();
常见问题
问题 1:关联查询结果为 null
原因分析:
- 数据库中确实没有关联数据
- ResultMap 配置错误
- 列名映射错误
解决方案:
<!-- 检查列别名是否正确 -->
<resultMap id="userMap" type="User">
<id property="id" column="user_id"/> <!-- 确保列名正确 -->
<association property="profile" javaType="Profile">
<id property="id" column="profile_id"/> <!-- 使用别名避免冲突 -->
</association>
</resultMap>
<!-- SQL中使用别名 -->
<select id="selectUser" resultMap="userMap">
SELECT
u.id as user_id,
p.id as profile_id,
p.avatar
FROM user u
LEFT JOIN profile p ON u.id = p.user_id
</select>
问题 2:集合映射重复数据
原因分析: 一对多查询时,主表数据会因为从表的多条记录而重复。
解决方案:
<!-- 确保主表有正确的ID映射 -->
<resultMap id="deptMap" type="Department">
<id property="id" column="dept_id"/> <!-- 必须配置ID -->
<collection property="employees" ofType="Employee">
<id property="id" column="emp_id"/> <!-- 子表也要配置ID -->
</collection>
</resultMap>
问题 3:延迟加载失效
原因分析:
- 全局配置未开启
- 在同一个SqlSession中已经加载过
- 使用了触发加载的方法
解决方案:
// 正确使用延迟加载
try (SqlSession session = sqlSessionFactory.openSession()) {
User user = session.selectOne("selectUser", 1);
// 此时profile未加载
// 访问关联对象时才加载
UserProfile profile = user.getProfile();
}
// 避免在toString()中访问延迟加载属性
@Override
public String toString() {
return "User{id=" + id + ", username=" + username + "}";
// 不要包含 profile
}
问题 4:多对多查询性能问题
解决方案:
// 分页查询
public List<Student> selectStudentsWithCourses(int offset, int limit) {
// 先查询学生
List<Student> students = studentMapper.selectStudentsByPage(offset, limit);
// 收集学生ID
List<Integer> studentIds = students.stream()
.map(Student::getId)
.collect(Collectors.toList());
// 批量查询课程
List<Map<String, Object>> studentCourses =
courseMapper.selectCoursesByStudentIds(studentIds);
// 组装结果
// ...
}
总结
MyBatis 的关系映射功能强大而灵活,掌握好以下要点可以帮助我们更好地使用:
🎯 核心要点
- 理解两种映射方式:嵌套查询支持延迟加载,嵌套结果一次加载所有数据
- 正确使用标签:association 用于一对一/多对一,collection 用于一对多/多对多
- 注意性能问题:避免 N+1 查询,合理使用缓存
- 保持简单设计:避免过深的嵌套和循环引用
📊 选择建议
- 简单关系:使用嵌套结果,一次查询获取所有数据
- 复杂关系:使用嵌套查询 + 延迟加载,按需加载数据
- 高性能要求:使用 DTO + 自定义查询,精确控制查询内容
相关文章
前后章节导航
MyBatis 核心技术
- Web程序设计笔记10——第六章:初识MyBatis - MyBatis 基础入门
- Web程序设计笔记11——第七章:MyBatis核心配置 - 配置详解
- Web程序设计笔记12——第八章:动态SQL - 动态SQL应用
Spring 整合相关
- Web程序设计笔记01——第一章:Spring的基本应用 - Spring 框架基础
- Spring Bean 管理完整指南 - Bean 管理深入理解
- Web程序设计笔记14——第十章:Spring 和 MyBatis 的整合 - SSM 整合
SpringBoot 进阶学习
- 整合JUnit、Mybatis - SpringBoot 整合 MyBatis
- 整合MyBatis-plus、Druid - 高级持久层框架
数据库设计与优化
- Web程序设计笔记08——第四章:Spring的数据库开发 - Spring数据库开发
- Spring 事务管理完整指南 - 事务处理
- 关于jdbc连接mysql URL上的常见问题 - 连接问题解决
性能优化相关
- N+1 查询问题解决方案
- MyBatis 缓存机制详解
- 数据库索引优化策略
- 复杂查询性能调优