3.3 MyBatis 增删改查(注解方式)
MyBatis 的增删改查是最基础最核心的功能,需要重点掌握。
需求说明
对员工信息进行增删改查操作。
- 查询(查询结果分页展示后续实现)
- 根据主键ID查询
- 根据条件查询
- 新增
- 更新
- 删除
- 根据主键 ID 删除
- 根据主键 ID 批量删除
准备工作
- 准备数据库表 dept 部门表和 emp 员工表并插入数据
-- 部门表
create table dept(
id int unsigned primary key auto_increment comment '主键ID',
name varchar(10) not null unique comment '部门名称',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '部门表';
-- 插入部门表数据
insert into dept (id,name,create_time,update_time) values
(1,'学工部',now(),now()),
(2,'教研部',now(),now()),
(3,'咨询部',now(),now()),
(4,'就业部',now(),now()),
(5,'人事部',now(),now());
-- 员工表
create table emp(
id int unsigned primary key auto_increment comment 'ID',
username varchar(20) not null unique comment '用户名',
password varchar(32) default '123456' comment '密码',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别,说明:1 男,2 女',
image varchar(300) comment '图像',
job tinyint unsigned comment '职位,说明:1 班主任,2 讲师,3 学工主管,4 教研主管,5 咨询师',
entrydate date comment '入职时间',
dept_id int unsigned comment '部门ID',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '员工表';
-- 插入员工表数据
insert into emp (id,username,password,name,gender,image,job,entrydate,dept_id,create_time,update_time) values
(1,'zhansan','123456','张三',1,'1.jpg',4,'2000-01-01',2,now(),now()),
(2,'lisi','123456','李四',1,'2.jpg',2,'2015-01-01',2,now(),now()),
(3,'wangwu','123456','王五',1,'3.jpg',4,'2008-01-01',2,now(),now()),
(4,'wangmazi','123456','王麻子',1,'4.jpg',1,'2000-01-01',2,now(),now()),
(5,'xiaohong','123456','小红',2,'5.jpg',5,'2009-01-01',3,now(),now());
- 创建一个新的 springboot 工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)
- Application.properties 配置文件中引入数据库连接信息
src/main/resources/application.properties
# 1 驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 2 数据库连接的 url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
# 3 连接数据库的用户名
spring.datasource.username=root
# 4 链接数据库的密码
spring.datasource.password=xxxx
- 创建对应的实体类 Emp(实体类属性采用驼峰命名)
src/main/java/com/ganming/pojp/Emp.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
private Integer id; // ID
private String username; // 用户名
private String password; // 密码
private String name; // 姓名
private Short gender; // 性别,说明:1 男,2 女
private String image; // 图像url
private Short job; // 职位,说明:1 班主任,2 讲师,3 学工主管,4 教研主管,5 咨询师
private LocalDate entrydate; // 入职日期 LocalDate:封装 年月日
private Integer deptId; // 部门ID
private LocalDateTime createTime; // 创建时间 LocalDateTime:封装 元月日时分秒
private LocalDateTime updateTime; // 修改时间
}
- 准备 Mapper 接口 EmpMapper
src/main/java/com/ganming/mapper/EmpMapper.java
package com.ganming.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper // 在运行时,框架会自动的生成该接口的实现类对象(代理对象),并且将该对象交给 IOC 容器管理
public interface EmpMapper {
}
MyBatis 删除
- 根据 ID 删除单条数据
src/main/java/com/ganming/mapper/EmpMapper.java
// 根据 ID 删除数据
@Delete("delete from emp where id = #{id}") // 动态获取方法参数id
public void delete(Integer id);
注意事项:
如果 mapper 接口方法形参只有一个普通类型的参数,#{…} 里面的属性名可以随便写,如:#{id}、#{value}。
该方法如果返回值,则为该 sql 语句执行后影响的行数。
测试根据 ID 删除数据方法:
src/test/java/com/ganming/MybatisCrudApplicationTests.java
@Resource // 完成依赖注入
private EmpMapper empMapper;
@Test
public void testDelete(){
empMapper.delete(5); // 调用 EmpMapper 中的删除方法
}
- MyBatis 配置开启日志输出
可以在 src/main/resources/application.properties
中,打开 mybatis 的日志,并指定输出到控制台。开启后能在控制台输出预编译 SQL。
# 指定 mybatis 输出日志的位置,输出控制台
mybatis.configuration.log-impl = org.apache.ibatis.logging.stdout.StdOutImpl
预编译 SQL :①性能更高;②更安全(防止 SQL 注入)
SQL 注入:是指通过操作输入的数据来修改事先定义好的 SQL 语句,以达到执行代码对服务器进行攻击的方法。
MyBatis 参数占位符:
#{…}:①执行 SQL 时,会将 #{…} 替换为 ?,生成预编译 SQL,会自动设置参数值。②使用时机:参数传递都使用 #{…};
${…}:①拼接 SQL 。直接将参数拼接在 SQL 语句中,存在 SQL 注入问题。②使用时机:如果对表名、列表名进行动态设置时使用;
MyBatis插入
src/main/java/com/ganming/mapper/EmpMapper.java
// 插入数据
@Insert("insert into emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" +
"value (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime});")
public void insert(Emp emp); // 用一个实体类来封装多个参数
src/test/java/com/ganming/MybatisCrudApplicationTests.java
@Test
public void testInsert(){
Emp emp = new Emp();
emp.setUsername("zhouzhiruo");
emp.setName("周芷若");
emp.setImage("z.jpg");
emp.setGender((short)2);
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2002,2,23));
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(1);
empMapper.insert(emp);
}
- 新增(主键返回)
如上,我们新增一个 emp 员工数据时,主键 id 是自增产生的,我们并没有传值,但是当程序中需要这个主键时(需要往其他表插入相关信息),我们该如何获取呢?
如果直接获取 emp 的 id,将得到的是 null,如下:默认情况下,主键值是不返回的。
如需返回,可在该方法上添加 @Options 注解
useGeneratedKeys = true
:表示我们需要拿到生成的主键值。
keyProperty = "id"
: 表示我们所获取到的主键赋值给 emp 对象的 id 属性上。
MyBatis更新
更新一般要根据一条数据的唯一键来更新其他字段,所以更新方法需要传递更新哪条数据的哪些字段值。
src/main/java/com/ganming/mapper/EmpMapper.java
// 修改数据
@Update("update emp set username = #{username},name = #{name},gender = #{gender},image = #{image},job = #{job},entrydate = #{entrydate},dept_id = #{deptId},update_time = #{updateTime} where id = #{id};")
public void update(Emp emp);
src/test/java/com/ganming/MybatisCrudApplicationTests.java
@Test
public void testUpdate(){
Emp emp = new Emp();
emp.setId(1); // 跟新第一条数据
emp.setUsername("zhaoming");
emp.setName("赵敏");
emp.setImage("z.jpg");
emp.setGender((short)2);
emp.setJob((short)1);
emp.setEntrydate(LocalDate.of(2002,2,23));
//emp.setCreateTime(LocalDateTime.now()); // 创建时间不需要更新了
emp.setUpdateTime(LocalDateTime.now()); // 每次更新都需要修改更新时间
emp.setDeptId(1);
empMapper.update(emp);
}
MyBatis查询
查询是有查询结果的,必须要有返回值,需要封装到实体类中,如果查出来是一条,就直接返回对应的实体类,如果查询结果是多条,就封装到实体类的集合或数组中。
- 根据 id 查询:根据 id 在本例中只能查到一条数据,所以设置返回结果为 Emp 类型即可。
src/main/java/com/ganming/mapper/EmpMapper.java
// 根据id查询
@Select("select * from emp where id = #{id}")
public Emp selectById(Integer id);
src/test/java/com/ganming/MybatisCrudApplicationTests.java
// 测试查询
@Test
public void testSelectById(){
Emp emp = empMapper.selectById(1);
System.out.println(emp);
}
打印的查询结果如下,可发现除了deptId、createTime、updateTime 这三个属性为 null 外,其他属性都正常显示。这是因为数据表中的字段名与需要封装的实体类 Emp 的属性名这三个没有完全对应上,数据表的命名规则常采用 a_column,而实体类的属性命名规则为aColumn。
MyBatis 的数据封装:实体类属性名和数据库表查询返回的字段名一致,mybatis 会自动封装;如果不一致则不会自动封装。
- 解决方案一(了解):给数据库表查询返回的字段名取别名,让别名和实体类属性名一致即可(需要起别名就不能用 * 来输出所有字段了)
- 解决方案二(了解):通过 @Results, @Result 注解手动映射封装
@Results 注解传入的参数是一个 @Result 数组;
@Result 注解传入的参数是 column(数据库表的字段)和 property(实体类的属性名),表示把column 的值映射到 property 的值上。
- 解决方案三(推荐):开启 mybatis 的驼峰命名自动映射开关
开关打开则下划线分割(a_column)的数据库表字段命名会自动封装到实体类中的驼峰(aColumn)命名属性当中。
在 src/main/resources/application.properties
中开启这项配置
- 按条件查询:
根据输入的员工姓名(支持模糊匹配)、性别(精确查询)、入职时间(范围查询)搜索满足条件的员工信息;并对查询结果根据入职时间倒序排序。
src/main/java/com/ganming/mapper/EmpMapper.java
// 根据条件查询
@Select("select * from emp where name like '%${name}%' and gender = #{gender} and " +
"entrydate between #{begin} and #{end} order by entrydate desc;")
public List<Emp> list(String name,short gender,LocalDate begin,LocalDate end);
由于 like
模糊匹配,我们需要在关键字前后拼接上 %
,所以 like
后面一定是个字符串,字符串当中是不能使用 #{}
的,所以在这里使用 ${}
进行字符串的拼接。但是这样拼接存在性能低、不安全、存在 SQL 注入问题。
mysql 给我们提供了一个 concat
字符串拼接函数,它可以接受多个字符串参数,将它们拼接位一个字符串:
利用 concat 字符串拼接比 ${} 拼接更好,排除了上述问题的隐患。
参数名说明(了解链接:视频13:50开始)