目录
1. 起别名:在SQL语句中,对不一样的列名起别名,让别名和实体类属性名一致
2. 手动结果映射:通过 @Results及@Result 进行手动结果映射
在springBoot的1.x版本 / 单独使用mybatis(使用@Param注解来指定SQL语句中的参数名)
1.1 需求
基础操作就是通过MyBatis来完成表结构当中数据的增删改查操作,这部分内容是MyBatis当中最为基础最为核心的功能。
crud就是增删改查的简写
查询 - Select
- 根据主键ID查询
- 条件查询
新增 - Inster
更新 - Update
删除 - Delete
- 根据主键ID删除
- 根据主键ID批量删除
查询有两个部分:
- 一个是根据主键ID查询,用于修改数据(Update)的时候,页面数据的回显;
- 另一个是条件查询,**并且还要对查询的结果进行分页展示{**条件分页查询 } 。
删除操作也包括两个: 一个是根据主键ID删除,另外一个是要根据主键ID进行批量删除
1.2 准备
实施前的准备工作:
- 准备数据库表
- 创建一个新的springboot工程,选择引入对应的起步依赖(MyBatis FrameWork、MySQLDriver、Lombok)
- application.properties中引入数据库连接信息
- 创建对应的实体类 Emp(实体类属性采用驼峰命名)
- 准备Mapper接口 EmpMapper
#配置数据库的连接信息 --- 四要素
#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=123456
POJO实体类代码: 实体类属性采用驼峰命名****
package com.gch.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
/**
POJO实体类当中的属性与表中的字段是一一对应的
*/
public class Emp {
/**主键ID */
private Integer id;
/** 用户名 */
private String username;
/** 密码 */
private String password;
/** 姓名 */
private String name;
/** 性别: 1-男 2-女 */
private short gender;
/** 图像url */
private String image;
/** 职位,说明:1-班主任 2-讲师 3-学工主管 4-教研主管 5-咨询师 */
private short job;
/** 入职日期:只需要记录年月日 */
private LocalDate entrydate;
/** 部门ID */
private Integer deptId;
/** 创建时间 */
private LocalDateTime createTime;
/** 修改时间 */
private LocalDateTime updateTime;
}
注意:注意区分Short与short !!!一个是Class类,一个是基本数据类型!!!!
Mapper接口:
package com.gch.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
public interface EmpMapper {
}
1.3 删除
1.3.1 功能实现
页面原型:
- 当我们点击后面的"删除"按钮时,前端页面会给服务端发送请求,并且会传递一个参数,也就是该行数据的唯一标识 - 主键ID。 我们接收到ID后,根据ID删除数据即可。
功能:根据主键删除数据
- SQL语句
package com.gch.mapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
@Mapper
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
public interface EmpMapper {
/**
根据ID删除数据
*/
@Delete("delete from emp where id = 17")
public void delete();
}
注意:此时这条SQL语句只能删除ID为17的这条数据,ID写死了,写成了硬编码。
改进:我们需要将这里写死的ID17改为动态的,我们需要在调用这条SQL语句的时候,动态的将这个ID值传递进来。
思考:什么时候会调用这条SQL语句?
回答:在调用这个接口方法的时候,所以,我们就只需要在调用这个接口方法的时候给这个接口方法传递一个参数,这个参数就是这个ID,然后在SQL语句执行的时候,将这个参数传递进来就可以了。
- 在SQL语句当中来动态的获取该方法在调用的时候传递进来的参数,此时就需要用到MyBatis当中提供的一个参数占位符:#{参数变量名}
- Mybatis框架让程序员更关注于SQL语句
改进后的接口方法:
package com.gch.mapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
@Mapper
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
public interface EmpMapper {
/**
该接口方法可以实现根据传递进来的ID动态的删除数据,使用#{key}方法获取方法中的参数值
*/
@Delete("delete from emp where id = #{id}")
public void delete(Integer id);
}
- @Delete注解: 用于编写delete操作的SQL语句
- 如果mapper接口方法形参只有一个普通类型的参数, #{…} 里面的属性名可以随便写,如:#{id}、#{value}。但是建议保持名字一致,增强可读性。
-
测试
- 在单元测试类中通过@Autowired注解注入EmpMapper类型对象
package com.gch;
import com.gch.mapper.EmpMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
/**
从Spring的IOC容器当中,获取类型是EmpMapper的对象并注入
*/
@Autowired
private EmpMapper empMapper;
@Test
public void testDelete() {
// 调用删除方法
empMapper.delete(17);
}
}
说明:Delete语句在执行时候,其实它是有返回值的,它的返回值代表此次操作影响的记录数。
而对于Insert和Update语句,在进行增加和修改的时候,它也是有返回值的,它的返回值也是代表此次操作影响的记录数。
- 由于这些返回值我们一般是不需要的,因此我们直接将返回值设为void。
1.3.2 日志输入
在Mybatis当中我们可以借助日志,查看到sql语句的执行、执行传递的参数以及执行结果。具体操作如下:
- 打开application.properties文件
- 开启mybatis的日志,并指定输出到控制台
#指定mybatis输出日志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
开启日志之后,我们再次运行单元测试,可以看到在控制台中,输出了以下的SQL语句信息:
1.3.3 预编译SQL / 预处理SQL
1.3.3.1 介绍
- 但是我们发现输出的SQL语句: delete from emp where id = ?,我们输入的参数{字段值} 16并没有在后面拼接,id的值是使用?进行占位,那这种SQL语句我们称为预编译SQL / 预处理SQL。
- 如果我们在MyBatis的Mapper接口当中,声明的SQL语句是使用 #{}这种占位符,那最终#{}会被问号?替代生成这样一个预编译SQL。
- 这个问号? 就是预编译SQL中的参数占位符,最终在执行的时候,会把这条SQL语句以及下面的参数都发送给数据库,最终数据库在执行这条SQL语句的时候,会使用下面的参数来替换掉这个?问号,最终来执行SQL操作。 delete from emp where id = 16;
提问:为什么要采用这种?问号占位符的 预编译SQL语句呢,而不是直接将输入参数拼接在SQL 语句当中呢?
这是因为采用预编译SQL / 预处理SQL语句有两大优势:
- 性能更高
- 更安全(防止SQL注入)
SQL语句的执行流程:
- 我们在Java项目当中,编写了一条SQL语句,SQL语句要想执行,就需要连接上数据库,然后将SQL语句发送给MySQL数据库服务器,而这条SQL语句发送给MySQL数据库服务器之后,数据库并不是立即就来执行的,而是要经历一系列的过程,比如要对SQL语句的语法进行检查解析,然后再通过优化器对SQL语句进行优化,然后再对SQL语句进行编译,编译成一些可执行的函数,最后再来执行SQL语句。
- 而为了提高效率,在数据库当中,它会将优化、编译后的SQL缓存起来,而这个缓存其实就是一块儿内存区域,就是来存储数据的,缓存起来之后,下一次再执行SQL语句的时候,它会先检查缓存,看一下缓存当中是否有编译好的SQL语句,如果有,就不用再执行这一系列的操作了,直接去执行SQL语句;如果没有,就又需要执行这一系列的操作,再把编译后的结果缓存起来。
正常的非预编译型的SQL语句 VS 预编译型的SQL语句:
性能更高:预编译SQL,编译一次之后会将编译后的SQL语句缓存起来,后面再次执行这条语句 时,不会再次编译。(只是输入的参数不同)
1.3.3.2 SQL注入
SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行 攻击****的方法。
- 由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。
测试1:使用资料中提供的程序,来验证SQL注入问题
第1步:进入到DOS
第2步:执行以下命令,启动程序
#启动存在SQL注入的程序
java -jar sql_Injection_demo-0.0.1-SNAPSHOT.jar
第3步:打开浏览器输入http://localhost:9090
- 校验用户名和密码其实本质上就是一个查询操作。
发现竟然能够登录成功:
以上操作为什么能够登录成功呢?
- 由于没有对用户输入内容进行充分检查,而SQL又是字符串拼接方式而成,在用户输入参数时,在参数中添加一些SQL关键字,达到改变SQL运行结果的目的,从而完成恶意攻击。
- 用户在页面提交数据的时候人为的添加一些特殊字符,使得sql语句的结构发生了变化,最终可以在没有用户名或者密码的情况下进行登录。
通过预编译SQL来解决SQL注入问题:
- 把整个
' or '1'='1作为一个完整的参数,赋值给第2个问号(' or '1'='1进行了转义,只当做字符串使用)
1.3.3.3 参数占位符
在Mybatis中提供的参数占位符有两种:${...} 、#{...}
-
#{...}
- 执行SQL时,会将#{…}替换为?,生成预编译SQL,会自动设置参数值
- 使用时机:参数传递,都使用#{…}
-
${...}
- 拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题
- 使用时机:如果对表名、列表进行动态设置时使用
注意事项: 在项目开发中,建议使用#{...},生成预编译SQL,防止SQL注入安全。
1.4 新增
功能:新增员工信息
页面原型
员工表结构
注意:表结构当中主键ID是自动增长的,因为向表中指定字段添加数据时,无需插入ID。
密码是有默认值的,并且页面原型当中也没有密码这一选项,因此添加字段时也无需指定密码。
1.4.1 基本新增
SQL语句:
-- 插入数据
insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)
values ('Tom', '汤姆', 1, '1.jpg', 1, '2005-01-01', 1, now(), now());
- 在insert()方法中如果一个一个参数去传递的话,很难维护。
- 推荐将多个参数直接封装到一个实体类对象当中,在调用insert()方法的时候就不用传递七八个参数了,只需要传递一个对象就可以,在#{中直接写的是对象的属性} :
接口方法:
package com.gch.mapper;
import com.gch.pojo.Emp;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
public interface EmpMapper {
/**
该接口方法可以实现根据传递进来的ID动态的删除数据,使用#{key}方法获取方法中的参数值
*/
@Delete("delete from emp where id = #{id}")
public void delete(Integer id);
/**
* 新增员工
* @param emp => 将多个参数封装到一个对象当中
*/
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" +
"values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
}
注意:下划线都是字段名,属性名都是采用驼峰式命名的。
说明:#{...} 里面写的名称是对象的属性名
测试类:
package com.gch;
import com.gch.mapper.EmpMapper;
import com.gch.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDate;
import java.time.LocalDateTime;
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
/**
从Spring的IOC容器当中,获取类型是EmpMapper的对象并注入
*/
@Autowired
private EmpMapper empMapper;
/**
增
*/
@Test
public void testInsert(){
// 构建员工对象
Emp emp = new Emp();
emp.setUsername("Tom");
emp.setName("汤姆");
emp.setGender((short)1);
emp.setImage("1.jpg");
emp.setJob((short)1);
emp.setEntrydate(LocalDate.now());
emp.setDeptId(1);
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
// 调用新增方法执行新增员工操作
empMapper.insert(emp);
}
}
总结:Insert操作先编写SQL语句,再编写接口方法。
1.4.2 主键返回
概念:在数据添加成功后,需要获取插入数据库数据的主键。
如:添加套餐数据时,还需要维护套餐菜品关系表数据。
业务场景: 在前面讲解到的苍穹外卖菜品与套餐模块的表结构,菜品与套餐是多对多的关系,一个套餐对应多个菜品。既然是多对多的关系,是不是有一张套餐菜品中间表来维护它们之间的关系。
那要如何实现在插入数据之后返回所插入行的主键值呢?
- 默认情况下,执行插入操作时,是不会主键值返回的。
- 如果我们想要拿到主键值,需要在Mapper接口中的方法上添加一个Options注解,并在注解中指定属性useGeneratedKeys=true和keyProperty="实体类属性名"
- useGeneratedKeys用于表示是否使用自动生成的主键值;
- keyProperty用于指定自动生成的主键属性的名称,它是实体类中对应主键属性的名字。
主键返回代码实现:
package com.gch.mapper;
import com.gch.pojo.Emp;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
@Mapper
public interface EmpMapper {
/**
* 新增员工
* useGeneratedKeys=true 告诉MyBatis使用自动生成的主键
* keyProperty = "id" 并将生成的主键设置到实体类对象的id属性中
* @param emp => 将多个参数封装到一个对象当中
*/
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" +
"values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
public void insert(Emp emp);
}
测试:
1.5 更新
功能:修改员工信息
- 点击 "编辑"按钮后,会查询所在行记录的员工信息,并把员工信息回显在修改员工的窗体上
- 在修改员工的窗体上,可以修改的员工数据:用户名、员工姓名、性别、图像、职位、入职日期、归属部门
思考:在修改员工数据时,要以什么做为条件呢?
答案:员工id
SQL语句:
-- 更新数据
update emp
set username = '',
name = '',
gender = '',
image = '',
job = '',
entrydate = '',
dept_id = '',
update_time = '' where id = 1;
接口方法:
package com.gch.mapper;
import com.gch.pojo.Emp;
import org.apache.ibatis.annotations.*;
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
@Mapper
public interface EmpMapper {
/**
* 更新员工
* @param emp => 将多个参数封装到实体类对象当中
*/
@Update("update emp set username = #{username},name = #{name},gender = #{gender},image = #{iamge},job = #{job}," +
"entrydate = #{entrydate},dept_id = #{deptId},update_time = #{updateTime} where id = #{id}")
public void update(Emp emp);
}
测试类:
package com.gch;
import com.gch.mapper.EmpMapper;
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
/**
从Spring的IOC容器当中,获取类型是EmpMapper的对象并注入
*/
@Autowired
private EmpMapper empMapper;
/**
改
*/
@Test
public void testUpdate(){
//要修改的员工信息
Emp emp = new Emp();
emp.setId(23);
emp.setUsername("songdaxia");
emp.setPassword(null);
emp.setName("老宋");
emp.setImage("2.jpg");
emp.setGender((short)1);
emp.setJob((short)2);
emp.setEntrydate(LocalDate.of(2012,1,1));
emp.setCreateTime(null);
emp.setUpdateTime(LocalDateTime.now());
emp.setDeptId(2);
//调用方法,修改员工数据
empMapper.update(emp);
}
}
**
1.6 查询
1.6.1 根据ID查询
在员工管理的页面中,当我们进行更新数据时,会点击 “编辑” 按钮,然后此时会发送一个请求到服务端,会根据Id查询该员工信息,并将员工数据回显在页面上。
SQL语句:
-- 根据ID查询员工
select * from emp where id = #{id};
接口方法:
package com.gch.mapper;
import com.gch.pojo.Emp;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
@Mapper
public interface EmpMapper {
/**
* 根据ID查询员工 => 由于ID是主键,因此查询返回的是一条记录
* 因此返回值直接使用实体类对象来封装,没必要再使用集合来封装
* @param id => ID主键
* @return => 根据ID查询所返回的员工记录
*/
@Select("select * from emp where id = #{id}")
public Emp select(Integer id);
}
测试类:
package com.gch;
import com.gch.mapper.EmpMapper;
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
/**
从Spring的IOC容器当中,获取类型是EmpMapper的对象并注入
*/
@Autowired
private EmpMapper empMapper;
/**
查
*/
@Test
public void testSelectGetById() {
Emp emp = empMapper.select(5);
System.out.println(emp);
}
}
执行结果:
而在测试的过程中,我们会发现有几个字段(deptId、createTime、updateTime)是没有数据值的****
1.6.2 数据封装
我们看到查询返回的结果中大部分字段是有值的,但是deptId,createTime,updateTime这几个字段是没有值的{为null} ,而数据库中是有对应的字段值的,这是为什么呢?
原因如下:
- 实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装。
- 如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。
解决方案:
- 起别名{给字段起别名,让别名与实体类属性名一致}
- 结果映射
- 开启驼峰命名
1. 起别名:在SQL语句中,对不一样的列名起别名,让别名和实体类属性名一致
package com.gch.mapper;
import com.gch.pojo.Emp;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
@Mapper
public interface EmpMapper {
/**
* 给字段起别名,让别名与字段名一致
* 根据ID查询员工 => 由于ID是主键,因此查询返回的是一条记录
* 因此返回值直接使用实体类对象来封装,没必要再使用集合来封装
* @param id => ID主键
* @return => 根据ID查询所返回的员工记录
*/
@Select("select id, username, password, name, gender, image, job, entrydate, dept_id as deptId, create_time as createTime" +
", update_time as updateTime from emp where id = #{id}")
public Emp select(Integer id);
}
再次执行测试类:
2. 手动结果映射:通过 @Results及@Result 进行手动结果映射
package com.gch.mapper;
import com.gch.pojo.Emp;
import org.apache.ibatis.annotations.*;
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
@Mapper
public interface EmpMapper {
/**
* 方案二:通过@Results,@Result注解手动映射封装
* @Results和@Result是MyBatis中用于映射查询结果的注解
* @Results它是一个@Result类型的value数组,里面可以封装一组@Result
* @Result注解用于单个映射结果的详细信息,将数据库查询结果的列映射到对应的实体类属性当中
* column = "数据库表当中的列名/字段名" property = "实体类当中的属性名"
* 我们只需要将属性名与字段名不一致的这几个字段进行手动封装即可
* @param id => 主键ID
* @return
*/
@Results({
@Result(column = "dept_id", property = "deptId"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")
})
@Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp where id = #{id}")
public Emp select(Integer id);
}
3. 开启MyBatis的驼峰命名自动映射开关(推荐) :
- 如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射
- 要开启驼峰命令的前提是:需要严格遵守数据库当中的字段是下划线分隔,类中的属性名是驼峰命名,这样才可以自动的完成映射封装。
驼峰命名规则: abc_xyz => abcXyz
- 表中字段名: abc_xyz
- 类中属性名: abcXyz
# 在application.properties中添加:
#开启MyBatis的驼峰命令自动映射开关(camel)
mybatis.configuration.map-underscore-to-camel-case=true
package com.gch.mapper;
import com.gch.pojo.Emp;
import org.apache.ibatis.annotations.*;
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
@Mapper
public interface EmpMapper {
/**
* 方案三:开启MyBatis的驼峰命名自动映射开关 --- a_column => aColumn
* 开关一旦打开,就会将表结构当中下划线分割的字段名会自动的封装到实体类当中驼峰命名的属性名当中
* 如果需要打开开关,是需要在application.properties当中来配置的
* @param id => 主键ID
* @return => 根据主键ID查询返回到的表中唯一数据
*/
@Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from mybatis.emp where id = #{id]}")
public Emp select(Integer id);
}
数据封装总结{类中属性名与表中字段名不一致}:
1.6.3 条件查询
在员工管理的列表页面中,我们需要根据条件查询员工信息,查询条件包括:姓名、性别、入职时间。
通过页面原型以及需求描述我们要实现的查询:
- 姓名: 要求支持模糊匹配
- 性别: 要求精确匹配
- 入职时间: 要求进行范围查询
- 根据最后修改时间进行降序排序
SQL语句:
-- 条件查询员工列表信息
-- like关键字进行模糊匹配,%用于匹配任意个字符
-- 使用and连接多个条件,而不是使用逗号
select id,username,password,name,gender,image,job,entrydate,dept_id,create_time,update_time
from emp
where name like '%张%'
and gender = 1
and entrydate between '2010-01-01'
and '2020-01-01'
order by update_time desc;
接口方法:
-
方式一(不推荐)
package com.gch.mapper;
import com.gch.pojo.Emp;
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
@Mapper
public interface EmpMapper {
/**
* 条件查询员工信息
* 根据条件查询返回的结果可能是多条记录
* 既然是多条记录就不能封装到单个对象当中了,需要封装到List集合当中
* 不能把这四个参数封装到Emp对象当中,因为在Emp实体类的属性当中只有一个entrydate入职时间
* 而这里不仅有入职时间,还有一个离职时间,因此直接在方法当中传递参数
* 注意:在进行like模糊查询时需要用到'',但是#{}是不能出现在引号之内的
* 因为#{}最终生成的是预编译的SQL,最终是要被问号替代的
* 而问号这个占位符是不能出现在引号之内的,可以${},因为${}是字符串拼接符号,它不会生成预编译SQL
* @param name => 查询的姓名
* @param gender => 查询的性别
* @param begin => 查询的入职时间
* @param end => 查询的离职时间
* @return => 把查询返回的多条记录封装到List集合里面
*/
@Select("select id,username,password,name,gender,image,job,entrydate,dept_id,create_time,update_time from emp " +
"where name like '%${name}%' and gender = #{gender} and entrydate between #{begin} and #{end} order by update_time desc ")
public List<Emp> select(String name, short gender, LocalDate begin,LocalDate end);
}
接口方法的注意事项:
- 方法中的形参名和SQL语句中的参数占位符名保持一致
- 模糊查询使用${...}进行字符串拼接,这种方式呢,由于是字符串拼接,并不是预编译的形式,所以效率不高、且存在sql注入风险。
测试类:
package com.gch;
import com.gch.mapper.EmpMapper;
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
/**
从Spring的IOC容器当中,获取类型是EmpMapper的对象并注入
*/
@Autowired
private EmpMapper empMapper;
@Test
public void testSelectList() {
List<Emp> list = empMapper.select("张", (short) 1,
LocalDate.of(2010, 01, 01), LocalDate.of(2020, 01, 01));
// 遍历集合输出
list.stream().forEach(s ->{
System.out.println(s);
});
}
}
说明:由于我们已经开启了MyBatis的驼峰命令自动映射开关,所以所有的字段值都已成功封装!
-
方式二(解决SQL注入风险)
- 使用MySQL提供的字符串拼接函数:concat('%' , '关键字' , '%')
concat - 字符串拼接函数演示:
-- concat 字符串拼接函数
select concat('Hello',' MySQL', ' World');
package com.gch.mapper;
import com.gch.pojo.Emp;
/**
加上@Mapper注解就代表程序在运行时会自动的创建该接口的代理对象,并且会将这个代理对象放入到IOC容器当中
*/
@Mapper
public interface EmpMapper {
/**
* 条件查询员工信息
* 使用MySQL提供的字符串拼接函数concat()来解决SQL注入问题
* @param name => 查询的姓名
* @param gender => 查询的性别
* @param begin => 查询的入职时间
* @param end => 查询的离职时间
* @return => 把查询返回的多条记录封装到List集合里面
*/
@Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from mybatis.emp where name like concat('%', #{name}, '%') and gender = #{gender} and entrydate between #{begin} and #{end} order by update_time desc")
public List<Emp> select(String name, short gender, LocalDate begin, LocalDate end);
}
测试类(见上):
生成的SQL都是预编译的SQL语句(性能高、防止SQL注入,安全)
1.6.4 参数名说明
在上面我们所编写的条件查询功能中,我们需要保证接口中方法的形参名和SQL语句中的参数占位符名相同。
参数名在不同的SpringBoot版本中,处理方案还不同:
-
在SpringBoot的2.x版本(保证参数名一致)
原因:SpringBoot的父工程对compiler编译插件进行了默认的参数parameters配置,使得在编译时,会在生成的字节码文件中保留原方法形参的名称,所以#{…}里面可以直接通过形参名获取对应的值
-
在springBoot的1.x版本 / 单独使用mybatis(使用@Param注解来指定SQL语句中的参数名)
原因:在编译时,生成的字节码文件当中,不会保留Mapper接口中方法的形参名称,而是使用var1、var2、...这样的形参名字,此时要获取参数值时,就要通过@Param注解来指定SQL语句中的参数名。