前言:
大家好,我是良辰丫,今天还是我们的mybatis的学习,主要内容有两个占位符,sql注入问题,like模糊匹配,以及多表查询等,不断提升我们的编程能力,加油哈! ! !💌💌💌
🧑个人主页:良辰针不戳
📖所属专栏:javaEE进阶篇之框架学习
🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。
💦期待大家三连,关注,点赞,收藏。
💌作者能力有限,可能也会出错,欢迎大家指正。
💞愿与君为伴,共探Java汪洋大海。
目录
- 1. 参数占位符 #{} 和 ${}
- 2. $符号处理关键字
- 3. sql注入问题
- 4. like模糊匹配
- 4.1 like与%连接通过#号预处理
- 4.2 like与%连接通过$符号处理
- 4.3 使用sql的拼接函数concat处理like
- 5. sql字段与java后端字段不统一
- 5.1 使用resultMap映射
- 5.2 通过mysql的as别名
- 7. 多表联查
1. 参数占位符 #{} 和 ${}
- #{}:预编译处理。
- ${}:字符直接替换。
- 预编译处理:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement
的 set ⽅法来赋值。- 直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。
- 整型直接使用#和$都可以正常使用,但是字符串就会出现问题,那么接下来我们就使用字符串进行举例.
创建一个接口字段 :
Stu getStuName(@Param("name") String name);
xml配置,通过#号处理字段
<select id="getStuName" resultType="com.example.demo.entity.Stu">
select * from stu where name = #{name}
</select>
测试单元
@Test
void getStuName() {
Stu stu = stuMapper.getStuName("李白");
Assertions.assertEquals(10,stu.getId());
}
测试通过
接下来使用$号
<select id="getStuName" resultType="com.example.demo.entity.Stu">
select * from stu where name = ${name}
</select>
这个时候就会出现问题了,字符串是直接替换没有加引号.
我们如果想要去使用$去处理字符串也可以,我们只需要再xml里面的字段加上引号即可.
2. $符号处理关键字
使用$
可以处理关键字,比如我们sql的升序降序关键字,因为$符号是直接替换,重要的事情要多次说,这样方便大家记忆.
创建接口字段
List<Stu> getStuAll(String str);
xml配置文件
<select id="getStuAll" resultType="com.example.demo.entity.Stu">
select * from stu order by id ${str};
</select>
测试单元
@Test
void getStuAll() {
List<Stu> list = stuMapper.getStuAll("desc");
}
如果我们传关键字的时候使用#号就会报错.
3. sql注入问题
- 接下来我们来模拟一个登录操作,来简单描述一下我们的注入问题
- 我们首先需要把stu表中只留下一个数据,否则属性注入会出现问题.
定义接口信息
Stu login(@Param("id") Integer id,@Param("name") String name);
xml配置
<select id="login" resultType="com.example.demo.entity.Stu">
select * from stu where id = #{id} and name = #{name}
</select>
- #是不会出现属性注入的.
- 我们主要看$符号
$符号在处理字符串的时候需要给它加引号,sql里面单引号和双引号都可以.
xml信息
<select id="login" resultType="com.example.demo.entity.Stu">
select * from stu where id = ${id} and name = '${name}'
</select>
测试单元
@Test
void login() {
int id = 5;
String name = "候六";
//String name = "' or 1='1";
Stu stu = stuMapper.login(id,name);
if(stu != null){
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
}
如果我们把name属性的key值写成下面的呢?这样就会出现属性注入问题.
@Test
void login() {
int id = 5;
String name = "' or 1='1";
Stu stu = stuMapper.login(id,name);
if(stu != null){
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
}
我们会惊奇的发现这样也能查到数据库信息.
为什么会出现上面的问题呢?
- $符号是直接替换,并没有通过预处理,因此$符号会把传入的参数当成sql语句.
- 此时参数与原来的sql语句进行拼接.
上面出现了sql注入,如果我们换成了#号呢?
<select id="login" resultType="com.example.demo.entity.Stu">
select * from stu where id = #{id} and name = #{name}
<!-- select * from stu where id = ${id} and name = '${name}'-->
</select>
测试单元
@Test
void login() {
int id = 5;
//String name = "候六";
String name = "' or 1='1";
Stu stu = stuMapper.login(id,name);
if(stu != null){
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
}
为什么#号不会出现sql注入,因为#号是预处理,它只会把那个句子当成一个value值,不会当成sql语句
4. like模糊匹配
like模式匹配会出问题?
- like与%直接连接通过#号预处理会失败.
- like与%直接连接通过$符号不会编译错误,但是可能出现sql注入.
4.1 like与%连接通过#号预处理
List<Stu> getStuSel(String str);
<select id="getStuSel" resultType="com.example.demo.entity.Stu">
select * from stu where name like '#{str}%'
</select>
@Test
void getStuSel() {
String str = "李";
stuMapper.getStuSel(str);
}
我们惊奇的发现此时使用#号出错了,与%联合使用并没有真正的做到拼接作用.
4.2 like与%连接通过$符号处理
如果我们把#号改成$号,会测试成功嘛?
此时竟然测试成功.
- 虽然$符号测试成功,但是我们在上面了解到$会有sql注入的问题,那么接下来我们就要使用sql中的拼接函数concat实现我们相应的功能.
- 这样就能保证我们既使用#号,又可以成功查询
4.3 使用sql的拼接函数concat处理like
<select id="getStuSel" resultType="com.example.demo.entity.Stu">
<!-- select * from stu where name like '${str}%'-->
select * from stu where name like concat(#{str},'%')
</select>
5. sql字段与java后端字段不统一
如果我们java后端属性和sql的字段名字不统一的时候会出现什么问题呢?我们首先另外创一个表.
create table stu2(id int primary key auto_increment,
username varchar(20),
age int);
注意我们这个表是username属性,但是在我们java中是name属性
然后添加一条数据.
insert into stu2 values(null,'张三',20);
添加接口属性
Stu getStuId2(@Param("id") Integer id);
添加xml配置
<select id="getStuId2" resultType="com.example.demo.entity.Stu">
select * from stu2 where id=${id}
</select>
测试单元
@Test
void getStuId2() {
Stu stu = stuMapper.getStuId2(1);
System.out.println(stu);
}
运行单元测试代码,此时我们发现后端获取到的name属性为null.
5.1 使用resultMap映射
这个时候我们需要使用resultMap映射,把数据库的属性和java的属性一一映射.
添加接口方法.
Stu getStuId2(@Param("id") Integer id);
映射字段.
<resultMap id="Map" type="com.example.demo.entity.Stu">
<id column="id" property="id"></id>
<result column="username" property="name"></result>
<result column="age" property="age"></result>
</resultMap>
- id表示主键.
- column表示数据库的字段名.
- id = "Map"中的Map是标识.
- type后面添加的是要映射的实体类.
- property是程序中的属性名
- result普通的字段和属性,也就resultMap里的一个标签.
xml配置
<select id="getStuId2" resultMap="Map">
select * from stu2 where id=${id}
</select>
5.2 通过mysql的as别名
- resultMap映射中只映射name,在单表查询中没有问题,但是在多表查询有一定的问题.
- resultMap的方式比较复杂,有没有简单的方式呢?
mysql里面的as别名
<select id="getStuId2" resultType="com.example.demo.entity.Stu">
select id,username as name,age from stu2 where id=${id}
</select>
7. 多表联查
接下来我们创建一个学生信息表
create table stuInfo(
stuId int not null,
classInfo varchar(100),
ident varchar(20)
);
在entity创建一个学生信息实体类StuInfo
package com.example.demo.entity;
import lombok.Data;
@Data
public class StuInfo {
private int stuId;
private String classInfo;
private String ident;
}
在entity包下面建一个包vo,vo里面创一个类StuInfoVO
package com.example.demo.entity.vo;
import com.example.demo.entity.StuInfo;
import lombok.Data;
@Data
public class StuInfoVO extends StuInfo {
//不想写基础字段,可以去继承
private String username;
}
在mapper包里面创一个StuInfoMapper接口
package com.example.demo.mapper;
import com.example.demo.entity.vo.StuInfoVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface StuInfoMapper {
StuInfoVO getById(@Param("id") Integer id);
}
在资源文件的mybatis中添加一个配置文件StuInfoMapper.xml
<?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">
<!--namespace是命名空间,表示实现哪个接口-->
<mapper namespace="com.example.demo.mapper.StuInfoMapper">
<select id="getById" resultType="com.example.demo.entity.vo.StuInfoVO">
select id,name,classInfo,ident from stu,stuInfo where stu.id = stuInfo.stuId
and stu.id = #{id}
</select>
</mapper>
insert into stuInfo values(13,'计算机2020班','学生会主席');
<?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">
<!--namespace是命名空间,表示实现哪个接口-->
<mapper namespace="com.example.demo.mapper.StuInfoMapper">
</mapper>
整体的目录结构.
接下来我们实现一个简单的联合查询
创建接口字段
StuInfoVO getById(@Param("id") Integer id);
xml文件配置
<select id="getById" resultType="com.example.demo.entity.vo.StuInfoVO">
select id,name,classInfo,ident from stu,stuInfo where stu.id = stuInfo.stuId
and stu.id = #{id}
</select>
测试单元
package com.example.demo.mapper;
import com.example.demo.entity.vo.StuInfoVO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class StuInfoMapperTest {
@Autowired
private StuInfoMapper stuInfoMapper;
@Test
void getById() {
StuInfoVO stuInfoVO = stuInfoMapper.getById(13);
System.out.println(stuInfoVO);
}
}
通过继承的方式后端获取不到信息.
那么,接下来我们不使用继承
package com.example.demo.entity.vo;
import com.example.demo.entity.StuInfo;
import lombok.Data;
@Data
public class StuInfoVO{
//不想写基础字段,可以去继承
private String username;
private int stuId;
private String classInfo;
private String ident;
}
这样后端就能成功获取到我们的sql字段.
在多表联查中我们可以使用数据库里面的语句.
- left join.
- inner join.