N

Norlcayn的博客

A text-focused Halo theme

  • 首页
  • 文章
  • 默认分类
  • 关于
主页 Spring MyBatis
文章

Spring MyBatis

发表于 2026-01-15 更新于 昨天
作者 Administrator
54~69 分钟 阅读

MyBatis

  • MyBatis是一款优秀的持久层框架,用于简化JDBC的开发
  • 官网:MyBatis 3

快速入门

引入MyBatis依赖

可以在创建SpringBoot项目时,勾选相关依赖:

Snipaste_2025-08-07_21-56-52

注意:由于是与MySQL数据库进行操作,所以别忘了添加MySQL驱动依赖(其他数据库同理)

也可以在pom.xml中引入依赖:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

编写MyBatis配置

通常在项目的resource/application.properties文件中进行相关配置的编写,主要是关于数据库的连接信息

配置信息如下:

# 配置数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/web01
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=1234

根据自己的数据库配置进行适当修改

如果需要在控制台中输出SQL语句的日志信息,可以添加以下配置:

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

创建Mapper接口

关键字:@Mapper

一般有了MyBatis,只需要创建Mapper接口即可,不需要对接口编写实现类(底层会自动生成实现类对象)

package com.norlcyan.mapper;

import com.norlcyan.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper     
public interface UserMapper {

    @Select("SELECT * FROM user")
    List<User> findAll();
}

应用程序在运行时,会自动为该接口创建一个实现类对象(代理对象),并且会自动将该类的实现对象放到IOC容器中

增删改查

MyBatis的增删改查注解都对应了SQL语句中的关键字:@Insert、@Delete、@Update、@Select

以删除为例,当条件语句中的参数是动态的情况,可以利用#{数据}的形式作为占位符:

@Delete("DELETE FROM user WHERE id = #{id}")
void delUserById(Integer id);

其中,DML语句(增删改)可以设置为无返回值也可以设置带有返回值。如果带有返回值,那返回值类型需要为Integer,且该返回值的类型表示的是被影响的行数

形参也可以为实体类(比如User),但是占位符的名称需要和类中成员属性名称完全一致

如果遇到字段为自增的情况,可以将实体类中的成员属性设置为null。或者利用@Options注解,这种方式还可以获取到数据库中自增后的值

虽然冷门但是面试题:除了#{数据},还有${...}。但是${...}表示的是将括号内的内容直接拼接到SQL语句中,类似于拼接符,这种形式不安全存在SQL注入问题,且性能低,几乎不用这种形式(除非是需要动态表名或字段名)

@Param注解

该注解主要是用于给形参起名,让SQL语句中的占位符可以对应上形参(编译后只会保留形参类型,不会保留形参名):

@Select("SELECT * FROM user WHERE username=#{username} AND password=#{password}");
public User findByUsernameAndPassword(@Param("username") String username,@Param("password") String password);

当方法的形参类型相同,且有多个形参时,需要使用@Param注解为其形参起名

如果基于官方骨架创建的SpringBoot项目,接口编译时会保留方法形参名,此时@Param注解可以省略,但是形参名需要和占位符名对应

XML映射

  • 在MyBatis中,既可以通过注解配置SQL语句,也可以通过XML配置文件配置SQL语句
  • 默认规则: 1. XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名) 2. XML映射文件的namespace属性为Mapper接口全限定名一致 3. XML映射文件中SQL语句的id与Mapper接口中的方法一致,并保持返回类型一致

假设当前的Mapper接口如下:

package com.norlcyan.mapper;

import com.norlcyan.pojo.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM user")
    List<User> findAll();

    @Delete("DELETE FROM user WHERE id = #{id}")
    void delUserById(Integer id);

    @Select("SELECT  * FROM user WHERE username=#{username} AND password=#{password}")
    User findUserByNameAndPassword(String username, String password);
}

将其改造为使用XML映射的方式:

  1. 先去除注解
package com.norlcyan.mapper;

import com.norlcyan.pojo.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {

    List<User> findAll();

    void delUserById(Integer id);

    User findUserByNameAndPassword(String username, String password);
}

有XML映射文件就不要使用注解,有注解就不要使用XML映射文件

简单的SQL语句可以交给注解,复杂的SQL语句还是XML映射文件更好

  1. 创建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.norlcyan.mapper.UserMapper">

    <!-- 查询所有用户 -->
    <select id="findAll" resultType="com.norlcyan.pojo.User">
        SELECT * FROM user
    </select>

    <!-- 根据ID删除用户 -->
    <delete id="delUserById" parameterType="java.lang.Integer">
        DELETE FROM user WHERE id = #{id}
    </delete>

    <!-- 根据用户名和密码查询用户 -->
    <select id="findUserByNameAndPassword" resultType="com.norlcyan.pojo.User">
        SELECT * FROM user WHERE username=#{username} AND password=#{password}
    </select>

</mapper>

namespace表示映射的Mapper接口,参数填入该接口的全类名

resultType 只需要指定实体类的全限定名,而**不需要关心是返回单个对象还是集合**

parameterType 实际上是可以省略的,MyBatis 可以根据方法参数自动推断参数类型。不省略的好处在于可读性更好

  1. 最重要的一步,将XML文件存放在指定位置:

Snipaste_2025-08-08_10-54-45

辅助配置

配置XML映射文件的位置:

mybatis.mapper-locations=classpath:mapper/*.xml

java目录和resource目录编译后都会存放在类路径下(classpath)

所以配置中表达的意思是在resource/mapper目录下,查找所有的xml文件

推荐IDEA MyBatis插件:MyBatisX

JDBC与MyBatis

Snipaste_2025-08-07_23-17-25

数据库连接池

  • 数据库连接池是个**容器**,负责分配、管理数据库连接(Connection)
  • 它运行应用程序**重复使用**一个现有的数据库连接,而不是再重新建立一个
  • 释放空间时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
  • 优势:

  • 资源复用

  • 提升系统响应速度
  • 避免数据库连接遗漏
  • 标准接口:DataSource

  • 官方提供的数据库连接池接口,由第三方组织实现此接口

  • 功能:获取连接Connection getConnection() throws SQLException;
  • 常见产品:

  • C3P0(使用较少)

  • DBCP(使用较少)
  • Druid
  • Hikari(SpringBoot默认)

Druid连接池是阿里巴巴开源的数据库连接池项目

功能强大性能优秀,是Java最好的数据库连接池之一

切换连接池

安装连接池依赖:

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.19</version
</dependency>

修改配置文件:

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

MyBatis动态SQL语句

核心动态SQL标签

标签 作用 适用场景 示例
<if> 条件判断 根据条件包含SQL片段 查询条件动态拼接
<choose> <when> <otherwise> 多条件选择 类似switch-case逻辑 多选一条件判断
<where> 动态WHERE子句 自动处理WHERE和AND/OR 动态查询条件
<set> 动态SET子句 自动处理SET和逗号 动态更新字段
<foreach> 循环遍历 集合遍历处理 IN查询、批量操作
<trim> 自定义trim 自定义前缀后缀处理 复杂SQL片段处理
<bind> 创建变量 创建OGNL变量 字符串处理、复用

条件判断标签

<if> 标签

<select id="findActiveBlogWithTitleLike" resultType="Blog">
  SELECT * FROM BLOG 
  WHERE state = 'ACTIVE'
  <if test="title != null">
    AND title LIKE #{title}
  </if>
</select>

适用场景:简单的条件判断,当条件满足时包含SQL片段

<choose> 标签

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG WHERE state = 'ACTIVE'
  <choose>
    <when test="title != null">
      AND title LIKE #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name LIKE #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

适用场景:多条件选择,类似编程语言中的switch-case语句

动态SQL片段标签

<where> 标签

<select id="findActiveBlogLike" resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
      state = #{state}
    </if>
    <if test="title != null">
      AND title LIKE #{title}
    </if>
    <if test="author != null and author.name != null">
      AND author_name LIKE #{author.name}
    </if>
  </where>
</select>

适用场景:动态WHERE子句,自动处理多余的AND/OR和空WHERE

<set> 标签

<update id="updateAuthorIfNecessary">
  UPDATE Author
  <set>
    <if test="username != null">username=#{username},</if>
    <if test="password != null">password=#{password},</if>
    <if test="email != null">email=#{email},</if>
  </set>
  WHERE id = #{id}
</update>

适用场景:动态UPDATE语句的SET子句,自动处理多余的逗号

<foreach> 标签

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT * FROM POST 
  WHERE ID IN
  <foreach item="item" index="index" collection="list"
           open="(" separator="," close=")">
    #{item}
  </foreach>
</select>

适用场景:集合遍历,处理IN查询、批量插入等

属性:

  • collection:集合名词
  • item:遍历出的单个元素
  • separator:每一次遍历使用的分隔符(可选)
  • open:遍历开始前拼接的片段(可选),注意是整个遍历开始前
  • close:遍历结束后拼接的片段(可选),注意是整个遍历结束后

<trim> 标签

<update id="updateAuthorIfNecessary">
  UPDATE Author
  <trim prefix="SET" suffixOverrides=",">
    <if test="username != null">username=#{username},</if>
    <if test="password != null">password=#{password},</if>
  </trim>
  WHERE id = #{id}
</update>

适用场景:自定义SQL片段处理,更灵活的前缀后缀控制

<bind> 标签

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

适用场景:创建可在其他部分使用的变量,常用于字符串处理

SQL复用标签

<sql> 和 <include> 标签

<sql id="userColumns">id, username, password, email</sql>

<select id="selectUsers" resultType="User">
  SELECT <include refid="userColumns"/> FROM users
</select>

适用场景:SQL片段复用,避免重复代码

常用动态SQL模式

1. 动态查询

<select id="selectByCondition" resultType="User">
  SELECT * FROM users
  <where>
    <if test="name != null and name != ''">
      AND name LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
    <if test="email != null and email != ''">
      AND email = #{email}
    </if>
  </where>
  ORDER BY id DESC
</select>

2. 动态更新

<update id="updateUser" parameterType="User">
  UPDATE users
  <set>
    <if test="name != null and name != ''">
      name = #{name},
    </if>
    <if test="age != null">
      age = #{age},
    </if>
    <if test="email != null and email != ''">
      email = #{email},
    </if>
  </set>
  WHERE id = #{id}
</update>

3. 批量插入

<insert id="batchInsert">
  INSERT INTO users (name, age, email) VALUES
  <foreach collection="list" item="user" separator=",">
    (#{user.name}, #{user.age}, #{user.email})
  </foreach>
</insert>

4. IN查询

<select id="selectByIds" resultType="User">
  SELECT * FROM users WHERE id IN
  <foreach item="id" index="index" collection="ids"
           open="(" separator="," close=")">
    #{id}
  </foreach>
</select>

注意事项

  1. 参数引用:使用 #{} 防止SQL注入,使用 ${} 直接拼接(慎用)
  2. 集合处理:collection属性可为list、array或Map的key
  3. OGNL表达式:test属性中可使用复杂的OGNL表达式
  4. 空值处理:注意字符串空值和null值的区别
  5. 性能考虑:复杂动态SQL可能影响查询计划缓存

MyBatis 常用注解总结

核心注解

注解 作用域 说明 适用场景 示例
@Select 方法 定义查询SQL 数据查询操作 @Select("SELECT * FROM emp WHERE id = #{id}")
@Insert 方法 定义插入SQL 数据插入操作 @Insert("INSERT INTO emp(username) VALUES(#{username})")
@Update 方法 定义更新SQL 数据更新操作 @Update("UPDATE emp SET name = #{name} WHERE id = #{id}")
@Delete 方法 定义删除SQL 数据删除操作 @Delete("DELETE FROM emp WHERE id = #{id}")

结果映射注解

注解 作用域 说明 适用场景 示例
@Results 方法 定义结果映射集合 复杂字段映射 @Results({@Result(column="dept_id", property="deptId")})
@Result 方法内 定义单个结果映射 字段名与属性名不一致时 @Result(column="create_time", property="createTime")
@ResultMap 方法 引用XML中定义的resultMap 复用XML映射配置 @ResultMap("EmpResultMap")

主键相关注解

注解 作用域 说明 适用场景 示例
@Options 方法 配置SQL执行选项 主键生成、缓存等 @Options(useGeneratedKeys=true, keyProperty="id")
@SelectKey 方法 自定义主键生成 特殊主键生成策略 @SelectKey(statement="SELECT SEQ.NEXTVAL", keyProperty="id", resultType=int.class, before=true)

动态SQL注解

注解 作用域 说明 适用场景 示例
@SelectProvider 方法 动态生成查询SQL 复杂动态查询 @SelectProvider(type=EmpSqlProvider.class, method="getEmpByCondition")
@InsertProvider 方法 动态生成插入SQL 复杂动态插入 @InsertProvider(type=EmpSqlProvider.class, method="insertEmp")
@UpdateProvider 方法 动态生成更新SQL 复杂动态更新 @UpdateProvider(type=EmpSqlProvider.class, method="updateEmp")
@DeleteProvider 方法 动态生成删除SQL 复杂动态删除 @DeleteProvider(type=EmpSqlProvider.class, method="deleteEmp")

参数绑定注解

注解 作用域 说明 适用场景 示例
@Param 参数 指定参数名称 多参数方法 findByNameAndAge(@Param("name") String name, @Param("age") Integer age)
@MapKey 方法 指定Map的key 返回Map集合 @MapKey("id") Map<Integer, Emp> findAll();

缓存相关注解

注解 作用域 说明 适用场景 示例
@CacheNamespace 类 配置二级缓存 启用Mapper级别缓存 @CacheNamespace
@CacheNamespaceRef 类 引用其他Mapper缓存 共享缓存配置 @CacheNamespaceRef(EmpMapper.class)

常用注解详细说明

1. CRUD基础注解

@Select 查询注解

@Select("SELECT * FROM emp WHERE id = #{id}")
Emp findById(Integer id);

@Select("SELECT * FROM emp WHERE name LIKE CONCAT('%', #{name}, '%')")
List<Emp> findByName(@Param("name") String name);

@Insert 插入注解

@Insert("INSERT INTO emp(username, name, gender) VALUES(#{username}, #{name}, #{gender})")
int insert(Emp emp);

// 获取自增主键
@Insert("INSERT INTO emp(username, name) VALUES(#{username}, #{name})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertWithKey(Emp emp);

@Update 更新注解

@Update("UPDATE emp SET name = #{name}, gender = #{gender} WHERE id = #{id}")
int update(Emp emp);

@Delete 删除注解

@Delete("DELETE FROM emp WHERE id = #{id}")
int deleteById(Integer id);

2. 结果映射注解

@Results 和 @Result

@Select("SELECT id, username, create_time FROM emp WHERE id = #{id}")
@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "username", column = "username"),
    @Result(property = "createTime", column = "create_time")
})
Emp findById(Integer id);

3. 动态SQL提供者

SQL提供者类

public class EmpSqlProvider {
    public String getEmpByCondition(Map<String, Object> params) {
        StringBuilder sql = new StringBuilder("SELECT * FROM emp WHERE 1=1");
        if (params.get("name") != null) {
            sql.append(" AND name LIKE CONCAT('%', #{name}, '%')");
        }
        return sql.toString();
    }
}

使用提供者

@SelectProvider(type = EmpSqlProvider.class, method = "getEmpByCondition")
List<Emp> findByCondition(@Param("name") String name);

4. 多参数处理

使用@Param

@Select("SELECT * FROM emp WHERE name = #{name} AND gender = #{gender}")
List<Emp> findByNameAndGender(@Param("name") String name, @Param("gender") Integer gender);

最佳实践建议

  1. 简单SQL使用注解:对于简单的CRUD操作,直接使用注解更简洁
  2. 复杂SQL使用XML:对于复杂的动态SQL,建议使用XML配置
  3. 主键回填:插入操作记得使用@Options(useGeneratedKeys=true)
  4. 参数命名:多参数方法使用@Param明确参数名称
  5. 结果映射:字段名与属性名不一致时使用@Results注解

这些注解可以帮助您快速构建MyBatis数据访问层,提高开发效率。

后端 Java Web入门 持久层框架
许可协议:  CC BY 4.0
分享

相关文章

下一篇

Spring MyBatis Plus

上一篇

AOP面向切面编程

最近更新

  • Hello Halo
  • 状态机
  • 数据表设计规范
  • 缓存优化
  • 事务失效

热门标签

Halo 异常处理 Redisson 并行 Vue 并发 递归 请求限流 SpringAMQP 类加载器

目录

©2026 Norlcayn的博客. 保留部分权利。

使用 Halo 主题 Chirpy