更新记录
20231207:第一版
20240309: 补充章节 “
resultMap 重点补充
”20241122:重构系列
配置环境
导入数据
-- 创建学生表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT, -- 学生ID,主键,自增
name VARCHAR(50) NOT NULL, -- 学生姓名
age INT NOT NULL, -- 学生年龄
teacher_id INT -- 所属老师ID
);
-- 创建老师表
CREATE TABLE teacher (
id INT PRIMARY KEY AUTO_INCREMENT, -- 老师ID,主键,自增
name VARCHAR(50) NOT NULL -- 老师姓名
);
INSERT INTO student (id, name, age, teacher_id) VALUES
(1, '小明', 16, 1), -- 小明 对应 张老师
(2, '小红', 17, 1), -- 小红 对应 张老师
(3, '小华', 15, 2), -- 小华 对应 李老师
(4, '小丽', 16, 2), -- 小丽 对应 李老师
(5, '小强', 17, 3), -- 小强 对应 王老师
(6, '小芳', 16, 3), -- 小芳 对应 王老师
(7, '小亮', 15, 4), -- 小亮 对应 赵老师
(8, '小霞', 17, 4); -- 小霞 对应 赵老师
INSERT INTO teacher (id, name) VALUES
(1, '张老师'),
(2, '李老师'),
(3, '王老师'),
(4, '赵老师');
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 组名 -->
<groupId>org.example</groupId>
<!-- 项目ID -->
<artifactId>demo</artifactId>
<!-- 项目版本 -->
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.18</spring-boot.version>
<alibaba.druid.version>1.2.21</alibaba.druid.version>
<mybatis.plus.boot.version>3.5.3.2</mybatis.plus.boot.version>
<mysql.boot.version>8.0.33</mysql.boot.version>
<lombok.version>1.18.30</lombok.version>
</properties>
<!-- 管理依赖版本 -->
<dependencyManagement>
<dependencies>
<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${alibaba.druid.version}</version>
</dependency>
<!-- MP -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.boot.version}</version>
</dependency>
<!-- MYSQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.boot.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 应用依赖 -->
<dependencies>
<!-- Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- MP -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MYSQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
添加配置文件
# 应用服务 WEB 访问端口
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/spring_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
Studentname: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
cache-enabled: true
创建 entity 实体类
package org.example.entity;
import lombok.Data;
@Data
public class Student {
private Integer id;
private String name;
private Integer age;
private Integer teacherId;
}
package org.example.entity;
import lombok.Data;
@Data
public class Teacher {
private Integer id;
private String name;
}
创建 org.example/mapper/StudentMapper.java
package org.example.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.example.entity.Student;
import java.util.List;
@Mapper
public interface StudentMapper {
// 查询全部学生
List<Student> selectStudent();
// 根据id查询学生
Student selectStudentById(int id);
}
创建 XML:resources/mapper/StudentMapper.xml
,添加 Select 语句,通过 #{}
方式获取参数。
<?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="org.example.mapper.StudentMapper">
<select id="selectStudent" resultType="org.example.entity.Student">
select *
from student
</select>
<select id="selectStudentById" resultType="org.example.entity.Student">
select *
from student
where id = #{id}
</select>
</mapper>
调用
@Data
@RestController
@Slf4j
public class HelloController {
@Autowired
private StudentMapper studentMapper;
@GetMapping("/")
public String show() {
System.out.println(studentMapper.selectStudent());
System.out.println(studentMapper.selectStudentById(2));
return "show";
}
}
输出
[Student(id=1, name=小明, age=16, teacherId=1), Student(id=2, name=小红, age=17, teacherId=1), Student(id=3, name=小华, age=15, teacherId=2), Student(id=4, name=小丽, age=16, teacherId=2), Student(id=5, name=小强, age=17, teacherId=3), Student(id=6, name=小芳, age=16, teacherId=3), Student(id=7, name=小亮, age=15, teacherId=4), Student(id=8, name=小霞, age=17, teacherId=4)]
Student(id=2, name=小红, age=17, teacherId=1)
CURD
查询
当参数比较多的情况下,key 通过 Map 的方式传递。
在 StudentMapper
中添加对应方法
@Mapper
public interface StudentMapper {
// 查询全部学生
List<Student> selectStudent();
// 根据id查询学生
Student selectStudentById(int id);
// 通过 Map
Student selectStudentByMap(Map<String, Object> map);
}
在 StudentMapper.xml
中添加 Select
语句
<?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="org.example.mapper.StudentMapper">
<!-- 1. 查询所有 -->
<select id="selectStudent" resultType="org.example.entity.Student">
select *
from student
</select>
<!-- 2. 查询ID -->
<select id="selectStudentById" resultType="org.example.entity.Student">
select *
from student
where id = #{id}
</select>
<!-- 3. 使用 Map 查询 -->
<select id="selectStudentByMap" resultType="org.example.entity.Student">
select *
from student
where id = #{id} and name = #{name}
</select>
</mapper>
开始测试
@Data
@RestController
@Slf4j
public class HelloController {
@Autowired
private StudentMapper studentMapper;
@GetMapping("/")
public String show() {
System.out.println(studentMapper.selectStudent());
System.out.println(studentMapper.selectStudentById(2));
Map<String, Object> map = new HashMap<>();
map.put("id", 3);
map.put("name", "小华");
System.out.println(studentMapper.selectStudentByMap(map));
return "show";
}
}
增加
在 StudentMapper
中添加对应方法
int addStudent(Student Student);
你可能疑问,为什么下面的 teacherId
为什么不能写成 teacher_id
? 因为他是对应实体类 Student
,这一点我会在下一节自定义映射会说明。
<insert id="addStudent" parameterType="org.example.entity.Student">
insert into student (name, age, teacher_id)
values (#{name}, #{age}, #{teacherId})
</insert>
开始插入数据
@Data
@RestController
@Slf4j
public class HelloController {
@Autowired
private StudentMapper studentMapper;
@GetMapping("/")
public String show() {
Student Student = new Student();
Student.setName("测试");
Student.setAge(22);
Student.setTeacherId(3);
System.out.println(studentMapper.addStudent(Student));
return "show";
}
}
修改
在 StudentMapper
中添加对应方法
int updateStudent(Student Student);
在 StudentMapper.xml
中添加 update
语句
<update id="updateStudent" parameterType="org.example.entity.Student">
update student
set name=#{name}
where id = #{id}
</update>
开始插入数据
@Data
@RestController
@Slf4j
public class HelloController {
@Autowired
private StudentMapper studentMapper;
@GetMapping("/")
public String show() {
Student Student = new Student();
Student.setId(1);
Student.setName("小明-update");
System.out.println(studentMapper.updateStudent(Student));
return "show";
}
}
删除
在 StudentMapper
中添加对应方法
int deleteStudent(int id);
在 StudentMapper.xml
中添加 Select
语句
<update id="deleteStudent" parameterType="org.example.entity.Student">
delete
from student
where id = #{id}
</update>
开始
@Data
@RestController
@Slf4j
public class HelloController {
@Autowired
private StudentMapper studentMapper;
@GetMapping("/")
public String show() {
System.out.println(studentMapper.deleteStudent(9));
return "show";
}
}
自定义映射
好了,已经学会了最基础的增删改查,接下来我们来看下如何进行自定义字段查询和多表查询
resultMap
现在面临一个问题,我希望同时查询学生表和老师表,但是他们同时拥有 name
字段,我该怎么办?
这个时候我们就应该引出 resultMap
自定义映射了。
我们先新建一个 VO 对象,用来专门与数据库字段交互。
package org.example.vo;
import lombok.Data;
@Data
public class ShowVO {
private Integer id;
private String name;
private Integer age;
private Integer teacherId;
private String teacherName;
}
去 Mapper 新建一个方法
@Mapper
public interface StudentMapper {
List<ShowVO> selectStudentTeacher();
}
StudentMapper.xml
<resultMap id="StudentTeacherMap" type="org.example.vo.ShowVO">
<!-- id为示自定义映射的唯一标识 -->
<id column="id" property="id"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="tid" property="teacherId"/>
<result column="tname" property="teacherName"/>
</resultMap>
<select id="selectStudentTeacher" resultMap="StudentTeacherMap">
select s.id, s.name, s.age, t.id as tid, t.name as tname
from student s, teacher t
where s.teacherId = t.id
</select>
执行,剩下就不多说了
@Data
@RestController
@Slf4j
public class HelloController {
@Autowired
private StudentMapper studentMapper;
@GetMapping("/")
public String show() {
System.out.println(studentMapper.selectStudentTeacher());
// [ShowVO(id=1, name=小明-update, age=16, teacherId=1, teacherName=张老师), ShowVO(id=2, name=小红, age=17, teacherId=1, teacherName=张老师), ShowVO(id=3, name=小华, age=15, teacherId=2, teacherName=李老师), ShowVO(id=4, name=小丽, age=16, teacherId=2, teacherName=李老师), ShowVO(id=5, name=小强, age=17, teacherId=3, teacherName=王老师), ShowVO(id=6, name=小芳, age=16, teacherId=3, teacherName=王老师), ShowVO(id=7, name=小亮, age=15, teacherId=4, teacherName=赵老师), ShowVO(id=8, name=小霞, age=17, teacherId=4, teacherName=赵老师), ShowVO(id=9, name=测试, age=22, teacherId=3, teacherName=王老师)]
return "show";
}
}
多对一映射 association
多对一也可以理解为一对一,特点是单个对象(如 Teacher
),返回都是同级的。
创建实体
@Data
public class Student {
private Integer id;
private String name;
private Integer age;
private Integer teacherId;
// 多个学生可以是同一个老师,即多对一
private Teacher teacher;
}
- 按查询结果嵌套查询
<resultMap id="StudentTeacherAssociationMap" type="org.example.vo.StudentTeacherVO">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="teacher_id" property="teacherId"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<association property="teacher" javaType="org.example.entity.Teacher">
<result column="tid" property="id"/>
<result column="tname" property="name"/>
</association>
</resultMap>
<select id="selectStudentTeacherAssociation" resultMap="StudentTeacherAssociationMap">
select s.id, s.name, s.age, s.teacher_id, t.id as tid, t.name as tname
from student s, teacher t
where s.teacher_id = t.id
</select>
- 按查询嵌套处理
javaType
:代表关联的对象
该查询好处是不需要填写其他属性,但只能传递一个参数。
<select id="selectStudentTeacherAssociation" resultMap="StudentTeacherAssociationMap">
select *
from student
</select>
<resultMap id="StudentTeacherAssociationMap" type="org.example.vo.StudentTeacherVO">
<!--association关联属性 property属性名 select查询方法 column关联列名-->
<association property="teacher" select="getTeacher" column="id" javaType="org.example.entity.Teacher"/>
</resultMap>
<!--
上面传递过来的id, association中column配置
-->
<select id="getTeacher" resultType="org.example.entity.Teacher">
select *
from teacher
where id = #{tid}
</select>
- 不管使用哪种方式结果都是一样的
@Data
@RestController
@Slf4j
public class HelloController {
@Autowired
private StudentMapper studentMapper;
@GetMapping("/")
public String show() {
System.out.println(studentMapper.selectStudentTeacherAssociation());
// [StudentTeacherVO(id=1, name=小明-update, age=16, teacherId=1, teacher=Teacher(id=1, name=张老师)), StudentTeacherVO(id=2, name=小红, age=17, teacherId=1, teacher=Teacher(id=1, name=张老师)), StudentTeacherVO(id=3, name=小华, age=15, teacherId=2, teacher=Teacher(id=2, name=李老师)), StudentTeacherVO(id=4, name=小丽, age=16, teacherId=2, teacher=Teacher(id=2, name=李老师)), StudentTeacherVO(id=5, name=小强, age=17, teacherId=3, teacher=Teacher(id=3, name=王老师)), StudentTeacherVO(id=6, name=小芳, age=16, teacherId=3, teacher=Teacher(id=3, name=王老师)), StudentTeacherVO(id=7, name=小亮, age=15, teacherId=4, teacher=Teacher(id=4, name=赵老师)), StudentTeacherVO(id=8, name=小霞, age=17, teacherId=4, teacher=Teacher(id=4, name=赵老师)), StudentTeacherVO(id=9, name=测试, age=22, teacherId=3, teacher=Teacher(id=3, name=王老师))]
return "show";
}
}
一对多映射 collection
一对多、多对多。目标数据结构是集合(如 List<Student>
)。
@Data
public class TeacherStudentVO {
private int id;
private String name;
//一个老师多个学生
private List<Student> students;
}
List<TeacherStudentVO> selectStudentTeacherCollection();
- 按查询结果嵌套查询
<resultMap id="TeacherStudentCollectionMap" type="org.example.vo.TeacherStudentVO">
<id column="tid" property="id"/>
<result column="tname" property="name"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<collection property="students" ofType="org.example.entity.Student">
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<result column="teacher_id" property="teacherId"/>
</collection>
</resultMap>
<select id="selectTeacherStudentCollection" resultMap="TeacherStudentCollectionMap">
select s.id, s.name, s.age, s.teacher_id, t.id as tid, t.name as tname
from student s, teacher t
where s.teacher_id = t.id
</select>
- 按查询嵌套处理
javaType
:集合的类型,这一点和多对一是不同的ofType
:集合 中 元素的类型,即每个学生对应一个Student
对象。
<select id="selectTeacherStudentCollection" resultMap="TeacherStudentCollectionMap">
select *
from teacher
</select>
<resultMap id="TeacherStudentCollectionMap" type="org.example.vo.TeacherStudentVO">
<!--association关联属性 property属性名 select查询方法 column关联列名-->
<collection property="students" javaType="ArrayList" ofType="org.example.entity.Student" column="id"
select="getStudent"/>
</resultMap>
<!--
上面传递过来的id, association中column配置
-->
<select id="getStudent" resultType="org.example.entity.Teacher">
select *
from student
where id = #{id}
</select>
- 不管使用哪种方式结果都是一样的
@Data
@RestController
@Slf4j
public class HelloController {
@Autowired
private StudentMapper studentMapper;
@GetMapping("/")
public String show() {
System.out.println(studentMapper.selectTeacherStudentCollection());
// [TeacherStudentVO(id=1, name=张老师, students=[Student(id=1, name=小明-update, age=16, teacherId=1), Student(id=2, name=小红, age=17, teacherId=1)]), TeacherStudentVO(id=2, name=李老师, students=[Student(id=3, name=小华, age=15, teacherId=2), Student(id=4, name=小丽, age=16, teacherId=2)]), TeacherStudentVO(id=3, name=王老师, students=[Student(id=5, name=小强, age=17, teacherId=3), Student(id=6, name=小芳, age=16, teacherId=3), Student(id=9, name=测试, age=22, teacherId=3)]), TeacherStudentVO(id=4, name=赵老师, students=[Student(id=7, name=小亮, age=15, teacherId=4), Student(id=8, name=小霞, age=17, teacherId=4)])]
return "show";
}
}
特殊 SQL
模糊查询
<select id="testMohu" resultType="Student">
<!--select * from student where name like '%${mohu}%'-->
<!--select * from student where name like concat('%',#{mohu},'%')-->
select * from student where name like "%"#{mohu}"%"
</select>
批量删除
<delete id="deleteMore">
delete from student where id in (${ids})
</delete>
动态表名
<select id="getAllStudent" resultType="Student">
select * from ${tableName}
</select>
动态 SQL
直接拿上面的案例过来
if & where
<select id="selectStudentById" resultType="org.example.entity.Student">
select *
from student
where id = #{id}
</select>
<select id="selectStudentById" resultType="org.example.entity.Student">
select *
from student
<where>
<if test="id != null">
id = #{id}
</if>
</where>
</select>
set
<update id="updateStudent" parameterType="org.example.entity.Student">
update student
set name=#{name}
where id = #{id}
</update>
<update id="updateStudent" parameterType="org.example.entity.Student">
update student
<set>
<if test="name != null">
name = #{name}
</if>
</set>
<where>
<if test="id != null">
id = #{id}
</if>
</where>
</update>
choose
- 适用于只想选择其中的一个。
<select id="selectStudentById" resultType="org.example.entity.Student">
select *
from student
<where>
<choose>
<when test="id != null">
and id = #{id}
</when>
<when test="name != null">
and name = #{name}
</when>
<!-- 如果 id 和 name 都不存在 -->
<otherwise>
</otherwise>
</choose>
</where>
</select>
- SQL 片段
<sql id="if-title-author">
<if test="id != null">
id = #{id}
</if>
</sql>
<select id="selectStudentById" resultType="org.example.entity.Student">
select *
from student
<where>
<include refid="if-title-author"></include>
</where>
</select>
Foreach
查询 1、3、5 的用户
List<Student> queryStudentForeach(Map map);
XML
<select id="queryStudentForeach" parameterType="map" resultType="org.example.entity.Student">
select * from student
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from student where 1=1 and (id=1 or id=3 or id=5)
-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
调用
Map map = new HashMap();
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids", ids);
List<Student> Students = StudentMapper.queryStudentForeach(map);
System.out.println(Students);
分页
- 只有紧跟在
PageHelper.startPage
方法后的第一个Mybatis 的**查询(Select)**方法会被分页。 - 对于带有
for update
的 sql,会抛出运行时异常 - 嵌套结果方式会导致结果集被折叠
添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
配置 yml
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
调用
// 页码为1, 每页2条记录
PageHelper.startPage(1, 2);
// 跳过第一条记录, 返回2条记录
PageHelper.offsetPage(1, 2);
// 查询结果, 现在查询到2条
List<Student> StudentList = StudentMapper.selectByPage();
// 封装分页结果
PageInfo<Student> pageInfo = new PageInfo<>(StudentList);
注解开发
- @select ()
- @update ()
- @Insert ()
- @delete ()
@Mapper
public interface StudentMapper {
@Select("select * from student where id = #{id}")
Student selectStudentById(int id);
@Insert("insert into student (name, age) values (#{name}, #{age})")
int addStudent(Student Student);
@Update("update student set name=#{name} where id = #{id}")
int updateStudent(Student Student);
@Delete("delete from student where id = #{id}")
int deleteStudent(int id);
}