作者:~小明学编程
文章专栏:spring框架
格言:热爱编程的,终将被编程所厚爱。
目录
对MyBatis进行单元测试
springboot的单元测试
生成单元测试类
MyBatis中的增删查改
增
删
查
改
${} 和 #{} 的区别
SQL注入
模糊查询中的问题
resultMap 和 resultType
多表查询
一对一
一对多
动态SQL
if标签
trim标签
where标签
set标签
foreach标签
对MyBatis进行单元测试
springboot的单元测试
前面我们说到了如何进行mybatis的操作,但是mybatis的操作依旧非常的繁琐想要测试我们的一块代码有没有问题的话是非常的麻烦的,所以下面我们需要介绍一下在springboot中进行单元测试。
单元测试:
- 单元测试是对程序中的 最小单元 进行检查和验证的过程就叫做单元测试。
- 开发者通过一小段代码,来检验代码的一个很小的功能是否正确,是否完善。
单元测试的优点:
- 单元测试不需要启动 Tomcat。
- 如果中途改动了代码,在项目打包的时候会发现错误,因为打包之前,所有单元测试都必须通过,然后才能打包成功。
- 如果不使用单元测试的话,会导致访问本地数据库,也就是会 “污染” 本地数据库。
生成单元测试类
//表示当前的单元测试运行在spring boot项目中
@SpringBootTest
class UserMapperTest {
void getUserById() {
}
}
如此一来就生成了我们的单元测试的代码,想要测试的话需要添加一些代码。
@Resource
private UserMapper userMapper;
@Test
void getUserById() {
UserInfo userInfo = userMapper.getUserById(1);
System.out.println(userInfo);
Assertions.assertNotNull(userInfo);//判断当前的返回的对象是否为空
}
MyBatis中的增删查改
增
<insert id="add">
insert into userinfo(username,password,photo)
values(#{username},#{password},#{photo});
</insert>
test文件:
@Test
@Transactional
void add() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("张三");
userInfo.setPassword("1234");
userInfo.setPhoto("000");
int num = userMapper.add(userInfo);//影响的行数
Assertions.assertEquals(num,1);
}
这里使用了@Transactional的注解,这个注解主要的目的就是为了防止我们的sql污染了我们的数据库,如此操作我们对数据库的改变就不会提交也就影响不到我们的数据库了。
删
<delete id="del">
delete from userinfo where id=#{id}
</delete>
@Transactional
@Test
void del() {
int n = userMapper.del(1);
Assertions.assertEquals(n,1);
}
查
<select id="getUserById" resultType="com.example.demo.model.UserInfo">
select * from userinfo where id=#{id}
</select>
@Test
void getUserById() {
UserInfo userInfo = userMapper.getUserById(1);
System.out.println(userInfo);
Assertions.assertNotNull(userInfo);
}
改
<update id="update">
update userinfo set username=#{username} where id=#{id}
</update>
@Test
void getUserById() {
UserInfo userInfo = userMapper.getUserById(1);
System.out.println(userInfo);
Assertions.assertNotNull(userInfo);
}
可以看到我们的测试全部通过了。
${} 和 #{} 的区别
我们在xml文件中编写sql语句的时候可以使用$或者#来进行占位那么这两者有什么区别呢?
可以看到我们用#的时候在执行sql的时候其实是有一个预处理的。
但是我们使用$的时候只是做了一个简单的替换,这就会导致一个问题就是当我们替换的是一个字符串的时候会因为没有双引号而报错。既然这样那么我们总是使用#{}不就一劳永逸了吗,当然不是的,下面这种情况就必须使用${}。
<select id="getOrderList" resultType="com.example.demo.model.UserInfo">
select * from userinfo order by createtime ${order}
</select>
这段代码是我们根据创建时间来进行一个排序的,这里就需要传入我们的关键字desc或者asc,如果我们再去使用#{}的话相当于传入了一个带双引号的字符串这显而易见是会报错的。
- #{} 是预处理,${} 是直接替换。
- #{} 适用于所有类型的参数匹配,${} 指适用于数值类型。
- #{} 性能高,并且没有安全问题。但 ${} 存在 SQL 注入的问题。
- 如果在 构造SQL语句 的时候,如果替换的参数,是 SQL 关键字,使用 ${} 更好,比如说排序的时候,直接用 desc,asc 这样去替换。如果是用于字段 的话,需要获取到参数类型的信息,使用 #{} 更好。
SQL注入
所谓的sql注入就是我们在输入数据的时候将我们的数据当作sql语句来执行,然后这样就会导致我们的底层sql在执行的时候会存在误差,下面就来举一个例子来演示一下我们的sql注入问题。
<select id="login" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username='${username}' and password='${password}'
</select>
以上是一个简单的根据我们的用户名和密码来查找我们的数据的sql语句。
@Test
void login() {
String username = "a";
String password = "' or 1='1";
List<UserInfo> userInfo = userMapper.login(username,password);
log.info("用户:"+userInfo);
}
这里我们将密码改成这种奇怪的字符串,接着我们来看一下查询的结果。
这里我们可以看到,最终的查询语句变成了这样,直接将我们的所有数据都给查到了,这种情况显然不是我们想要看到的而且这种情况也是非常的危险的。
接着我们换成#再看看:
这个时候可以发现我们此时的password只是一个混乱的字符串,根本就查询不到任何的数据。
模糊查询中的问题
下面我们想要根据用户名中的一个字符来查询数据,这个时候我们应该怎么来写呢?
<select id="getUserByName" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username like "%${username}%"
</select>
以上是我们用$符来做的一个sql查询,但是这种写法肯定会有sql注入的问题,这个时候就需要我们#符来处理但是这样的话肯定会存在字符串中嵌套字符串的问题,所以这时候需要我们sql里面的一个函数concat()用来连接字符串。
<select id="getUserByName" resultType="com.example.demo.model.UserInfo">
select * from userinfo where username like concat('%',#{username},'%')
</select>
可以看到这个时候我们的问题就完美的解决了。
resultMap 和 resultType
前面我们在查询的时候都是对象中的属性名称和数据库中的列名一致的,但是有些时候二者的名称可能会不一致,这个时候如果再去执行代码就会报错。
所以这个时候就需要我们的resultMap将我们类中的属性名称和数据库中的属性名称给一一映射起来。
<resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
<!-- id主键映射-->
<id column="id" property="id"></id>
<!-- result普通属性映射-->
<result column="username" property="name"></result>
</resultMap>
其中column是数据库中的字段名称,property是我们要映射的类中的属性名称。
多表查询
一对一
上述我们解决了单表查询的问题,但是处理多表查询的时候又该如何去处理呢?
@Data
public class ArticleInfo {
private Integer id;
private String title;
private String content;
private String createtime;
private String updatetime;
private Integer uid;
private Integer rcount;
private Integer state;
private UserInfo userInfo;
}
这是我们的articlinfo表的参数这里我们加了userinfo的字段,而userinfo又是userinfo表,所以我们在查询的时候需要将userinfo表的内容全部给查询到。这个时候就需要resultMap的映射将我们的内容全部给映射出来。
<?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.ArticleMapper">
<resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">
<id column="id" property="id"></id>
<result column="title" property="title"></result>
<result column="content" property="content"></result>
<result column="createtime" property="createtime"></result>
<result column="updatatime" property="updatetime"></result>
<result column="uid" property="uid"></result>
<result column="rcount" property="rcount"></result>
<result column="state" property="state"></result>
<association property="userInfo" resultMap="com.example.demo.mapper.UserMapper.BaseMap">
<!-- 一对一,让userinfo这个字段和userMap中的resultMap相对应-->
</association>
</resultMap>
<select id="getArticleById" resultMap="BaseMap">
select a.*,u.*
from articleinfo a left join userinfo u
on a.uid=u.id
where a.id=#{id};
</select>
</mapper>
其中数据库中 的userinfo需要映射到UserMapper中的BaseMap,所以我们还要再去观察一下usermapper中的basemap是否健全。
<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="state" property="state"></result>
<result column="updatetime" property="updatetime"></result>
</resultMap>
这里我们将其补全了,然后就开始进行查询。
查询语句:多表查询
<select id="getArticleById" resultMap="BaseMap">
select a.*,u.*
from articleinfo a left join userinfo u
on a.uid=u.id
where a.id=#{id};
</select>
结果:
乍一看是没问题的但是我们做个对比就发现问题了。
这里面有两个id其中userinfo中的id应该是2但是我们看到我们mybatis中的查询结果却是1,这就是典型的覆盖问题,原因就是我们的查询中有两个id所以想要解决这个问题的话就需要将这两个id给区别开来。
<association property="userInfo" resultMap="com.example.demo.mapper.UserMapper.BaseMap"
columnPrefix="u_"> <!-- 处理两张表中有相同字段的情况 -->
<!-- 一对一,让userinfo这个字段和userMap中的resultMap相对应-->
</association>
<select id="getArticleById" resultMap="BaseMap">
select a.*,u.username u_username,u.id u_id,u.updatetime u_updatetime
from articleinfo a left join userinfo u
on a.uid=u.id
where a.id=#{id};
</select>
查询结果:
这个时候user中的id就变成了2了。
一对多
用户的属性:
@Data
public class UserInfo {
private Integer id;
private String name;
private String password;
private String photo;
private String createtime;
private String updatetime;
private int state;
private List<ArticleInfo> artList;
}
<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="state" property="state"></result>
<result column="a_updatetime" property="updatetime"></result>
<collection property="artList" resultMap="com.example.demo.mapper.ArticleMapper.BaseMap" columnPrefix="a_">
<!-- 类属性以及他们之间的对应关系-->
</collection>
</resultMap>
不同于一对一,一对多用的是collection来处理的。
sql代码:
<select id="getArticleById" resultMap="BaseMap">
select a.*,u.username u_username,u.id u_id,u.updatetime u_updatetime
from articleinfo a left join userinfo u
on a.uid=u.id
where a.id=#{id};
</select>
测试:
@Test
void getUserAndArticleById() {
UserInfo userInfo = userMapper.getUserAndArticleById(2);
log.info("用户:\n"+userInfo);
}
动态SQL
if标签
我们在使用数据库的时候不是所有的我们会遇到一种情况,那就是我们有的时候会传一些参数有的时候不会传递,所以我们在进行操作的时候该如何去操作呢?
if标签就是典型的控制我们什么时候需要某个参数什么时候不需要某个参数的,具体用法如下:
</insert>
<insert id="add2">
insert into userinfo(username,password
<if test="photo!=null">
,photo
</if>
)
values(#{name},#{password}
<if test="photo!=null">
,#{photo}
</if>
)
</insert>
这是我们的一个插入语句,我们的photo的字段可有可无所以我们用一个if标签来进行选择是否存在。
@Test
void add2() {
UserInfo user = new UserInfo();
user.setName("zhangsan");
user.setPassword("1234");
user.setPhoto("hhd.jpg");
Integer userInfo = userMapper.add2(user);
log.info("用户:"+userInfo);
}
}
trim标签
最主要的作用,去除 SQL 语句前后多余的某个字符。一共有四个属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀
<insert id="add3">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name!=null" >
username,
</if>
<if test="password!=null" >
password,
</if>
<if test="photo!=null" >
photo,
</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name!=null" >
#{name},
</if>
<if test="password!=null" >
#{password},
</if>
<if test="photo!=null" >
#{photo},
</if>
</trim>
</insert>
@Test
void add3() {
UserInfo user = new UserInfo();
user.setName("lisi");
user.setPassword("1234");
user.setPhoto("hhd.jpg");
Integer userInfo = userMapper.add2(user);
log.info("用户:"+userInfo);
}
where标签
主要作用是实现查询中的 where sql 替换的,它可以实现如果没有任何的查询条件,那么它可以隐藏查询中的 where sql,但如果存在 查询条件,那么会生成 where 的 sql 查询,并且使用 where 标签可以自动去除前面的 and 字符。
<select id="getUserById" resultMap="BaseMap">
select * from userinfo
<where>
<if test="id!=null">
id=#{id}
</if>
</where>
</select>
如果不传 id 的话:
@Test
void getUserById() {
UserInfo userInfo = userMapper.getUserById(null);
System.out.println(userInfo);
Assertions.assertNotNull(userInfo);
}
可以看到如果我们传递的是一个null的话,那么此时我们的动态sql中的where标签里面一个参数也没有所以我们的where标签自动取消了。
set标签
set标签用来更新我们的数据,其主要的功能在于可以动态的选择参数,也就是可以选择我们更新一个两个还是三个数据,同时set自带清除后面多余的逗号。
<update id="update2">
update userinfo
<set>
<if test="name!=null">
username=#{name},
</if>
<if test="password!=null">
password=#{password},
</if>
<if test="photo!=null">
photo=#{photo},
</if>
</set>
where id=#{id}
</update>
主要写法如上所示
@Test
void update2() {
UserInfo user = new UserInfo();
user.setName("xiaohuang");
user.setPassword("1234");
user.setId(2);
int num = userMapper.update2(user);
Assertions.assertEquals(1,num);
}
foreach标签
对集合进行遍历时可以使用该标签,foreach 标签有这些属性:
- collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象,也就是我们的形参的名称
- item:遍历时的每⼀个对象(集合中的元素)
- open:语句块开头的字符串(类似 trim的 prefix)
- close:语句块结束的字符串(类似 trim的 close)
- separator:每次遍历之间间隔的字符串(间隔符)
<delete id="delIds">
delete from userinfo where id in
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</delete>
@Test
void delIds() {
List<Integer> ids = new ArrayList<>();
ids.add(11);
ids.add(15);
int num = userMapper.delIds(ids);
log.info("批量删除的个数"+num);
}