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

yml 文件配置如下。

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

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。

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

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

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

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

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

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

添加测试用例:

@Test
void queryById() {
 UserInfo userInfo = userInfoMapper.queryById(4);
 System.out.println(userInfo);
}

运行结果:

image-20241007150941216

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

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

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

//插入数据
@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 语句:

insert into userinfo (username, `password`, age, gender, phone) values 
("zhaoliu","zhaoliu",19,1,"18700001234")

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

Mapper 接口的方法:

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

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

测试代码:

 @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);
    }

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

image-20241007153531754

返回主键:

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

@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 语法支持的。

delete from userinfo where id = ?

Mapper 接口的方法:

@Delete("delete from userinfo where id = #{id}")
void delete(Integer id);

1.1.4 改(@Update):

SQL 语句:

update userinfo set username="zhaoliu" where id=5

Mapper 接口的方法:

@Update("update userinfo set username=#{username} where id=#{id}")
void update(UserInfo userInfo);

1.1.5 查(@Select):

SQL 语句:

@Select("select id, username, `password`, age, gender, phone, delete_flag, 
create_time, update_time from userinfo")

Mapper 接口的方法:

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

查询结果:

image-20241007160052250

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

原因分析:

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

观察下图:

image-20241007160512525

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

解决方法:

  1. 起别名

  2. 结果映射

  3. 开启驼峰命名

1.1.5.1 起别名:

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

@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 结果映射:
@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 定义一个名称。

@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 文件)。

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 文件。

image-20241007165055159

创建完成后,向里面填入 MyBatis 的固定 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:返回的数据类型。
image-20241007170155891

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

1.2.2 增:

UserInfoMapper 接口:

Integer deleteUser(Integer id);

UserInfoMapper.xml 实现:

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

测试代码:

@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);
}

效果如下:

image-20241008094127079

  • 返回自增 id:

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

<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 接口:

Integer deleteUser(Integer id);

UserInfoMapper.xml 实现:

<delete id="deleteUser">
    delete from userinfo where id = #{id}
</delete>

1.2.4 改:

UserInfoMapper 接口:

Integer updateUser(UserInfo userInfo);

UserInfoMapper.xml 实现:

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

1.2.5 查:

UserInfoMapper 接口:

List<UserInfo> queryAllUser();

UserInfoMapper.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>

运行结果:

image-20241008095014817

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

解决办法和注解类似:

  1. 起别名

  2. 结果映射

  3. 开启驼峰命名

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

Mapper.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>
image-20241008100239710

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

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

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

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

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

SQL 语句的执行流程:

  1. SQL 语法校验和解析

  2. SQL 优化

  3. SQL 执行

  4. #{} 性能更高:

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

image-20241008101914520

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

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

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

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

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

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

Mapper 实现:

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

部分结果如下:

image-20241008103313736

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

image-20241008103424177

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

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

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

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

三、动态 SQL:

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

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

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

3.1 <if> 标签:

接口定义:

Integer insertUserByCondition(UserInfo userInfo);

Mapper.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>

测试代码如下:

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

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

image-20241008105642300

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

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

3.2 <trim>标签:

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

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

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

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

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

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

调整 Mapper.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。

<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:

<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:每次遍历之间间隔的字符串。

使用演示:

接口方法:

void deleteByIds(List<Integer> ids);

Mapper.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,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码。

image-20241008121124855

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

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

例如 SQL片段:

<sql id="allColumn">
 id, username, age, gender, phone, delete_flag, create_time, update_time
</sql>

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

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

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

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐