目录
查看 MyBatis 执行 SQL 语句的方法与预编译 SQL 介绍
前言
MyBatis 基础操作案例需求与环境准备概述
本部分围绕 MyBatis 基础操作展开,首先依据 tlias智能学习辅助系统 员工管理模块的页面原型及需求,明确要实现员工信息的增删改查功能,包括条件分页查询、新增、编辑(查询回显与更新)、单条及批量删除等操作。
随后进行环境准备工作,为后续具体操作奠定基础,这是实现功能的前置关键步骤。
案例需求分析
- 查询功能需求
- 条件分页查询:在员工列表查询时,需依据页面上方的条件(如员工姓名、部门等)进行筛选,并对结果进行分页展示,方便用户查看和管理大量数据。
- 查询回显:点击编辑按钮时,要先根据主键 ID 将对应员工数据查询出来并展示在表单中,以便用户在原有数据基础上修改。
- 新增功能需求:点击新增员工按钮后,在弹出对话框的表单中录入信息,点击保存时,需将数据通过
INSERT
语句插入数据库。 - 编辑与更新功能需求:编辑操作先查询回显数据,用户修改后点击保存,执行
UPDATE
语句将修改后的数据更新到数据库。 - 删除功能需求
- 单条删除:点击删除按钮,根据主键 ID 删除对应员工数据。
- 批量删除:勾选多条数据前的复选框后点击批量删除按钮,删除选中的多条数据。
环境准备
数据库表准备
执行 EMP
(员工表)及相关测试数据的 SQL 语句,创建出 EMP
表并插入 17 条测试数据,同时创建了关联的 DPT
(部门表),虽实际操作暂未用到,但保证数据完整性。
-- 部门管理
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, 'jinyong', '123456', '金庸', 1, '1.jpg', 4, '2000-01-01', 2, now(), now()),
(2, 'zhangwuji', '123456', '张无忌', 1, '2.jpg', 2, '2015-01-01', 2, now(), now()),
(3, 'yangxiao', '123456', '杨逍', 1, '3.jpg', 2, '2008-05-01', 2, now(), now()),
(4, 'weiyixiao', '123456', '韦一笑', 1, '4.jpg', 2, '2007-01-01', 2, now(), now()),
(5, 'changyuchun', '123456', '常遇春', 1, '5.jpg', 2, '2012-12-05', 2, now(), now()),
(6, 'xiaozhao', '123456', '小昭', 2, '6.jpg', 3, '2013-09-05', 1, now(), now()),
(7, 'jixiaofu', '123456', '纪晓芙', 2, '7.jpg', 1, '2005-08-01', 1, now(), now()),
(8, 'zhouzhiruo', '123456', '周芷若', 2, '8.jpg', 1, '2014-11-09', 1, now(), now()),
(9, 'dingminjun', '123456', '丁敏君', 2, '9.jpg', 1, '2011-03-11', 1, now(), now()),
(10, 'zhaomin', '123456', '赵敏', 2, '10.jpg', 1, '2013-09-05', 1, now(), now()),
(11, 'luzhangke', '123456', '鹿杖客', 1, '11.jpg', 5, '2007-02-01', 3, now(), now()),
(12, 'hebiweng', '123456', '鹤笔翁', 1, '12.jpg', 5, '2008-08-18', 3, now(), now()),
(13, 'fangdongbai', '123456', '方东白', 1, '13.jpg', 5, '2012-11-01', 3, now(), now()),
(14, 'zhangsanfeng', '123456', '张三丰', 1, '14.jpg', 2, '2002-08-01', 2, now(), now()),
(15, 'yulianzhou', '123456', '俞莲舟', 1, '15.jpg', 2, '2011-05-01', 2, now(), now()),
(16, 'songyuanqiao', '123456', '宋远桥', 1, '16.jpg', 2, '2010-01-01', 2, now(), now()),
(17, 'chenyouliang', '123456', '陈友谅', 1, '17.jpg', NULL, '2015-03-21', NULL, now(), now());
项目创建与依赖引入
- 创建新的 Spring Boot 项目,并引入三个关键依赖:
Lombok
用于简化实体类定义,MyBatis
实现数据库操作功能,MySQL
驱动包使项目能连接 MySQL 数据库。 - 在项目的
application.properties
文件中配置数据库连接的四要素,即驱动类名、连接 URL、用户名和密码,确保项目能与数据库建立连接。
实体类与 Mapper 接口创建
- 创建
EMP
员工实体类,其属性与表结构字段一一对应,遵循驼峰命名规范,如将数据库字段中的下划线分隔形式(如dpt_id
)转换为驼峰形式(dptId
),对于日期类型字段,数据库中的date
类型(入职日期)在实体类中用LocalDate
类型,datetime
类型(创建时间和修改时间)在实体类中用LocalDateTime
类型。实体类上添加@Data
、@NoArgsConstructor
、@AllArgsConstructor
等 Lombok 注解,自动生成get
、set
、toString
等方法及构造函数,简化代码编写。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
private Integer id;
private String username;
private String password;
private String name;
private Short gender;
private String image;
private Short job;
private LocalDate entrydate;
private Integer deptId;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
- 准备
EMP Mapper
接口,并添加@Mapper
注解,使程序运行时自动创建该接口的代理对象并放入 Spring 的 IoC 容器中,方便后续数据库操作方法的调用与管理。
import org.apache.ibatis.annotations.*;
@Mapper
public interface EmpMapper {
}
删除功能需求与 SQL 语句分析
在员工管理系统中,点击删除按钮后,前端会将员工数据的主键 ID 传递给后端,后端需依据此 ID 从数据库中删除对应记录。在 SQL 层面,使用
DELETE FROM EMP WHERE id = [具体 ID 值]
语句实现删除操作,其中 EMP
为员工表,id
是主键。例如,若要删除 ID 为 17 的员工记录,SQL 语句即为
DELETE FROM EMP WHERE id = 17
MyBatis 中删除功能的实现步骤
Mapper 接口方法定义
在 EMP Mapper
接口中定义 delete
方法,由于删除操作通常无需返回结果给前端(一般只关注操作是否成功执行),故将返回值设为 void
。使用 @Delete
注解标注该方法,并在注解的 value
属性中编写 SQL 删除语句。如 @Delete("delete from emp where id = #{id}")
,这里的 ${id} 是 MyBatis 的参数占位符,用于在方法执行时动态传入实际的 ID 值。
单元测试编写与执行
- 在测试类中,首先通过
@Autowired
注解注入EMP Mapper
类型的对象,以便在测试方法中调用delete
方法。
- 编写单元测试方法
public void testDelete()
,并添加@Test
注解。在方法内部,调用empMapper.delete(16)
(假设当前要删除 ID 为 16 的员工记录)来执行删除操作。
- 运行单元测试,若测试通过且数据库中对应员工记录被成功删除(可通过刷新数据库查看),则表明删除功能实现正确。
注意事项与技巧
- 参数占位符命名规范:在使用 MyBatis 的参数占位符 #{ } 时,若 Mapper 接口方法参数只有一个普通类型参数,占位符 #{ } 内的属性名可随意编写,但在实际项目开发中,建议与方法参数名保持一致,以增强代码可读性和可维护性。例如,方法参数为
int id
,占位符写为 #{id} 更符合规范,方便后续代码阅读与理解,避免因随意命名导致的混淆。 - 数据库表与项目的同步:在编写 SQL 语句时,如果出现表名无法自动提示的情况,可能是因为数据库表在项目中的缓存未及时更新。此时可在相关操作界面点击刷新按钮,使项目识别新创建或修改的数据库表,确保 SQL 语句编写的准确性和便捷性,避免因表名错误或无法识别导致的操作失败。
查看 MyBatis 执行 SQL 语句的方法与预编译 SQL 介绍
开启 MyBatis 日志查看执行语句
在 application.properties
配置文件中配置 MyBatis 日志信息并指定输出到控制台,以便查看框架底层执行的 SQL 语句。可通过输入关键字(如 mybatis log
)利用 IDEA 提示功能找到相关配置项,如
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
开启后运行单元测试,能在控制台看到执行的 SQL 语句及相关信息,如 delete from emp where id =?
,其中 ?
为参数占位符。
预编译 SQL 语句原理与优势
在 MyBatis 的 Mapper 接口中使用 #{}
占位符编写 SQL 语句,最终会生成预编译 SQL,#{}
会被替换为 ?
。预编译 SQL 具有性能更高和更安全两大优势。
- 性能优势体现:在数据库执行 SQL 语句时,普通 SQL 语句每次执行都可能需要进行语法检查、解析、优化、编译等操作,若数据量较大且 SQL 语句相似(仅参数不同),如多次执行
delete from emp where id = [不同值]
,会重复这些操作,浪费资源。而预编译 SQL 会将编译后的结果缓存起来,后续相同结构的 SQL 语句执行时可直接使用缓存,减少编译次数,提高执行效率。例如,删除多个员工数据时,预编译 SQL 只需编译一次,大大提升性能。 - 安全优势体现(防止 SQL 注入):SQL 注入是通过操作输入数据修改 SQL 语句以攻击服务器的方式。例如,在登录验证的 SQL 语句
select count(*) from emp where username = [输入用户名] and password = [输入密码]
中,如果直接将用户输入拼接在 SQL 语句中,恶意用户可通过特殊输入(如在密码框输入' or '1'='1
)改变 SQL 语句的逻辑,使登录验证绕过。而预编译 SQL 将用户输入作为参数传递,不会将其直接拼接在 SQL 语句中,避免了 SQL 注入风险,保证系统安全。
SQL 注入与预编译 SQL 防范效果
SQL 注入
考虑如下场景,存在一个学校的图书管理系统,管理员需登录该系统以执行图书管理操作。在正常登录流程中,当管理员输入正确的用户名 “小李” 及密码 “abc123” 后,系统会依据既定逻辑进行验证。此时,在系统后台控制台会生成并执行一条 SQL 语句,其形式为
select count (*) from admin_table where username = ' 小李 ' and password = 'abc123
此语句的作用在于从名为 “admin_table” 的管理员表中精确查找与输入用户名和密码相匹配的用户记录,并统计符合条件的记录数量。若能成功找到匹配记录,则表明登录信息无误,管理员得以顺利登录系统。
然而,若存在恶意攻击者试图非法侵入该系统,其在密码框中输入诸如 “' or '1'='1” 的特殊字符串。在此情况下,系统后台执行的 SQL 语句将转变为
select count (*) from admin_table where username = ' 随便瞎写的名字 ' and password = '' or '1'='1'
显然,由于 “1=1” 这一条件恒成立,无论实际的用户名与正确密码为何,该查询语句都会返回非零结果。如此一来,系统便会被误导,误判非法用户输入的信息为合法,进而允许其成功登录。通过此例,清晰地展示了 SQL 注入的发生机制与潜在危害,其原理不难理解。
预编译 SQL 防范 SQL 注入
接下来,我们再来看采用预编译 SQL 的登录项目的情况。同样在登录页面输入之前的恶意数据进行测试,此时在控制台所显示的 SQL 语句呈现为预编译形式,例如 “select count (*) from emp where username =? and password =?”。在这种机制下,用户的输入仅仅是作为参数传递给 SQL 语句,而不会像之前那样被恶意拼接在 SQL 语句之中。
当输入错误密码时,系统会严格按照预编译 SQL 的逻辑进行验证,不会受到恶意输入的干扰,从而准确地提示登录失败。这充分表明预编译 SQL 有效地阻止了 SQL 注入攻击,切实保障了系统的安全性。
MyBatis 中不同占位符的区别与使用建议
在 MyBatis 中,#{}
和 ${}
是两种不同的占位符。
#{}
占位符:在 SQL 语句执行时,#{}
会被替换为?
,生成预编译 SQL 语句,主要用于参数传递场景,如根据员工 ID 删除员工信息时delete from emp where id = #{id}
,能提高性能并防止 SQL 注入,是项目开发中常用的占位符。${}
占位符:${}
是直接将参数拼接在 SQL 语句中,如select * from ${tableName}
,一般用于对表名或字段名进行动态设置,但这种方式存在 SQL 注入风险且性能较低。在实际项目中,除非有特殊需求(如动态切换表名等极个别情况),否则应尽量避免使用${}
占位符。因为其带来的安全隐患和性能问题可能会对系统造成严重影响。在大多数常规的数据库操作中,如数据的增删改查操作里的参数传递环节,都应优先选择#{}
占位符,以确保系统的高效与安全运行。
END
学习自:黑马程序员——JavaWeb课程