【Java EE】Mybatis

发布于:2025-07-11 ⋅ 阅读:(15) ⋅ 点赞:(0)

1. 初识 Mybatis

        简单来说,Mybatis 是一个持久层框架,用于操作数据库,使开发者更简单地完成程序和数据库之间的交互。

        开始使用 Mybatis:

        1. 创建 SpringBoot 项目,导入 Mybatis 的依赖和 Mybatis 支持的数据库的依赖。

        2. 配置数据库连接:

spring:
  application:
    name: 项目名
  # 数据库连接配置,以 Mysql 为例
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/数据库名称?characterEncoding=utf8&useSSL=false
    username: 用户名
    password: 密码
    driver-class-name: com.mysql.cj.jdbc.Driver

        3. 创建实体类用于接收某一数据表的数据,属性与表头字段一一对应

@Getter
@Setter
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

        4.  开始写 Dao/Mapper 层接口。建议类名使用 Mapper 结尾,方法名使用 select、insert、delete 或 update 开头,每个接口只用于操作一个数据表中的数据(尽量避免 “慢SQL”)。为接口添加 Mybatis 提供的 Mapper 注解,调用方法时,框架会自动生成对应实体类的对象,该接口也会交给 SpringIoC 管理。

@Mapper
public interface UserInfoMapper {

    List<UserInfo> selectAll();

    Integer insertUser(UserInfo userInfo);

    Integer deleteUser(Integer id);

    Integer updateUser(UserInfo userInfo);
}

        5. 写具体 sql 语句作为接口实现,有两种方式,注解和 XML。

        注解方式:

@Mapper
public interface UserInfoMapper {

    // 查
    @Select("select * from user_info")
    List<UserInfo> selectAll();

    // 使用自增主键
    @Options(useGeneratedKeys = true, keyProperty = "id")
    // 增
    @Insert("insert into user_info (username, password, age) " +
            "values (#{username}, #{password}, #{age})")
    Integer insertUser(UserInfo userInfo);

    // 删
    @Delete("delete from user_info where id = #{id}")
    Integer deleteUser(Integer id);

    // 改
    @Update("update user_info set username = #{username}, password = #{password} " +
            "where id = #{id}")
    Integer updateUser(UserInfo userInfo);
}

        XML 方式:

        首先配置 XML 文件的路径,** 表示通配符,此时 Mybatis 会检索到该目录下所有以 Mapper 结尾的文件。

mybatis:
  mapper-locations: classpath:dao/**Mapper.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="对应接口的全限定名称">

    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into user_info (username, password, age)
        values (#{username}, #{password}, #{age})
    </insert>

    <update id="updateUser">
        update user_info
        set username = #{username}, password = #{password}
        where id = #{id}
    </update>

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

    <select id="selectAll" resultType="com.boilermaker.mybatislearning.model.UserInfo">
        select *
        from user_info
    </select>

</mapper>

2. 使用细节

        #{ } 中填入的是 Java 实体类中的属性,sql 中的其他部分均为 sql 关键字或表头字段。

        传入参数时,Mybatis 会将参数(或参数中的属性)赋值到 #{ } 中。

        返回结果时,Mybatis 会建立表头字段与接收属性的映射关系。映射关系为忽略大小写的相同名称,如果属性与表头字段不一致则无法直接映射。Java 规范中属性需使用小驼峰,而数据库规范中使用蛇形,此时需要在配置项中设置驼峰自动转换。

mybatis:
  configuration:
    map-underscore-to-camel-case: true # 配置驼峰⾃动转换

        配置打印 Mybatis 日志:

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置打印 MyBatis⽇志

        #{ } 和 ${ } 的区别:

        1. #{ } 是 “预编译SQL”,编译一次后会将该 sql 的结构缓存。参数位置使用 ?占位,因为每次调用该方法时只是传入的参数不同,sql 结构都是一致的。Mybatis 会根据参数类型自动添加引号。${ } 是 “即时SQL”,只是字符串的拼接。

        2. 从性能上来说,预编译 SQL 减少了 SQL 语法解析、语法优化、编译的一系列过程,性能更高。

        3. 从安全性来说,${ } 有被 SQL 注入攻击的风险,即在参数中添加某些 SQL 关键字来对数据库进行恶意攻击。

        4. 能使用 #{ } 尽量使用 #{ },但也有一些情况下必须使用 ${ }。有时需要将表名、关键字作为参数传入,而这些参数不能加引号,这时就必须使用 ${ }。使用 ${ } 就要特别注意 SQL 注入的问题,必须对参数进行合法性校验,或者只提供选项接口,不主动获取输入。比如对于排序功能,sql 中写为 order by id ${ },此时 ${ } 中只能传入 asc 或 desc,开放给用户的接口就只需设置升序和倒序两个选项。

3. 数据库连接池

        对于客户端服务器结构的数据库来说,客户端(命令行工具或图形界面)必须通过网络协议(如 TCP/IP)与服务器进程通信。传统的数据库操作每次请求都需要经历 TCP 三次握手、数据库权限验证、连接初始化等步骤,频繁的创建和销毁连接会消耗大量资源。

        数据库连接池使得程序可以重复使用同一个现有的数据库连接。它会预先创建一定数量的数据库连接并缓存到池中,应用需要时直接从池中获取空闲连接,用完后归还而非关闭。

        常见的数据库连接池有 Hikari、Druid 等,SpringBoot 默认使用 Hikari。

4. 进阶操作

4.1 like 查询

        like 查询也属于只能使用 $ 的情况,因为 key 的两侧不能加引号。

List<UserInfo> queryAllUserByLike(String key);
    <select id="queryAllUserByLike" resultType="com.boilermaker.mybatislearning.model.UserInfo">
         select *
         from user_info
         where username like '%${key}%'
    </select>

        但这样写依然有 SQL 注入的风险。key 是查询关键字,应当可以是任意值,这里不方便进行参数校验,所以改用 Mysql 自带的内置函数 concat 来写。

    <select id="queryAllUserByLike" resultType="com.boilermaker.mybatislearning.model.UserInfo">
         select *
         from user_info
         where username like concat('%', #{key}, '%')
    </select>

        like 查询并不常用,因为会导致索引失效。

4.2 动态 SQL

4.2.1 if 和 trim 标签

        if 标签用于区分必填字段和非必填字段。对于下面的 insert 操作,if 标签下的字段只有满足 if 条件才会拼接,若不满足条件则会使用默认值,test 后的字段是 Java 对象的属性。

    <insert id="insertUserByCondition">
        insert into user_info (
        username, 
        password,
        <if test="age != null">
            age,
        </if>
        <if test="gender != null">
            gender,
        </if>
        <if test="phone != null">
            phone,
        </if>
        ) 
        values ( 
        #{username}, 
        #{password},
        <if test="age != null">
            #{age},
        </if>
        <if test="gender != null">
            #{gender},
        </if>
        <if test="phone != null">
            #{phone},
        </if>
        )
    </insert>

        如果三个 if 条件都没有满足,SQL 会被拼接成这样:

insert into user_info (
username,
password,
)
values ......

        password 后多了一个逗号,这会使 SQL 执行失败。此时需要 trim 标签辅助。

        trim 标签后有四个参数可选,分别为:

                prefix:在 trim 语句块前加上这个前缀。

                suffix:在 trim 语句块后加上这个后缀。

                prefixOverrides:在 trim 语句块前去除这个前缀。

                suffixOverrides:在 trim 语句块后去除这个后缀。

        使用 trim 后整合了括号,看起来更加简洁,且不会有末尾逗号的影响:

    <insert id="insertUserByCondition">
        insert into user_info
        <trim prefix="(" suffix=")" suffixOverrides=",">
            username, 
            password,
            <if test="age != null">age,</if>
            <if test="gender != null">gender,</if>
            <if test="phone != null">phone,</if>
        </trim>
        values  
        <trim prefix="(" suffix=")" suffixOverrides=",">
            #{username}, 
            #{password},
            <if test="age != null">#{age},</if>
            <if test="gender != null">#{gender},</if>
            <if test="phone != null">#{phone},</if>
        </trim>
    </insert>

4.2.2 where 标签

        where 标签帮我们方便地动态组装 where 条件,它会自动去除开头的 and 或 or。

    <select id="selectUserByCondition" resultType="com.boilermaker.mybatislearning.model.UserInfo">
        select * from user_info
        <where>
            <if test="id != null">and id = #{id}</if>
            <if test="username != null">and username = #{username}</if>           
            <if test="age != null">and age = #{age}</if>
            <if test="gender != null">and gender = #{gender}</if>
            <if test="phone != null">and phone = #{phone}</if>            
        </where>
    </select>

4.2.3 set 标签

        传入一个 Java 对象实例去更新数据库原有字段,使用 set 标签指定动态内容。

    <update id="updateUserByCondition">
        update user_info
        <set>
            <if test="username != null">username = #{username},</if>           
            <if test="age != null">age = #{age},</if>
            <if test="gender != null">gender = #{gender},</if>
            <if test="phone != null">phone = #{phone},</if>            
        </set>
        where id = #{id}
    </update>

4.2.4 foreach 标签

        foreach 标签用于批量操作。如下面批量删除数据,collection 用于绑定集合,item 是集合中的每一个对象,open 是 foreach 语句块的前缀,close 是 foreach 语句块的后缀,separator 是每次遍历的间隔。

    void deleteByIds(List<Integer> ids);
    <delete id="deleteByIds">
        delete from user_info where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

        批量增加数据: 

    void insertUsers(List<UserInfo> users);
    <insert id="insertUsers">
        insert into user_info (username, password, age) values
        <foreach collection="users" item="user" separator=",">
            (#{user.username}, #{user.password}, #{user.age})
        </foreach>
    </insert>

4.2.5 sql 和 include 标签

        使用 sql 标签封装一个 SQL 片段,再通过 include 标签进行复用。

    <sql id="allColumn">
        id, username, age, gender, phone, delete_flag, create_time, update_time
    </sql>
    <select id="selectAll" resultType="com.boilermaker.mybatislearning.model.UserInfo">
        select 
        <include refid="allColumn"></include>
        from user_info
    </select>

网站公告

今日签到

点亮在社区的每一天
去签到