侧边栏壁纸
博主头像
快乐江湖的博客博主等级

更多内容请点击CSDN关注“快乐江湖”

  • 累计撰写 127 篇文章
  • 累计创建 33 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

第九章第二节:MyBatis之增、删、改、查

快乐江湖
2023-12-05 / 0 评论 / 0 点赞 / 9 阅读 / 26610 字

一:说明

(1)如何传参

在接口的方法中传递参数时可以直接传参,但是当字段过多时,就不那么合适了。因此我们直接传递对象,然后赋值时直接使用对象中的属性

例如后面查询例子

(2)#{}${}

在MyBatis中,#{}${}是两种不同的参数注入方式,它们在SQL语句中的使用有一些重要的区别

  • #{}(预编译): 使用#{}时,MyBatis会自动将传入的值进行预编译处理,将参数值安全地嵌入SQL语句中。这样可以有效防止SQL注入攻击,因为用户输入的数据不会直接拼接到SQL语句中
  • ${}(直接替换):直接将参数的值拼接到SQL语句中,不进行预编译处理。这种方式更像是简单的字符串替换,可以用于处理一些特殊需求,但是也容易引发SQL注入问题,因为用户输入的数据直接嵌入到SQL语句中

尽管推荐使用#{}来防止SQL注入,但有一些情况下可能需要使用${}。主要的情况包括

  • 动态表名或列名:如果你需要动态指定表名或列名,而且这个表名或列名不能通过预编译参数来实现,那么可能需要使用${}。在这种情况下,确保动态生成的表名或列名是可信的,以避免SQL注入风险
  • 排序字段的动态指定:有时候你可能希望动态指定排序的字段,这时候可能需要使用${}

(3)SQL注入问题

SQL注入:是一种常见的网络安全问题,它发生在应用程序未正确验证用户输入数据的情况下。攻击者可以通过巧妙构造的输入,向数据库发送恶意的SQL查询,从而绕过应用程序的身份验证和访问控制,甚至执行潜在的破坏性操作

我们通过一个简单的案例来说明SQL注入问题。考虑一个具有登录功能的网站,用于验证用户提供的用户名和密码。假设这个网站使用以下SQL查询来检查用户凭据

SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的密码';

现在,假设用户输入了以下作为用户名的内容:

' OR '1'='1' --

当这个输入传递到SQL查询中时,查询变成了:

SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = '输入的密码';

出现这个问题的原因在于

  • OR '1'='1' 总是为真,因为1等于1。这就是一个恶意用户想要达到的目标,绕过身份验证
  • -- 是SQL中的注释符号,它表示后续的所有内容都是注释,不会被执行。这对于绕过密码检查非常重要

二:增、改、删

(1)增

  • 以在userinfo表中添加一条记录为例

控制层

package com.example.mybatisstudy.controller;  

import com.example.mybatisstudy.entity.UserInfo;  
import com.example.mybatisstudy.service.UserService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.RequestBody;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  

import java.time.LocalDateTime;  

@RestController  
@RequestMapping("/user")  
public class UserController {  

    @Autowired  
    private UserService userService;  

    // 当返回实体对象时,Spring会自动转换为json  
    @RequestMapping("/getuserbyid")  
    public UserInfo getUserById(Integer id) {  
        if (id == null) return null;  
        return userService.getUserById(id);  
    }  

    @ RequestMapping("/adduser")  
    public String addUser(@RequestBody UserInfo userInfo) {  
        userInfo.setCreatetime(LocalDateTime.now());  
        userInfo.setUpdatetime(LocalDateTime.now());  
        int res = userService.addUser(userInfo);  
        if (res == 1) {  
            return "插入成功";  
        }  
        return "插入失败";  


    }  
}

服务层

package com.example.mybatisstudy.service;  

import com.example.mybatisstudy.entity.UserInfo;  
import com.example.mybatisstudy.mapper.UserMapper;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  

@Service  
public class UserService {  
    // Spring会找这个interface的实现类  
    // 如果有多个实现类就会报错(可以使用resource解决)  
    @Autowired  
    private UserMapper userMapper;  

    // 这个方法名建议和接口中的方法名一致  
    public UserInfo getUserById(Integer id) {  
        return userMapper.getUserById(id);  
    }  

    public Integer addUser(UserInfo userInfo) {  
        return userMapper.addUser(userInfo);  
    }  


}

Mapper

package com.example.mybatisstudy.mapper;  

import com.example.mybatisstudy.entity.UserInfo;  
import org.apache.ibatis.annotations.Mapper;  

import java.util.List;  

@Mapper  
public interface UserMapper {  
    /**  
     * 根据用户id查询用户信息  
     * @param id  
     * @return  
     */  
    UserInfo getUserById (Integer id);  

    /**  
     * 查询全部  
     * @return  
     */  
    List<UserInfo> getALL();  

    /**  
     * 添加用户  
     */  

    int addUser(UserInfo userInfo);  
}

xml

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">  
<mapper namespace="com.example.mybatisstudy.mapper.UserMapper">  
    <select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">  
        select * from userinfo where id = #{id}  
    </select>  

    <select id="getALL" resultType="com.example.mybatisstudy.entity.UserInfo">  
        select * from userinfo  
    </select>  

    <insert id="addUser">  
        insert into userinfo(username,password,createtime,updatetime)  
        values(#{username},#{password},#{createtime},#{updatetime})  
    </insert>  

</mapper>

测试


如果需要返回自增id,则mapper.xml实现如下

  • useGeneratedKeys:取出主键
  • keyColumn:设置生成键值在表中的列名,如果生成列不止一个,可以用都好分隔多个属性名称
  • keyProperty:将主键取出之后赋值给类的某个属性
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">  
<mapper namespace="com.example.mybatisstudy.mapper.UserMapper">  
    <select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">  
        select * from userinfo where id = #{id}  
    </select>  

    <select id="getALL" resultType="com.example.mybatisstudy.entity.UserInfo">  
        select * from userinfo  
    </select>  

    <insert id="addUser" useGeneratedKeys="true" keyProperty="id">  
        insert into userinfo(username,password,createtime,updatetime)  
        values(#{username},#{password},#{createtime},#{updatetime})  
    </insert>  

</mapper>
```java
@ RequestMapping("/adduser")  
public String addUser(@RequestBody UserInfo userInfo) {  
    userInfo.setCreatetime(LocalDateTime.now());  
    userInfo.setUpdatetime(LocalDateTime.now());  
    int res = userService.addUser(userInfo);  
    if (res == 1) {  
        return "插入成功" + userInfo.getId();  
    }  
    return "插入失败";  


}

(2)改

  • 以在userinfo表中将id为2的username由“张三”更改为“张奇”为例

控制层

package com.example.mybatisstudy.controller;  

import com.example.mybatisstudy.entity.UserInfo;  
import com.example.mybatisstudy.service.UserService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.RequestBody;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  

import java.time.LocalDateTime;  

@RestController  
@RequestMapping("/user")  
public class UserController {  

    @Autowired  
    private UserService userService;  

    // 当返回实体对象时,Spring会自动转换为json  
    @RequestMapping("/getuserbyid")  
    public UserInfo getUserById(Integer id) {  
        if (id == null) return null;  
        return userService.getUserById(id);  
    }  

    @RequestMapping("/adduser")  
    public String addUser(@RequestBody UserInfo userInfo) {  
        userInfo.setCreatetime(LocalDateTime.now());  
        userInfo.setUpdatetime(LocalDateTime.now());  
        int res = userService.addUser(userInfo);  
        if (res == 1) {  
            return "插入成功" + userInfo.getId();  
        }  
        return "插入失败";  

    }  

    @RequestMapping("/update")  
    public String updateUserNameById(Integer id, @RequestParam("newname") String newUserName) {  
        int res = userService.updateUserNameById(id, newUserName);  
        if (res == 1) {  
            return "更改成功";  
        }  

        return "更改失败";  
    }  
}

服务层

package com.example.mybatisstudy.service;  

import com.example.mybatisstudy.entity.UserInfo;  
import com.example.mybatisstudy.mapper.UserMapper;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  

@Service  
public class UserService {  
    // Spring会找这个interface的实现类  
    // 如果有多个实现类就会报错(可以使用resource解决)  
    @Autowired  
    private UserMapper userMapper;  

    // 这个方法名建议和接口中的方法名一致  
    public UserInfo getUserById(Integer id) {  
        return userMapper.getUserById(id);  
    }  

    public Integer addUser(UserInfo userInfo) {  
        return userMapper.addUser(userInfo);  
    }  

    public Integer updateUserNameById(Integer id, String newUserName) {  
        return userMapper.updateUserNameById(id, newUserName);  
    }  


}

Mapper

package com.example.mybatisstudy.mapper;  

import com.example.mybatisstudy.entity.UserInfo;  
import org.apache.ibatis.annotations.Mapper;  

import java.util.List;  

@Mapper  
public interface UserMapper {  
    /**  
     * 根据用户id查询用户信息  
     * @param id  
     * @return  
     */  
    UserInfo getUserById (Integer id);  

    /**  
     * 查询全部  
     * @return  
     */  
    List<UserInfo> getALL();  

    /**  
     * 添加用户  
     */  

    int addUser(UserInfo userInfo);  

    /**  
     * 修改用户名  
     */  
    int updateUserNameById(Integer id, String newUserName);  
}

xml

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">  
<mapper namespace="com.example.mybatisstudy.mapper.UserMapper">  
    <select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">  
        select * from userinfo where id = #{id}  
    </select>  

    <select id="getALL" resultType="com.example.mybatisstudy.entity.UserInfo">  
        select * from userinfo  
    </select>  

    <insert id="addUser" useGeneratedKeys="true" keyProperty="id">  
        insert into userinfo(username,password,createtime,updatetime)  
        values(#{username},#{password},#{createtime},#{updatetime})  
    </insert>  

    <update id="updateUserNameById">  
        update userinfo set username=#{newUserName} where id=#{id}  
    </update>  

</mapper>

测试


(3)删

  • 略:删除操作和更新操作非常相似

三:查

(1)单表查询

单表查询非常简单,因此不再阐述。这里主要说明一种非常特殊的情况:like使用#{}报错

List<UserInfo> getUserByName(String UserName);
```xml
<select id="getUserByName" resultType="com.example.mybatisstudy.entity.UserInfo">  
    select * from userinfo where username like '%#{username}%'  
</select>

这个sql语句相当于

select * from userinfo where username like '%'username'%'

在不使用${}(最好不要使用)的前提下,如果要解决这个问题,可使用MySql内置函数concat()解决

<select id="getUserByName" resultType="com.example.mybatisstudy.entity.UserInfo">  
    select * from userinfo where username like concat('%', #{username}, '%');  
</select>

(2)resultType和resultMap

A:resultType

resultType:用于指定查询结果的类型,即数据库查询返回的每一行记录将被映射为指定类型的对象。它通常用于简单的映射场景,其中查询返回的数据可以直接映射到一个简单的 Java 对象或基本数据类型。适用于绝大多数情况

<select id="selectUser" resultType="com.example.User">
    SELECT * FROM users WHERE id = #{id}
</select>

但如果数据库字段名和类属性名不同时,就会出现下面的问题


<select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">  
    select * from userinfo where id = #{id}  
</select>

B:resultMap

resultMap:提供了更灵活和复杂的结果映射机制。它允许你通过定义映射规则,将查询结果的列映射到 Java 对象的属性或其他复杂结构中。它通常用于处理复杂查询,例如多表连接查询、嵌套查询等情况

<resultMap id="testMap" type="com.example.mybatisstudy.entity.UserInfo">  
    <id column="id" property="id"></id>  
    <result column="username" property="UN"></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="getUserById" resultMap="testMap">  
    select * from userinfo where id = #{id}  
</select>

C:更为简单的解决方法

仍然使用resultType,我们可以使用别名解决

<select id="getUserById" resultType="com.example.mybatisstudy.entity.UserInfo">  
    select id, username as UN, password, photo, createtime, updatetime, state from userinfo where id = #{id}  
</select>

(3)多表查询

  • 以在articleinfo表中查询uid=1username为例

由于articleinfo中没有username字段,所以要联合userinfo表查询。我们可以建立类似于视图的实体

ArticleInfo.java

package com.example.mybatisstudy.entity;  

import lombok.Data;  

@Data  
public class ArticleInfo {  
    private int id;  
    private String title;  
    private String content;  
    private String createtime;  
    private String updatetime;  
    private int uid;  
    private int rcount;  
    private int state;  
}

ArticleInfoView.java

package com.example.mybatisstudy.entity.view;  

import com.example.mybatisstudy.entity.ArticleInfo;  
import lombok.Data;  


@Data  
public class ArticleInfoView extends ArticleInfo {  
    private String username;  
}

ArticleMapper.java

package com.example.mybatisstudy.mapper;  

import com.example.mybatisstudy.entity.ArticleInfo;  
import com.example.mybatisstudy.entity.view.ArticleInfoView;  
import org.apache.ibatis.annotations.Mapper;  

@Mapper  
public interface ArticleMapper {  
    ArticleInfoView getById (Integer id);  
}

ArticleMapper.xml

  • select article.*, user.username: 这部分指定了要选择的列。它从名为articleinfo的表中选择所有列,并从名为userinfo的表中选择username列。这意味着查询的结果将包含来自articleinfo表的所有列,以及来自userinfo表的username
  • from articleinfo article: 这部分指定了要从哪个表中检索数据。表名为articleinfo,它用别名article来简化查询中的引用
  • left join userinfo user on user.id=article.id: 这是一个左连接操作,它将articleinfo表与userinfo表连接起来。连接的条件是两个表中的id列相等。左连接意味着即使在userinfo表中找不到匹配的id,仍然会返回articleinfo表中的行。如果没有匹配的userinfo行,相关的username列将包含NULL值
  • where article.id=${id}: 这部分是一个筛选条件,它限制了查询的结果,只包含满足条件的行。${id}是一个占位符,它表示在实际执行查询时将被具体的值替换。这个条件指定了在articleinfo表中选择具有特定id值的行
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybati s.org/dtd/mybatis-3-mapper.dtd">  
<mapper namespace="com.example.mybatisstudy.mapper.ArticleMapper">  
    <select id="getById" resultType="com.example.mybatisstudy.entity.view.ArticleInfoView">  
        select article.*, user.username from articleinfo article left join userinfo user on user.id=article.id where <article></article>.id=${id}  
    </select>  

</mapper>

四:动态SQL

动态SQL:它允许你根据不同的条件生成不同的SQL语句,从而更灵活地构建和执行SQL查询。动态SQL通常用于构建动态查询条件,避免在代码中硬编码多个不同版本的SQL语句

例如下图,在用户注册时,可能面临【必填字段】和【非必填字段】的问题。当一些不确定字段传入时,应该如何编写呢?MyBatis的动态SQL可以解决这一问题

(1)if标签

if标签:允许你在SQL语句中添加条件判断

如下<if>标签用于条件判断。它的test属性用于指定一个条件表达式,只有当条件表达式为true时,<if>标签内的内容才会被包含在最终生成的SQL语句中。在这个例子中,如果usernameage都不为null,那么对应的条件语句会被包含在最终的SQL语句中,从而实现了动态生成SQL语句的效果

<select id="getUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  WHERE 1 = 1
  <if test="username != null">
    AND username = #{username}
  </if>
  <if test="age != null">
    AND age = #{age}
  </if>
</select>

再来一个比较复杂的例子:假设我们有一个查询用户的功能,用户可以选择按照用户名升序或降序排序,也可以选择按照年龄升序或降序排序

<select id="getUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  WHERE 1 = 1
  <if test="orderByUsername != null">
    ORDER BY username
    <if test="ascending">
      ASC
    </if>
    <if test="!ascending">
      DESC
    </if>
  </if>
  <if test="orderByAge != null">
    <if test="orderByUsername == null"> <!-- 如果已经有了排序条件,追加一个逗号 -->
      ORDER BY
    </if>
    age
    <if test="ascending">
      ASC
    </if>
    <if test="!ascending">
      DESC
    </if>
  </if>
</select>

插入的例子:我们使用了<if>标签来判断是否需要插入年龄信息。如果用户提供了年龄信息,那么在生成的插入语句中会包含年龄字段。否则,将只插入用户名和邮箱信息

<insert id="insertUser" parameterType="User">
  INSERT INTO users (username, email
  <if test="age != null">
    , age
  </if>
  ) VALUES (#{username}, #{email}
  <if test="age != null">
    , #{age}
  </if>
  )
</insert>

(2)trim标签

trim标签:用来处理SQL语句的前缀、后缀、以及包裹元素之间的逗号问题,从而使动态SQL的构建更加灵活。有以下属性

  • prefix:表示整个语句块以它作为前缀
  • suffix:表示整个语句块以它作为后缀
  • prefixOverrides:表示整个语句块要去除掉的前缀
  • suffixOverrides:表示整个语句块要去除掉的后缀

如下:假设我们有一个插入用户的功能,用户可以选择是否插入用户的年龄信息

  • <trim>标签用于处理插入语句中字段的前缀和后缀
  • 通过prefix属性和suffix属性,我们可以指定在包裹元素内容之前和之后要添加的字符串
  • suffixOverrides属性指定了要从包裹元素内容中移除的后缀字符串,以处理逗号的问题
<insert id="insertUser" parameterType="User">
  INSERT INTO users
  <trim prefix="(" suffix=")" suffixOverrides=",">
    username,
    <if test="email != null">
      email,
    </if>
    <if test="age != null">
      age,
    </if>
  </trim>
  VALUES
  <trim prefix="(" suffix=")" suffixOverrides=",">
    #{username},
    <if test="email != null">
      #{email},
    </if>
    <if test="age != null">
      #{age},
    </if>
  </trim>
</insert>

如果用户提供了邮箱和年龄信息,生成的插入语句将是

INSERT INTO users (username, email, age) VALUES (#{username}, #{email}, #{age})

如果用户只提供了用户名和邮箱信息,生成的插入语句将是:

INSERT INTO users (username, email) VALUES (#{username}, #{email})

(3)where标签

where标签:用于构建SQL语句中的WHERE子句。与<trim>标签类似,<where>标签可以处理SQL语句中的前缀和后缀,同时在内容为空的情况下能够自动省略WHERE关键字。通过<where>标签,可以更方便地构建包含动态条件的WHERE子句,而无需手动处理冗余的AND关键字。这有助于提高动态SQL语句的可读性和维护性

  • <where>标签通常配合<if>标签使用
  • <where>标签会删除最前的and关键这,而不会删除最后面的

如下:假设我们有一个查询用户的功能,用户可以根据不同的条件查询,比如用户名、邮箱和年龄

  • <where>标签用于包裹<if>标签,它会自动处理WHERE关键字和多余的AND
<select id="getUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  <where>
    <if test="username != null">
      AND username = #{username}
    </if>
    <if test="email != null">
      AND email = #{email}
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
  </where>
</select>

下面是wheretrim处理同一段功能代码的区别,使用哪个完全取决于个人偏好和需求

# <where标签>
<select id="getUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  <where>
    <if test="username != null">
      AND username = #{username}
    </if>
    <if test="email != null">
      AND email = #{email}
    </if>
  </where>
</select>
# <trim标签>

<select id="getUsers" parameterType="map" resultType="User">
  SELECT * FROM users
  <trim prefix="WHERE" prefixOverrides="AND">
    <if test="username != null">
      AND username = #{username}
    </if>
    <if test="email != null">
      AND email = #{email}
    </if>
  </trim>
</select>

(4)set标签

set标签:当你需要根据不同条件来更新不同字段时,可以使用set标签。使用它,你可以指定一系列可能得更新,并且只有在相应条件满足时,这些更新才会被包含在最终的SQL语句中

  • set标签通常要配合if标签一起使用
  • set标签会自动去除最后一个英文

如下:你需要根据用户提供的数据来更新一个用户的记录。用户可能只更新了他们的邮箱地址,或者他们的地址和电话号码,或者所有这些信息。使用动态 SQL 的 SET 标签,你可以构建一个 SQL 语句,它只会更新用户实际提供了新数据的字段

<update id="updateUser">
  UPDATE user
  <set>
    <if test="email != null">email = #{email},</if>
    <if test="address != null">address = #{address},</if>
    <if test="phone != null">phone = #{phone}</if>
  </set>
  WHERE id = #{id}
</update>

当然你可以使用trim标签代替

<update id="updateUser">
  UPDATE user
  <trim prefix="SET" suffixOverrides=",">
    <if test="email != null">email = #{email},</if>
    <if test="address != null">address = #{address},</if>
    <if test="phone != null">phone = #{phone},</if>
  </trim>
  WHERE id = #{id}
</update>

(5)foreach标签

foreach标签:该标签允许你遍历一个集合,并对每个元素执行一段 SQL 代码片段。它在处理诸如批量插入、更新或查询时非常有用

  • collection: 指定要遍历的集合。这个属性是必需的。在 MyBatis 中,根据传入的对象类型(例如 List、Map 或数组),这个属性的值可能是 listmap 或其他
  • item: 为集合中的每个元素指定一个别名。在 <foreach> 体内,可以使用这个别名来引用当前元素
  • index: 在遍历 Map 或 List 时,这个变量代表键或索引
  • separator: 指定在每次迭代后添加的分隔符,常用于 SQL 语句中元素之间的分隔,如逗号
  • openclose: 指定 <foreach> 生成的 SQL 片段的开始和结束部分,常用于添加括号

例如:假设你有一个产品 ID 列表,并且你想构建一个查询以获取所有这些产品的详细信息。你可以使用 <foreach> 标签来动态生成查询:

<select id="selectProducts" resultType="Product">
  SELECT * FROM product
  WHERE id IN
  <foreach item="item" collection="list" open="(" separator="," close=")">
    #{item}
  </foreach>
</select>

在这个例子中,如果你的产品 ID 列表是 [1, 2, 3],那么 MyBatis 将生成以下 SQL 语句:

SELECT * FROM product WHERE id IN (1, 2, 3)
0

评论区