MyBatis操作数据库(2)

前提紧要:

MyBatis操作数据库(1)


单表查询

参数占位符 #{} 和 ${}

● #{}:预编译处理。

● ${}:字符直接替换。

预编译处理: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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值