这个专栏给大家介绍一下 Java 家族的核心产品 - SSM 框架
JavaEE 进阶专栏Java 语言能走到现在 , 仍然屹立不衰的原因 , 有一部分就是因为 SSM 框架的存在
接下来 , 博主会带大家了解一下 Spring、Spring Boot、Spring MVC、MyBatis 相关知识点
并且带领大家进行环境的配置 , 让大家真正用好框架、学懂框架
来上一篇文章复习一下吧
点击即可跳转到前置文章
CSDN 平台观感有限 , 可以私聊作者获取源笔记链接
我们之前介绍过 : 传参的时候使用 #{}
这种情况适用于 99.99% 的情况 , 但是还有 0.01% 的情况是使用不了 #{} 的
这也是一道非常经典的面试题 !!!
我们通过 getUserById 来举例 :
[外链图片转存中…(img-lZFq1WJB-1692756514196)]
我们通过单元测试来看一下 , 目前这个查询有无问题
那我们接下来 , 把 #{id} 替换成 ${id}
[外链图片转存中…(img-WpAtZwlE-1692756514197)]
这不都运行成功了吗 ? 那他们俩能有什么区别 ?
在 int 类型的传参中 , #{} 和 ${} 是一样的 , 但是二者在字符串类型的时候就不同了 .
接下来 , 给大家实验一下 , 我们在 UserMapper 里面添加一个方法 : 根据用户名查询用户
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// MyBatis 接口,此注解一定不能省略
@Mapper
public interface UserMapper {
// 根据用户姓名查询用户
public UserInfo getUserByName(@Param("username") String username);
}
[外链图片转存中…(img-8tt4Xp9Y-1692756514197)]
接下来 , 我们去 UserMapper.xml 里面编写 SQL 语句
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="getUserByName" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username = #{username}
select>
mapper>
接下来 , 我们去 UserMapper 里面去生成对应的单元测试
[外链图片转存中…(img-2clTLWJL-1692756514197)]
[外链图片转存中…(img-8aSTt11x-1692756514198)]
[外链图片转存中…(img-RA34kBzR-1692756514199)]
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void getUserByName() {
UserInfo userInfo = userMapper.getUserByName("admin");
System.out.println(userInfo);
}
}
[外链图片转存中…(img-NCfrBRLt-1692756514199)]
那我们把 #{} 改成 ${} 试试看
就报错了
[外链图片转存中…(img-plzsJeYu-1692756514200)]
那我们回到 MySQL 里面 , 尝试去搜索 admin 这个用户 , 能够正常搜索到 .
select * from userinfo where name = 'admin';
[外链图片转存中…(img-ludyzF1G-1692756514201)]
但是我们把 admin 外面的单引号去掉之后就会报错
[外链图片转存中…(img-ngbM0Sde-1692756514201)]
而且这个报错信息跟我们程序中的报错信息一致
[外链图片转存中…(img-ZGEh2hF7-1692756514201)]
我们就可以把 ${} 当成这样的 SQL 语句
select * from userinfo where username = admin; -- 注意:这个 admin 没加单引号
这样的话 , MySQL 就会误以为是 username 字段与 admin 字段进行比较 , 但是并未找到 admin 字段 , 所以就报错了 .
我们之前实验 int 类型的数据之所以没错 , 是因为我们的 SQL 语句被替换成了这样 :
select * from userinfo where username = 1; -- 这样写, MySQL 就会以为是判断 username 是否等于1 , 就不会报错了
所以这个就是 #{} 和 ${} 之间的区别
#{} : 预编译进行处理
与之前讲的 JDBC 里面的 ? 类似 , #{} 就是事先占一个位置的 , 这个位置有可能是 int 类型的 , 也有可能是 String 类型的 , 占了这个位置 , 这一列是什么 , 预编译的这个位置类型就已经确定 , 在执行的时候就不会发生报错了 .
e.g. 假如说我们现在的 SQL 语句长这样
select * from user where username = ?;
我们要往 ? 里面填充数据 , 但是我们提前知道 ? 这个位置是 String 类型 , 所以在对比的时候不会发生问题的 , 就相当于公司聚会 , 马云爸爸的位置还有马哥的位置为了避免尴尬 , 提前就规定好了座位 .
[外链图片转存中…(img-k2Xfav5E-1692756514202)]
${} : 字符直接替换
属于那种小公司 , 谁先来谁先找座 , 不讲究那些乱码七遭的说头
小结一下 :
#{} 就相当于自己能分辨数据类型 , 比如 String 类型的数据 , #{} 自己就知道加单引号
而 ${} 不是 , ${} 就相当于一个空位置 , 谁来谁上不添加任何修饰
所以我们传过去 String 类型的数据后
#{} 就知道把数据用单引号括起来表示这是字符串
select * from userinfo where username = 'admin'; -- 单引号是 #{} 主动去添加的
${} 就是什么也不做
select * from userinfo where username = admin; -- ${} 不会进行任何修饰
我们要是想让 ${} 的方式成功 , 我们需要自己去添加单引号
[外链图片转存中…(img-B35wR1Ks-1692756514202)]
❓ 那我们以后所有的查询都用单引号包裹起来不就得了 ?
✅ 是不可以的 . 虽然通过这种方式也能够查询到数据
[外链图片转存中…(img-u8YX41bO-1692756514202)]
但是这种查询是发生了隐式类型转换的 , 这种方式就不能去走索引了 , 性能就会大大降低 .
目前是数据量少 , 如果我们在线上环境 , 执行速度慢的就会非常明显了 .
但是刚才我们说 还有 0.01 {} 还有 0.01% 的情况 , 肯定还是有它自己存在的价值的 , 它的价值就在于使用直接替换 ( 还有0.01{}) 的这种形式可以 传递/执行 MySQL 的关键字
举个栗子 :
排序的时候 , 有两种情况 , 价格从低到高 / 价格从高到低 , 这是一种规则 .
❓ 那我们点这里的时候 , 传过去的数据是什么 ?
✅ 传过去的就是排序的规则 , 排序的规则属于 MySQL 的关键字 , 在 MySQL 中 , 排序的语句如下图
[外链图片转存中…(img-6MH6dt2I-1692756514203)]
那我们点击价格从高到底 , 传过去的就是 desc 关键字 .
点击价格从低到高 , 传过去的就是 asc 关键字 .
我们自己来设计一个例子 :
在 UserMapper 里面写一个方法声明 : 查询所有的信息(按价格排序)
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// MyBatis 接口,此注解一定不能省略
@Mapper
public interface UserMapper {
// 按排序条件查询所有的信息
public List<UserInfo> getAllByOrder(@Param("order") String order);
}
[外链图片转存中…(img-zJRkuXTI-1692756514203)]
接下来 , 我们去 UserMapper.xml 中编写 SQL 语句
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="getAllByOrder" resultType="com.example.demo.model.UserInfo">
select * from userinfo order by id ${}
select>
mapper>
我们去编写单元测试
[外链图片转存中…(img-nV5s7fx3-1692756514203)]
[外链图片转存中…(img-9TdQgnl8-1692756514203)]
[外链图片转存中…(img-z92ZTKgI-1692756514204)]
为了更方便查看效果 , 我们在 MySQL 中新添加一条数据
insert into userinfo (username,password) values ('zhangsan','zhangsan');
[外链图片转存中…(img-SgzoBkdm-1692756514204)]
如果我们使用的是 asc , 或者是 desc , 我们现在数据库里面查看一下效果
接下来我们编写单元测试的代码
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void getAllByOrder() {
List<UserInfo> list = userMapper.getAllByOrder("asc");
for (UserInfo userInfo : list) {
System.out.println(userInfo.toString());
}
}
}
运行一下 :
这是因为我们的 #{} 把 SQL 语句优化成了这样
select * from userinfo order by id 'asc';
[外链图片转存中…(img-OltZ6jCM-1692756514205)]
这条 SQL 语句是存在语法问题的 , 所以会报错 .
那我们把 #{} 改成 ${} 试试看 , 就可以了
[外链图片转存中…(img-hdUkl4uC-1692756514205)]
[外链图片转存中…(img-wvWLxNaC-1692756514205)]
假如我们有一个这样的例子 :
正常的查询应该是这样 :
那么什么叫 SQL 注入呢 ?
SQL 注入就是在不知道账号密码的情况下 , 就访问到了你的信息
SQL 注入就是这样的一个简单字符串 : ' or 1='1'
他利用的就是 SQL 执行漏洞
直接来看运行结果
[外链图片转存中…(img-9RPF7has-1692756514206)]
’ or 1=‘1’ 并不是你真正的密码啊 , 怎么就获取到你的信息了呢 ?
[外链图片转存中…(img-fnKbtTKf-1692756514206)]
如果使用了 ${} , 那么程序就会存在 SQL 注入的问题 , 因为他会把 ’ or 1=‘1’ 当成关键字来去执行代码
而 #{} 不会存在这种问题 , #{} 是预处理的 , 他会把 ’ or 1=‘1’ 当成密码来看 , 不认为这一串是 SQL 语句
那我们写个代码来实验一下
我们在 UserMapper 里面写一个登录方法
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// MyBatis 接口,此注解一定不能省略
@Mapper
public interface UserMapper {
public UserInfo login(@Param("username") String username,
@Param("password") String password);
}
接下来 , 我们在 UserMapper.xml 里面编写 SQL 语句
注意 : 这时候要保证我们数据表里面仅有一条数据 , 不然会报错
因为我们返回值是返回的对象 , 而不是 List
[外链图片转存中…(img-gs7buBy2-1692756514206)]
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="login" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username = ${username} and password = ${password}
select>
mapper>
接下来 , 我们编写单元测试
[外链图片转存中…(img-QZ7RbhER-1692756514206)]
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void login() {
// 测试 ${} 的时候 , 要把账号用单引号括起来
// 因为 ${} 识别字符串的时候会把他当成 SQL 语句,然后报错
String username = "'admin'";
String password = "'' or 1='1'";//SQL 注入
UserInfo userInfo = userMapper.login(username, password);
System.out.println("userinfo ->" + userInfo);
}
}
[外链图片转存中…(img-sOyG1T5v-1692756514207)]
再测试 #{}
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void login() {
// 测试 #{} 的时候,就不用再加单引号了
// 因为 #{} 会自动帮我们加
String username = "admin";
String password = "' or 1='1'";//SQL 注入
UserInfo userInfo = userMapper.login(username, password);
System.out.println("userinfo ->" + userInfo);
}
}
对于普通的查询 , 我们使用 #{} 都能搞得定 , 但是模糊查询比较特殊 , 模糊查询使用 % 或者 _ 来指代 0-n 个字符
本来我们的 SQL 实现是这个样子
<select id="findUserByName2" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username like '%#{username}%';
select>
我们上面使用的是 #{} 的形式 , 他会自己判断类型 . 我们这里传输过去的是 String 类型 , 所以实际的 SQL 就变为了
select * from userinfo where username like '%'username'%';
-- username 是字符串类型 , 就自动在 username 两边加了单引号
这样肯定是不可以的
[外链图片转存中…(img-AyM3fFuh-1692756514208)]
但是我们想要实现的 SQL 是 :
select * from userinfo where username like '%m%';
那咱们就改成使用 ${} 不就可以了吗 ? 答案真的有这样简单吗 ?
其实是不行的 . 想想之前咱们的淘宝案例 , 传过去的是 asc desc 这两个关键字 , 这两个关键字是可以进行穷举的 , 意思就是我们可以写两条 SQL 语句块来实现升序排列以及降序排列 , 然后这两个关键字我们是在 Controller 层里面去进行参数校验的 , 参数校验成功才可以去实现相对应的功能 .但是用户名能穷举吗 , 他的可能性无穷无尽 , 那咱们还能写无数个 SQL 语句块 ?
这肯定是不行的 , 不过我们可以通过 SQL 里面的一个内置函数 concat() 来进行处理 ,他可以实现将多个字符拼接在一起 ,那我们就把 %#{username}#
分成了三个部分 : % 、#{username} 、# ,这样的话把他们三个拼接到一起就可以了
<select id="findUserByName3" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username like concat('%',#{username},'%')
select>
那我们来试验一下 , 先在 UserMapper 里面写方法声明
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// MyBatis 接口,此注解一定不能省略
@Mapper
public interface UserMapper {
// 模糊查询
public UserInfo findUserByName3(@Param("username") String username);
}
然后 UserMapper.xml 我们刚才已经写完了
接下来完成单元测试即可
[外链图片转存中…(img-2514weQ0-1692756514208)]
[外链图片转存中…(img-juKFtIJ1-1692756514208)]
[外链图片转存中…(img-kvWM2XxK-1692756514209)]
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void findUserByName3() {
UserInfo userInfo = userMapper.findUserByName3("m");//传过去要按哪个字符/字符串去查找
System.out.println(userInfo.toString());
}
}
[外链图片转存中…(img-wSRirPM6-1692756514209)]
用 MySQL 命令行也查到了相关信息
[外链图片转存中…(img-ymdkh1Li-1692756514209)]
我们在进入公司上班的过程中 , 肯定会遇见这样的问题 :
之前我们学过 , 数据库的字段名一般通过下划线来分隔 , 比如 user_name
而 Java 语言的规范是变量的命名使用小驼峰命名法 , 比如 userName
那在公司之后 , 操作数据库的是一波人 , 他们习惯变量使用下划线分隔
那为了与数据库中的字段名保持一致 , 我们 Java 程序员就不得不低头 , 也采用下划线分割的方式 ?
那肯定不行 , 但是不保持一致还查询不到
[外链图片转存中…(img-dGLjjAiN-1692756514209)]那我们 MyBatis 也考虑到了这个问题 , 就推出了 resultMap , 他的功能与 resultType 一致
resultMap 可以解决程序中实体类的名称和数据库当中字段名不一样的问题
那 resultMap 怎样去使用呢 ?
resultMap 使用起来相对麻烦一下 , 他需要在 xml 文件中进行配置
我们就可以使用一个 标签在 xxxMapper.xml 里面进行声明
<resultMap id="BaseMap" type="com.example.demo.model.User">
<id column="id" property="id">id>
<result column="username" property="username">result>
<result column="password" property="pwd">result>
resultMap>
这段代码就相当于我们定义了一个返回结果的字典来映射数据库当中的字段和实体类当中的属性
意思就是把程序中的 id 和数据库中的 id 匹配上 , 其中 : property 代表的是程序里面的属性名 , column 代表数据库里面的字段
、
就是把 username 和 password 匹配上
那我们就可以在 UserMapper.xml 里面去声明 resultMap 了
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
<id column="id" property="id">id>
<result column="username" property="name">result>
<result column="password" property="password">result>
<result column="photo" property="photo">result>
<result column="createtime" property="createtime">result>
<result column="updatetime" property="updatetime">result>
<result column="state" property="state">result>
resultMap>
<select id="getAll" resultType="com.example.demo.model.UserInfo">
select * from userInfo
select>
mapper>
[外链图片转存中…(img-q2nbdvBl-1692756514210)]
然后我们测试的是 getAll 方法 ,就需要把 getAll 的方法对应的 xml 实现里面的 ResultType 删除掉 ,改成 ResultMap 即可 , 参数填写上面的 id
[外链图片转存中…(img-Oj8RyQxC-1692756514210)]
接下来 , 我们就可以去执行单元测试了
[外链图片转存中…(img-S95mEhYZ-1692756514210)]
虽然这样写麻烦一些 , 但是他可以解决数据库字段名与实体类属性名不一致的问题
[外链图片转存中…(img-msA7kh6F-1692756514210)]
我们的数据库目前有两张表 : articleinfo 以及 userinfo
其实这两张表是有关联关系的
[外链图片转存中…(img-nDlRdeb4-1692756514211)]
articleInfo 里面的 uid 对应的就是 userinfo 里面的 id , 这其实就是一个外键 .
那如果我们想得到所有文章的信息 ,并且得到每篇文章对应的作者的时候 , 我们就需要采用多表查询的方式了
在 MyBatis 里面 , 多表查询怎样实现呢 ?
目前我们的需求就是打印文章表的时候 , 顺便打印作者姓名
所以我们就可以先在文章表对应的实体类上 , 先添加作者姓名属性
[外链图片转存中…(img-4eCkQ1Lj-1692756514211)]
package com.example.demo.model;
import lombok.Data;
import java.util.Date;
/**
* 文章的实体类
*/
@Data
public class ArticleInfo {
private int id;
private String title;
private String content;
private Date createtime;
private Date updatetime;
private int uid;
private int rcount;//访问量
private int state;//状态
// 多表查询
private String username;//文章作者名 - 来源于别的表
}
接下来在 ArticleMapper 里面写多表联合查询的方法声明
package com.example.demo.mapper;
import com.example.demo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// 记得添加 @Mapper 注解
@Mapper
public interface ArticleInfoMapper {
// 查询文章及其作者信息
public List<ArticleInfo> getAll();
}
[外链图片转存中…(img-mPfL4vvi-1692756514211)]
接下来去 ArticleMapper.xml 里面编写多表联合查询的 SQL 语句
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ArticleInfoMapper">
<select id="getAll" resultType="com.example.demo.model.ArticleInfo">
select a.*,u.username from articleinfo as a
left join userinfo as u
on a.uid = u.id;
select>
mapper>
[外链图片转存中…(img-L3I5fYB7-1692756514211)]
先去数据库中看一下我们的 SQL 语句有没有问题
没有问题我们就可以跑单元测试了
[外链图片转存中…(img-pPmbenXq-1692756514212)]
[外链图片转存中…(img-MYhMRECU-1692756514212)]
[外链图片转存中…(img-D9tMXYsy-1692756514212)]
package com.example.demo.mapper;
import com.example.demo.model.ArticleInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
// 要记得加这个注解,代表 Spring Boot 的单元测试
@SpringBootTest
class ArticleInfoMapperTest {
// 属性注入
@Autowired
private ArticleInfoMapper articleInfoMapper;
@Test
void getAll() {
List<ArticleInfo> list = articleInfoMapper.getAll();
for (ArticleInfo info : list) {
System.out.println(info);
}
}
}
也成功打印了
要注意 :
那我们这两个字段的名称就是对不上呢 ?
我们有两种方案 :
我们把 AticleInfo 实体类里面的 username 属性改成了 name
在 ArticleMapper.xml 里面写 resultMap 标签
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ArticleInfoMapper">
<resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">
<id column="id" property="id">id>
<result column="username" property="name">result>
resultMap>
<select id="getAll" resultMap="BaseMap">
select a.*,u.username from articleinfo as a
left join userinfo as u
on a.uid = u.id;
select>
mapper>
单元测试运行一下
[外链图片转存中…(img-BYVWPDbK-1692756514213)]
所以我们只需要在 SQL 语句上进行重命名操作 (as 关键字)
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ArticleInfoMapper">
<select id="getAll" resultType="com.example.demo.model.ArticleInfo">
select a.*,u.username as name from articleinfo as a
left join userinfo as u
on a.uid = u.id;
select>
mapper>
[外链图片转存中…(img-t6x03m4p-1692756514214)]
这里是官方的 动态 SQL 文档 : https://mybatis.org/mybatis-3/zh/dynamic-sql.html
官方 : 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL , 可以彻底摆脱这种痛苦。
MyBatis 主要就是解决必传参数以及非必传参数的 , 也就是我们常见的必填项与非必填项
[外链图片转存中…(img-66QuD24A-1692756514214)]
标签 这种情况就适用于 : 传递的参数我现在是不确定的 , 有可能有用 , 有可能没用 .
我们就可以使用 标签来进行判断 , 他就会根据传递的参数进行 SQL 语句拼接
我们用 photo 字段举例
photo 用户有可能传 ,有可能不传 .
我们先来模拟一下不使用动态标签的时候 , SQL 语句的编写
insert into userinfo (username,password,photo) values ('张三','zhangsan',null);
-- 在没有学习动态标签的时候,我们是把可选参数和必选参数一起去添加的
-- 前端没给我们传过来头像数据,那我们就先插入一个 null
如果我们有动态标签了 , 用户没传头像 , 那我们插入数据就可以不管头像字段了
insert into userinfo (username,password) values ('李四','lisi');
[外链图片转存中…(img-1Japvhsq-1692756514216)]
NULL 和 空不是一样的东西 ,所以事情就变得很难搞了
[外链图片转存中…(img-RbrvXyvn-1692756514217)]
想要实现必选项的问题 , 我们就可以使用 标签
语法 :
<if test="photo!=null">
...
if>
如果有多个位置需要动态输入 , 我们就可以写多个 标签
我们来试验一下 , 以 User的添加举例
在这里再给大家推荐一个十分好用的插件 : MyBatisX
[外链图片转存中…(img-2WILAZKT-1692756514217)]
[外链图片转存中…(img-VdmfO0Yn-1692756514217)]
这个插件的作用就是 :
点击前面的小鸟 , 就可以跳转到接口声明或者 xxx.xml 实现
[外链图片转存中…(img-F9riPc0O-1692756514217)]
[外链图片转存中…(img-f4O85Km6-1692756514218)]
我们在 UserMapper 接口里面去声明方法
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// MyBatis 接口,此注解一定不能省略
@Mapper
public interface UserMapper {
// 添加用户
public int add(@Param("username") String username,
@Param("password") String password,
@Param("photo") String photo);
}
[外链图片转存中…(img-HvhdNRiB-1692756514218)]
接下来我们去 UserMapper.xml 里面去实现具体 SQL 实现
<insert id="add">
insert into userinfo(username,password,photo)
values(#{username},#{password},#{photo})
insert>
但是我们的 photo 字段不是必传项 , 所以需要程序员去设置一下
<?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="com.example.demo.mapper.UserMapper">
<!-- insert 只有一个参数:函数名 -->
<insert id="add">
insert into userinfo (username,
<if test="photo!=null">
photo,
</if>
password)
values (#{username}
<if test="photo!=null">
,#{photo}
</if>
,#{password})
</insert>
</mapper>
添加完之后 , 我们就可以通过单元测试验证一下了
如果数据库中的自增主键 ID 已经混乱了 , 我们就可以通过这条 SQL 去修改自增主键的 ID
alter table ‘your_table_name’ auto_increment = '期望的id值'
[外链图片转存中…(img-Mf3MK18l-1692756514218)]
[外链图片转存中…(img-03k2Dyjw-1692756514218)]
我们先实验 : 可选参数传值的情况
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void add() {
int result = userMapper.add("王五","wangwu","default.png");
System.out.println("添加用户结果:" + result);
}
}
[外链图片转存中…(img-s0470fUN-1692756514219)]
[外链图片转存中…(img-X5u7eIOF-1692756514219)]
我们再来试验一下可选参数不传值的情况 :
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void add() {
int result = userMapper.add("老六","laoliu",null);
System.out.println("添加用户结果:" + result);
}
}
[外链图片转存中…(img-vIyYPcKo-1692756514219)]
[外链图片转存中…(img-Tpa2bHBh-1692756514220)]
字面意思就是 : 去掉某些信息
我们就可以结合 标签完成一些事情
标签中有如下属性 :
以 prefixOverrides、suffixOverrides 我们来举个栗子 :
我们刚才在写这里的时候 , 其实有点取巧了
[外链图片转存中…(img-WsjamgOd-1692756514220)]
如果我们这样写
<insert id="add">
insert into userinfo (username,password,
<if test="photo!=null">
photo
if>)
values (#{username},#{password},
<if test="photo!=null">
#{photo}
if>
)
insert>
如果我们传入照片 , 无所谓了
但是如果我们没传进去照片 , 那么 SQL 语句就变成了
insert into userinfo (username,password, ) values ("老六","laoliu", );
[外链图片转存中…(img-6MxS5rbI-1692756514220)]
就变成了病句 , 最后面的逗号就单蹦了 .
那使用我们的 就解决不了了 , 我们就可以搭配 使用
我们把场景设置的极端一些 : 所有字段都设置成可填字段
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<insert id="add">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
username,
if>
<if test="password!=null">
password,
if>
<if test="photo!=null">
photo,
if>
trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
#{username},
if>
<if test="password!=null">
#{password},
if>
<if test="photo!=null">
#{photo},
if>
trim>
insert>
mapper>
其中 , 我们最后一个字段 photo 加不加逗号无所谓了 , 因为我们已经有 标签里面的 suffixOverrides 属性保驾护航了 , 不加也不会报错 , 加了更不会报错
测试一下吧
先测试每个字段都插入的情况
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void add() {
int result = userMapper.add("老七","laoqi","default.png");
System.out.println("添加用户结果:" + result);
}
}
[外链图片转存中…(img-dSKxgy0Z-1692756514220)]
[外链图片转存中…(img-odbTSjoC-1692756514221)]
我们再来试一下不传入 photo 字段的情况
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void add() {
int result = userMapper.add("老八","laoba",null);
System.out.println("添加用户结果:" + result);
}
}
[外链图片转存中…(img-PZMHuJDc-1692756514221)]
[外链图片转存中…(img-wvYvaqkW-1692756514221)]
标签是针对于查询的
比如一个页面上 , 我们可以通过用户名查询 , 也可以通过用户 ID 去查询
我们选择使用用户名查询 , 那么这个用户名我们可以传 , 也可以不传 .
传的话 , 就返回对应用户信息 . 不传的话 , username 就是空 , 应该返回所有用户信息 , 但是事与愿违
[外链图片转存中…(img-942M8mAE-1692756514221)]
那我们能不能使用 标签呢 ?
正常情况下 , 我们使用 标签 , 就可以把 SQL 看成这样
select * from userinfo where username = '';
但是我们不传参数的话 , SQL 就变成了这样
select * from userinfo where;
select * from userinfo where username = 'zhangsan' and photo = '';
假如我们使用 标签 , 把 where username = 'zhangsan'
包裹起来 , 再用另外一个 标签把 and photo = ''
包裹起来 , 那我们第一个参数不填 , SQL 就变成了这样
select * from userinfo and photo = '';
这个 SQL 也是有错误的
所以 标签就不太好使了 , 我们需要使用 标签了 , 标签仍然是与 标签搭配使用
我们把之前的 getUserByName 改造一下
[外链图片转存中…(img-Sjcoz30u-1692756514222)]
提前把这里改回来
[外链图片转存中…(img-Am1mZEsv-1692756514222)]
<?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="com.example.demo.mapper.UserMapper">
<select id="getUserByName" resultType="com.example.demo.model.UserInfo">
select * from userinfo
<where>
<!-- <where>标签可以天然去除第一个标签里面的 and -->
<if test="username!=null">
and username=#{username}
</if>
</where>
</select>
</mapper>
进行单元测试
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void getUserByName() {
UserInfo userInfo = userMapper.getUserByName("admin");
System.out.println(userInfo);
}
}
[外链图片转存中…(img-h4aRIydG-1692756514222)]
我们试一下不传名字
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void getUserByName() {
UserInfo userInfo = userMapper.getUserByName(null);
System.out.println(userInfo);
}
}
清除一下无效数据
因为测试类返回的是 UserInfo , 而不是 UserInfo 数组 , 没能力支持多条数据 , 只能实验一组数据
[外链图片转存中…(img-8MLtpTWv-1692756514223)]
运行一下
[外链图片转存中…(img-IIhH9qMk-1692756514223)]
我们就可以对 标签做一个总结 :
[外链图片转存中…(img-xf9jzGfc-1692756514223)]
标签常常使用在 update 语句中
update userinfo set password = "123456" where id = 1;
标签是去除掉后面的 ,
的 , 而 标签是去除掉前面的 and
的
我们可以来试一试
在 UserMapper 接口当中声明方法
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// MyBatis 接口,此注解一定不能省略
@Mapper
public interface UserMapper {
// 修改用户
public int updateById(@Param("username") String username,
@Param("password") String password,
@Param("id") Integer id);
}
[外链图片转存中…(img-ycBNpq6A-1692756514224)]
然后去 UserMapper.xml 中实现具体的 SQL
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<update id="updateById">
update userinfo
<set>
<if test="username != null">
username = #{username},
if>
<if test="password != null">
password = #{password},
if>
set>
where id=#{id}
update>
mapper>
[外链图片转存中…(img-czGu6trh-1692756514224)]
接下来 , 编写单元测试代码
单元测试的代码生成方法就不一一截图了
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void updateById() {
int result = userMapper.updateById(null,"666",1);
System.out.println("添加用户结果:" + result);
}
}
[外链图片转存中…(img-ZlXCiNv3-1692756514224)]
标签的作用 :
,
的用途 : 我想实现一个批量删除的功能 , 传递的是一个 id 集合
delete from userinfo where id in(3,4,5);
但是我们括号里面并不是几个数 , 而是一个 list 集合 , 我们通过 标签循环集合当中的每个元素
标签有几个属性
[外链图片转存中…(img-wlwAM7Q5-1692756514225)]
为了测试 标签 , 我们在数据库中构造几条数据
insert into userinfo (username,password) values ('张三','123456');
insert into userinfo (username,password) values ('李四','123456');
insert into userinfo (username,password) values ('王五','123456');
[外链图片转存中…(img-7ntvwiGU-1692756514225)]
先在 UserMapper 接口中声明方法
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// MyBatis 接口,此注解一定不能省略
@Mapper
public interface UserMapper {
// 删除多条数据
public int delByIds(List<Integer> ids);
}
再去 UserMapper.xml 中实现 具体 SQL
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<delete id="delByIds">
delete from userinfo where id in
<foreach collection="ids" item="item" open="(" close=")" separator=",">
#{item}
foreach>
delete>
mapper>
[外链图片转存中…(img-oXSszqX0-1692756514226)]
最后生成单元测试类检验一下
package com.example.demo.mapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest // 当前测试的上下文环境为 Spring Boot
class UserMapperTest {
// 要测试谁 , 就注入谁
@Autowired
private UserMapper userMapper;
@Test
void delByIds() {
List<Integer> list = new ArrayList<>();
list.add(6);
list.add(7);
list.add(8);
int result = userMapper.delByIds(list);
System.out.println("删除了:" + result);
}
}
[外链图片转存中…(img-NhQTewAx-1692756514226)]