前提紧要:
单表查询
参数占位符 #{} 和 ${}
● #{}:预编译处理。
● ${}:字符直接替换。
预编译处理:MyBatis 在处理#{}时,会将 SQL 中的 #{} 替换为?号,使⽤ PreparedStatement的 set ⽅法来赋值,会带上‘ ’。
直接替换:是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。String等类型就会报错,int类型执行效果相同。
${} 优点
使用 ${sort} 可以实现排序查询,⽽使⽤ #{sort} 就不能实现排序查询了,因为当使⽤ #{sort} 查询时,
如果传递的值为 String 则会加单引号,就会导致 sql 错误。

List<Userinfo> getListByOrder(@Param("order") String order);
<select id="getListByOrder" resultType="com.example.demo.entity.Userinfo">
select * from userinfo order by id ${order}
</select>
@Test
void getListByOrder() {
List<Userinfo> list = userMapper.getListByOrder("desc");
System.out.println(list);
}

它的问题是可能会带来越权查询和操作数据等问题。
SQL 注入问题
<select id="login" resultType="com.example.demo.entity.Userinfo">
select * from userinfo where username= '${username}' and password='${password}'
</select>
Userinfo login(@Param("username")String username,@Param("password")String password);
@Test
void login(){
String username="admin";
String password="' or 1='1"; //sql注入 安全问题
Userinfo userinfo=userMapper.login(username,password);
System.out.println("登录状态: "+ (userinfo==null?"失败":"成功"));
}
正常登录:

SQL注入:


此时将 ${ }改为 #{ } ,其他代码不变。
<select id="login" resultType="com.example.demo.entity.Userinfo">
select * from userinfo where username= #{username} and password=#{password}
</select>

${ }使用注意事项:一定是可以穷举的值,在使用之前一定要对传递的值进行合法性验证(安全性验证)
用于查询的字段,尽量使用 #{ } 预查询的方式
like 查询
like 使用 #{} 报错
<select id="getListByName" resultType="com.example.demo.entity.Userinfo">
select * from userinfo where username like '%#{username}%'
</select>
相当于: select * from userinfo where username like '%'username'%';
这里不能直接使用 ${},保证其安全性。可以考虑使用 mysql 的内置函数 concat() 来处理
mysql> SELECT CONCAT('张三','李四','王五');
result> 张三李四王五
实现代码如下:
<select id="getListByName" resultType="com.example.demo.entity.Userinfo">
select * from userinfo where username like concat('%',#{username},'%');
</select>
多表查询
如果是增、删、改返回搜影响的行数,那么在 mapper.xml 中是可以不设置返回的类型 ;
但是即使是最简单查询⽤户的名称也需要设置返回的类型
对于<select>查询标签来说⾄少需要两个属性:
id 属性:用于标识实现接⼝中的那个⽅法;
结果映射属性:结果映射有两种实现标签:<resultMap> 和 <resultType>。
返回字典映射:resultMap
resultMap 使用场景:
● 字段名称和程序中的属性名不同的情况,可使用 resultMap 配置映射
● ⼀对⼀和⼀对多关系,可使用 resultMap 映射并查询数据
字段名和属性名不同的情况

程序中的属性如下:
@Data
public class Userinfo {
private Integer id;
private String name; //username
private String password;
private String photo;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private int state;
}
<select id="getAll" resultType="com.example.demo.entity.Userinfo">
select * from userinfo
</select>
查询的结果如下:

name=null,name字段查询不到
这个时候就可以使用 resultMap 了,resultMap 的使用如下:
<!-- 标识 要映射的实体类-->
<resultMap id="baseMap" type="com.example.demo.entity.Userinfo" >
<!-- column 数据库字段名 property 程序中的属性名-->
<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" resultMap="baseMap">
select * from userinfo
</select>
查询的结果就有值了,如下图所示:

简单解决方案:使用别名 as
<select id="getAll" resultType="com.example.demo.entity.Userinfo">
select id,username as name,password,photo,createtime,updatetime,state from userinfo
</select>

多表查询
多表联查:联表查询语句(left join/inner join)+ XXX VO 解决。

@Data
public class Articlinfo {
private int id;
private String title;
private String content;
private String createtime;
private String updatetime;
private int uid;
private int rcount;
private int state;
}
继承Articleinfo 增加属性
@Data
public class ArticleinfoVO extends Articlinfo {
private String username;
}
@Mapper
public interface ArticleMapper {
ArticleinfoVO getById(@Param("id") Integer id);
}
返回值类型为ArticleinfoVO
<select id="getById" resultType="com.example.demo.entity.viewobject.ArticleinfoVO">
select a.*,u.username
from articleinfo a
left join userinfo u
on u.id=a.uid
where a.id=#{id}
</select>
@SpringBootTest
class ArticleMapperTest {
@Autowired
private ArticleMapper articleMapper;
@Test
void getById() {
ArticleinfoVO ariticleifoVO=articleMapper.getById(1);
System.out.println(ariticleifoVO);
}
}

public class ArticleinfoVO{ }很灵活,返回给前端什么值,就加入什么属性。
ariticleifoVO为什么这里只打印了username?
通过调试我们看到ariticleifoVO里面所有属性都赋值成功了。

在target目录下注意到@Data注解添加的toString方法中只有username

此时要想打印别的信息,重写toString方法即可。

@Override
public String toString() {
return "ArticleinfoVO{" +
"username='" + username + '\'' +
"} " + super.toString();
}
此时打印出全部信息:

在多表查询时,如果使⽤ resultType 标签,在⼀个类中包含了另⼀个对象是查询不出来被包含的对象 的,⽐如以下实体类:
Serializable接口
实体类中,都实现Serializable接口,不实现这个Serializable,会报错。
什么是序列化、反序列化
由于序列化和反序列化概念太抽象,以搬桌子为例,桌子太大了不能通过比较小的门,我们要把它拆了再运进去,这个拆桌子的过程就是序列化。同理,反序列化就是等我们需要用桌子的时候再把它组合起来,这个过程就是反序列化。
“序列化”下一个定义:
把原本在内存中的对象状态变成可存储或传输的过程称之为序列化。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
序列化前的对象和反序列化后得到的对象,内容是一样的(且对象中包含的引用也相同),但两个对象的地址不同。换句话说,序列化操作可以实现对任何可Serializable对象的”深度复制(deep copy)"。
什么情况下需要序列化
a)当你想把内存中的对象状态保存到一个文件中或者数据库中,以便可以在以后重新创建精确的副本;
b)当你想用套接字在网络上传送对象的时候(从一个应用程序域发送到另一个应用程序域中);
c)当你想通过RMI传输对象的时候;
序列化ID
private static final long serialVersionUID = 1L;
注意事项
a)序列化时,只对对象的状态进行保存,而不管对象的方法;
b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
d)并非所有的对象都可以序列化。
e) 序列化会忽略静态变量,即序列化不保存静态变量的状态。静态成员属于类级别的,不能序列化。添加了static、transient关键字后的变量不能序列化。
复杂情况:动态SQL使用
动态 sql 是Mybatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接。
可以参考官⽅⽂档:mybatis – MyBatis 3 | 动态 SQL
<if>标签
在注册用户的时候,可能会有这样⼀个问题,如下图所示:

注册分为两种字段:必填字段和非必填字段 。那如果在添加用户的时候有不确定的字段传⼊,就需要使用动态标签 <if> 来判断了。
photo字段没有默认值

没有默认值为空 和 NULL不一样

<insert id="add2">
insert into userinfo(username,
<if test="photo!=null">
photo,
</if>
password)
value(#{username},
<if test="photo!=null">
#{photo},
</if>
#{password})
</insert>
<trim>标签
如果所有字段都是非必填项,就考虑使用<trim>标签结合<if>标签,对多个字段都采取动态生成的方式。
<trim>标签中有如下属性:
● prefix:表示整个语句块,以prefix的值作为前缀
● suffix:表示整个语句块,以suffix的值作为后缀
● prefixOverrides:表示整个语句块要去除掉的前缀
● suffixOverrides:表示整个语句块要去除掉的后缀
<insert id="add3">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
usrname,
</if>
<if test="password!=null">
password,
</if>
<if test="photo!=null">
photo,
</if>
</trim>
value
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username!=null">
#{usrname},
</if>
<if test="password!=null">
#{password},
</if>
<if test="photo!=null">
#{photo},
</if>
</trim>
</insert>
<where>标签
传⼊的⽤户对象,根据属性做 where 条件查询,用户对象中属性不为 null 的,都为查询条件。
<select id="getListByParam" resultType="com.example.demo.entity.Userinfo">
select * from userinfo
<where>
<if test="username!=null">
username=#{username}
</if>
<if test="password!=null">
and password=#{password}
</if>
</where>
</select>
注意:where标签会删除最前面的and关键字
以上<where>标签也可以使⽤ <trim prefix="where" prefixOverrides="and"> 替换。
<set>标签
根据传⼊的用户对象属性来更新用户数据,可以使用<set>标签来指定动态内容。
<update id="update2">
update userinfo
<set>
<if test="username!=null">
username=#{username},
</if>
<if test="password!=null">
password=#{password},
</if>
<if test="photo!=null">
photo=#{photo},
</if>
</set>
where id=#{id}
</update>
注意:set标签会自动删除最后一个英文逗号
以上<set>标签也可以用<trim prefix="set" suffixOverrides=",">替换
<foreach>标签
对集合进⾏遍历时可以使用该标签。<foreach>标签有如下属性:
● collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
● item:遍历时的每⼀个对象
● open:语句块开头的字符串
● close:语句块结束的字符串
● separator:每次遍历之间间隔的字符串
int dels(List<Integer> ids);
<delete id="dels">
delete from userinfo where id in
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</delete>