MyBatis 用法详解

文章目录

  • [一、普通 SQL](#一、普通 SQL)
    • [1.1 注解实现:](#1.1 注解实现:)
      • [1.1.1 参数传递:](#1.1.1 参数传递:)
      • [1.1.2 增(@Insert):](#1.1.2 增(@Insert):)
      • [1.1.3 删(@Delete):](#1.1.3 删(@Delete):)
      • [1.1.4 改(@Update):](#1.1.4 改(@Update):)
      • [1.1.5 查(@Select):](#1.1.5 查(@Select):)
        • [1.1.5.1 起别名:](#1.1.5.1 起别名:)
        • [1.1.5.2 结果映射:](#1.1.5.2 结果映射:)
        • [1.1.5.3 开启驼峰命名(推荐):](#1.1.5.3 开启驼峰命名(推荐):)
    • [1.2 MyBatis XML 配置文件实现:](#1.2 MyBatis XML 配置文件实现:)
      • [1.2.1 添加 Mapper.xml](#1.2.1 添加 Mapper.xml)
      • [1.2.2 增:](#1.2.2 增:)
      • [1.2.3 删:](#1.2.3 删:)
      • [1.2.4 改:](#1.2.4 改:)
      • [1.2.5 查:](#1.2.5 查:)
  • [二、#{} 和 {} 的区别:](#{} 和 {} 的区别:)
  • [三、动态 SQL:](#三、动态 SQL:)
    • [3.1 `<if> `标签:](#3.1 <if> 标签:)
    • [3.2 `<trim>`标签:](#3.2 <trim>标签:)
    • [3.3 `<where>`标签:](#3.3 <where>标签:)
    • [3.4 `<set>`标签:](#3.4 <set>标签:)
    • [3.5 `<foreach>`标签:](#3.5 <foreach>标签:)
    • [3.6 `<include>`标签:](#3.6 <include>标签:)

使用 MyBatis 需要导入依赖(MyBatis 和 MySQL(数据库都行)),和配置 yml 文件。导入对应的依赖比较简单,所以下面只给出 yml 文件的配置。

yml 文件配置如下。

下面用中文填写(除了注释)的都是要根据自己的情况来填写的。

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/对应数据库名称?characterEncoding=utf8&useSSL=false
    username: root
    password: 对应数据库的密码(如果是纯数字记得加上单引号)
    driver-class-name: com.mysql.cj.jdbc.Driver
  mvc:
    favicon:
      enable: false
  profiles:  #多平台配置
    active: dev
# 设置 Mybatis 的 xml 保存路径
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true  #自动驼峰转换

MyBatis 有两种实现方式。(1)注解(2)xml 文件实现。

一、普通 SQL

1.1 注解实现:

注解实现,其实就是在对应的注解里面写上 SQL 语句即可。

1.1.1 参数传递:

需求:查找 id=4 的用户,对应的 SQL 就是:select * from userinfo where id=4。

java 复制代码
@Select("select username, `password`, age, gender, phone from userinfo where id = 4")
UserInfo queryById();

但是这样的话,只能查找 id=4 的数据,所以 SQL 语句中的 id 值不能写成固定数值,需要变为动态的数值。

解决方法:在 queryById 方法中添加一个参数(id),将方法中的参数,传给 SQL 语句。

使用#{}的方式获取方法中的参数。 注意:#{}里面的名称需要和方法参数名称一致(如果方法参数是一个对象,那么#{}里面的值,要和对象的属性名称一样。)

java 复制代码
@Select("select username, `password`, age, gender, phone from userinfo where id= #{id} ")
UserInfo queryById(Integer id);

如果 mapper 接口方法形参只有一个普通类型的参数,#{...}里面的属性名可以随便写,如:#{id}、# {value}。建议和参数名保持一致。

添加测试用例:

java 复制代码
@Test
void queryById() {
 UserInfo userInfo = userInfoMapper.queryById(4);
 System.out.println(userInfo);
}

运行结果:

也可以通过 @Param ,设置参数的别名,如果使用 @Param 设置别名,#{...}里面的属性名必须和 @Param 设置的一样。

java 复制代码
@Select("select username, `password`, age, gender, phone from userinfo where id = #{userid} ")
UserInfo queryById(@Param("userid") Integer id);

如果参数是对象,设置了 @Param 属性,#{...} 需要使用 别名.属性 来获取。

java 复制代码
//插入数据
@Insert("insert into userinfo(username,`password`,age,gender,phone) " +
        "values(#{gobeyye.username},#{gobeyye.password}" +
        ",#{gobeyye.age},#{gobeyye.gender},#{gobeyye.phone})")
Integer insertUserInfo1(@Param("gobeyye") UserInfo userInfo);

1.1.2 增(@Insert):

SQL 语句:

sql 复制代码
insert into userinfo (username, `password`, age, gender, phone) values 
("zhaoliu","zhaoliu",19,1,"18700001234")

把 SQL 中的常量替换为动态的参数:

Mapper 接口的方法:

java 复制代码
@Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);

直接使用 UserInfo 对象的属性名来获取参数。

测试代码:

java 复制代码
 @Test
    void insertUesrInfo1() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("zhaoliu");
        userInfo.setPassword("zhaoliu");
        userInfo.setAge(19);
        userInfo.setGender(1);
        userInfo.setPhone("18700001234");
        userInfoMapper.insertUesrInfo1(userInfo);
    }

运行结果:下面这个运行结果,需要导入日志配置。

返回主键:

如果想要拿到自增 id(主键一般默认是 id),需要在 Mapper 接口的方法上添加一个 Options 的注解。

java 复制代码
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username, age, gender, phone) values (#{userinfo.username},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")
Integer insert(@Param("userinfo") UserInfo userInfo);
  • useGeneratedKeys:这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。

  • keyProperty:指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素,设置它的值,默认值:未设置(unset)。

注意:设置 useGeneratedKeys = true 之后,方法返回值依然是受影响的行数,自增 id 会设置在上述 keyProperty 指定的属性中。

1.1.3 删(@Delete):

SQL 语句:

问号是占位符的意思,方便下面写代码,不是 SQL 语法支持的。

sql 复制代码
delete from userinfo where id = ?

Mapper 接口的方法:

java 复制代码
@Delete("delete from userinfo where id = #{id}")
void delete(Integer id);

1.1.4 改(@Update):

SQL 语句:

sql 复制代码
update userinfo set username="zhaoliu" where id=5

Mapper 接口的方法:

java 复制代码
@Update("update userinfo set username=#{username} where id=#{id}")
void update(UserInfo userInfo);

1.1.5 查(@Select):

SQL 语句:

sql 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, 
create_time, update_time from userinfo")

Mapper 接口的方法:

java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
List<UserInfo> queryAllUser();

查询结果:

从运行结果上可以看到,我们 SQL 语句中,查询了 delete_flag,create_time,update_time 但是这几个属性却没有赋值。

原因分析:

当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性。

观察下图:

我们可以发现,这是因为字段名和属性名称不一致导致的(命名规则不同导致的)。

解决方法:

  1. 起别名

  2. 结果映射

  3. 开启驼峰命名

1.1.5.1 起别名:

在 SQL 语句中,给列名起别名,保持别名和实体类属性名一样。

java 复制代码
@Select("select id, username, `password`, age, gender, phone," +
        " delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from userinfo")
public List<UserInfo> queryAllUser();
1.1.5.2 结果映射:
java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
@Results({
        @Result(column = "delete_flag", property = "deleteFlag"),
        @Result(column = "create_time", property = "createTime"),
        @Result(column = "update_time", property = "updateTime")
})
List<UserInfo> queryAllUser2();

如果其他 SQL,也希望可以复用这个映射关系,可以给这个 Results 定义一个名称。

java 复制代码
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
@Results(id="resultMap",value = {
    @Result(column = "delete_flag", property = "deleteFlag"),
    @Result(column = "create_time", property = "createTime"),
    @Result(column = "update_time", property = "updateTime")
})
List<UserInfo> queryAllUser2();

@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time " +
        "from userinfo where id= #{userid} ")
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);

使用 id 属性给该 Results 定义别名,使用 @ResultMap 注解来复用 Results。

1.1.5.3 开启驼峰命名(推荐):

通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而 Java 属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true(设置 application.properties 或者 application.yml 文件)。

yaml 复制代码
mybatis:
  configuration:
#    日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#    自动驼峰转换
    map-underscore-to-camel-case: true

这时 Java 代码不做任何处理。字段就能全部正确赋值。

1.2 MyBatis XML 配置文件实现:

1.2.1 添加 Mapper.xml

首先根据 yml 文件中配置的 mapper 路径创建对应的 xml 文件。

创建完成后,向里面填入 MyBatis 的固定 xml 格式:

查询所有用户的具体实现:

xml 复制代码
<?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.UserInfoXMlMapper">
 <select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
 select username,`password`, age, gender, phone from userinfo
 </select>
</mapper>

以下是对以上标签的说明:

  • <mapper>标签:需要指定 namespace 属性,标识命名空间,值为 mapper 接口的全限定名,包括全包名.类名。
  • <select>查询标签:是用来执行数据库的查询操作的。select 中的属性解释如下:
  1. id :是和 Interface(接口)中定义的方法名称一样的,表示对接口的具体实现方法。
  2. resultType:返回的数据类型。

其实 xml 增删改查和注解实现增删改查,其实差不多,无非就是一个使用注解,另一个使用标签。

1.2.2 增:

UserInfoMapper 接口:

java 复制代码
Integer deleteUser(Integer id);

UserInfoMapper.xml 实现:

xml 复制代码
<insert id="insertUser">
 insert into userinfo (username, `password`, age, gender, phone) values (#{username}, #{password}, #{age},#{gender},#{phone})
</insert>

测试代码:

java 复制代码
@Test
void insertUser() {
    UserInfo userInfo = new UserInfo();
    userInfo.setUsername("gobeyye");
    userInfo.setPassword("123456");
    userInfo.setAge(18);
    userInfo.setGender(1);
    userInfo.setPhone("12345678");
    userInfoXMLMapper.insertUser(userInfo);
}

效果如下:

  • 返回自增 id:

接口定义不变,Mapper.xml 实现设置 useGeneratedKeys 和 keyProperty 属性。代码如下:

xml 复制代码
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into userinfo (username, `password`, age, gender, phone)
    values (#{username}, #{password}, #{age},#{gender},#{phone})
</insert>

1.2.3 删:

UserInfoMapper 接口:

java 复制代码
Integer deleteUser(Integer id);

UserInfoMapper.xml 实现:

xml 复制代码
<delete id="deleteUser">
    delete from userinfo where id = #{id}
</delete>

1.2.4 改:

UserInfoMapper 接口:

java 复制代码
Integer updateUser(UserInfo userInfo);

UserInfoMapper.xml 实现:

xml 复制代码
<update id="updateUser">
 update userinfo set username=#{username} where id=#{id}
</update>

1.2.5 查:

UserInfoMapper 接口:

java 复制代码
List<UserInfo> queryAllUser();

UserInfoMapper.xml 实现:

xml 复制代码
<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
 select id, username,`password`, age, gender, phone, delete_flag, create_time, update_time from userinfo
</select>

运行结果:

结果显示:deleteFlag,createTime,updateTime 也没有进行赋值。

解决办法和注解类似:

  1. 起别名

  2. 结果映射

  3. 开启驼峰命名

其中 1 和 3 方法和注解一样,下面重点讲如何使用 xml 写结果映射。

Mapper.xml:

xml 复制代码
<resultMap id="BaseMap" type="com.gobeyye.mybatis.moder.UserInfo">
    <id column="id" property="id"></id>
    <result column="delete_flag" property="deleteFlag"></result>
    <result column="create_time" property="createTime"></result>
    <result column="update_time" property="updateTime"></result>
</resultMap>

<select id="queryAllUser" resultMap="BaseMap">
    select id, username,`password`, age, gender, phone, delete_flag, create_time, update_time from userinfo
</select>

多表查询和单表查询类似,只是 SQL 不同而已,不再多说。

二、#{} 和 ${} 的区别:

MyBatis 参数赋值有两种方式,文章前面使用了 #{} 进行赋值,接下来我们看下二者的区别。

#{} 和 ${} 的区别就是预编译SQL即时SQL的区别。

了解 SQL 语句的执行流程,有助于理解下面的区别。

SQL 语句的执行流程:

  1. SQL 语法校验和解析

  2. SQL 优化

  3. SQL 执行

  4. #{} 性能更高:

绝大多数情况下,某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要经过上面的语法解析,SQL 优化、SQL 编译等,则效率就明显不行了。

预编译 SQL,编译一次之后会将编译后的 SQL 语句缓存起来,后面再次执行这条语句时,不会再次编译 (只是输入的参数不同),省去了解析优化等过程,以此来提高效率。

  1. #{} 更安全(防止 SQL 注入,最重要的一点):

SQL 注入:是通过操作输入的数据来修改事先定义好的 SQL 语句,以达到执行代码对服务器进行攻击的方法。

#{} 内部的值,只会被识别成参数,而 ${} 内部的值,既可以识别成参数也可以被识别成 SQL 语句。

  1. 特殊场景只能使用 ${}:

在排序功能中,我们可以选择升序或者降序。

Mapper 实现:

java 复制代码
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +
        "from userinfo order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);

部分结果如下:

如果将 ${} 换成 #{},就会出现如下结果,并报错:

这是因为 #{} 会根据参数类型判断,是否拼接引号''。如果参数类型为 String,就会加上引号。

当使用 #{sort} 查询时,asc 前后自动给加了引号,导致 sql 错误。

除此之外,还有表名作为参数时,也只能使用 ${},但是一定要注意 SQL 注入问题。

综上,能使用 #{} 的场景就使用 #{},实在不行再使用 ${},但是一定要注意 SQL 注入问题。

三、动态 SQL:

动态 SQL 是 Mybatis 的强大特性之一,能够完成不同条件下不同的 sql 拼接。

可以参考官方文档:https://mybatis.net.cn/dynamic-sql.html

由于动态 SQL 会复杂一点,所以一般将动态 SQL 写在 xml 文件中(使用注解也行)。下面就都使用 xml 文件的形式,注解就不再演示。

3.1 <if> 标签:

接口定义:

java 复制代码
Integer insertUserByCondition(UserInfo userInfo);

Mapper.xml 实现:

xml 复制代码
<insert id="insertUserByCondition">
    INSERT INTO userinfo (
    username, `password`, age,
    <if test="gender != null">
        gender,
    </if>
    phone)
    VALUES (#{username},#{password}, #{age},
    <if test="gender != null">
        #{gender},
    </if>
    #{phone})
</insert>

测试代码如下:

java 复制代码
@Test
void insertUserByCondition() {
    UserInfo userInfo = new UserInfo();
    userInfo.setUsername("gobeyye");
    userInfo.setPassword("123123");
    userInfo.setAge(18);
    userInfo.setPhone("999999");
    userInfoXMLMapper.insertUserByCondition(userInfo);
}

效果如下:可以看到 gender 由于属性没有值,所以该字段被去除了。

这里有的友友可能会有疑问:把全部字段都加上,不进行赋值的字段,参数传为 null 不就行了?

答:不行,因为 MySQL 中,有的字段如果没有赋值,会有默认值(可以自己设定),如果传值为 null,就会把默认值覆盖掉,这是我们不希望看到的,所以只能使用 if 标签。

3.2 <trim>标签:

之前的插入用户功能,只是有一个 gender 字段可能是选填项,如果有多个字段,一般考虑使用标签结合标签,对多个字段都采取动态生成的方式。

<trim>标签中有如下属性:

  • prefix:表示整个语句块,以 prefix 的值作为前缀。

  • suffix:表示整个语句块,以 suffix 的值作为后缀。

  • prefixOverrides:表示整个语句块要去除掉的前缀。

  • suffixOverrides:表示整个语句块要去除掉的后缀。

调整 Mapper.xml 的插入语句为:

xml 复制代码
<insert id="insertUserByCondition">
    insert into userinfo
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username !=null">
            username,
        </if>
        <if test="password !=null">
            `password`,
        </if>
        <if test="age != null">
            age,
        </if>
        <if test="gender != null">
            gender,
        </if>
        <if test="phone != null">
            phone,
        </if>
    </trim>
    values
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username !=null">
            #{username},
        </if>
        <if test="password !=null">
            #{password},
        </if>
        <if test="age != null">
            #{age},
        </if>
        <if test="gender != null">
            #{gender},
        </if>
        <if test="phone != null">
            #{phone}
        </if>
    </trim>
</insert>

在上面的这段代码中,就可以使用 <trim>标签起到解决,多余后缀,的问题。

3.3 <where>标签:

where 标签解决的是,如果所有条件都加上了 if 标签,且都没有进行传值,这时 where 就会多出来,这时 SQL 语法就出错了,使用 where 标签就可以很好的解决上面的问题。

<where>只会在子元素有内容的情况下才插入 where 字句,而且会自动去除字句开头的 AND 或 OR。

xml 复制代码
<select id="queryByCondition" resultType="com.example.demo.model.UserInfo">
    select id, username, age, gender, phone, delete_flag, create_time,
    update_time
    from userinfo
    <where>
        <if test="age != null">
            and age = #{age}
        </if>
        <if test="gender != null">
            and gender = #{gender}
        </if>
        <if test="deleteFlag != null">
            and delete_flag = #{deleteFlag}
        </if>
    </where>
</select>

3.4 <set>标签:

<set> :动态的在 SQL 语句中插入 set 关键字,并会删掉额外的逗号。(用于 update 语句中)。

Mapper.xml:

xml 复制代码
<update id="updateUserByCondition">
    update userinfo
    <set>
        <if test="username != null">
            username = #{username},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
        <if test="deleteFlag != null">
            delete_flag = #{deleteFlag},
        </if>
    </set>
    where id = #{id}
</update>

3.5 <foreach>标签:

对集合进行遍历时可以使用该标签。标签有如下属性:

  • collection:绑定方法参数中的集合,如 List,Set,Map 或数组对象。

  • item:遍历时的每一个对象。

  • open:语句块开头的字符串。

  • close:语句块结束的字符串。

  • separator:每次遍历之间间隔的字符串。

使用演示:

接口方法:

java 复制代码
void deleteByIds(List<Integer> ids);

Mapper.xml:

xml 复制代码
<delete id="deleteByIds">
    delete from userinfo
    where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

3.6 <include>标签:

在 xml 映射文件中配置的 SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码。

我们可以对重复的代码片段进行抽取,将其通过标签封装到一个 SQL 片段,然后再通过标签进行引用。

  • <sql>:定义可重用的 SQL 片段。
  • <include>:通过属性 refid,指定包含的 SQL 片段。

例如 SQL片段:

xml 复制代码
<sql id="allColumn">
 id, username, age, gender, phone, delete_flag, create_time, update_time
</sql>

通过 include 标签进行引用。操作如下:

xml 复制代码
<select id="queryById" resultType="com.example.demo.model.UserInfo">
    select
    <include refid="allColumn"></include>
    from userinfo where id= #{id}
</select>

结语:
其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话,还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

相关推荐
漫步向前44 分钟前
28.mysql读写分离
mysql
CHQIUU1 小时前
告别手动映射:在 Spring Boot 3 中优雅集成 MapStruct
spring boot·后端·状态模式
破 风2 小时前
Docker启动mysql容器时找不到 mysqlx.sock 和 mysqld.sock
mysql·docker·容器
XiaoLeisj2 小时前
【设计模式】深入解析代理模式(委托模式):代理模式思想、静态模式和动态模式定义与区别、静态代理模式代码实现
java·spring boot·后端·spring·设计模式·代理模式·委托模式
李少兄2 小时前
解决Spring Boot版本冲突导致的`NoSuchFieldError`
java·spring boot·后端
Live000003 小时前
Next.js 结合 MySQL 数据库全站开发教程
前端·mysql·next.js
书唐瑞3 小时前
使用 binlog2sql 闪回 MySQL8 数据
mysql·python3·mysql8·binlog2sql·闪回
pwzs3 小时前
常见的 Spring Boot 注解汇总
java·spring boot·后端·spring
他҈姓҈林҈3 小时前
创建可执行 JAR 文件
spring boot
神仙别闹3 小时前
基于Java(JSP)+MySQL实现深度学习的音乐推荐系统
java·深度学习·mysql