MyBatis

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

快速入门

引入MyBatis依赖

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

Snipaste_2025-08-07_21-56-52

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

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

1
2
3
4
5
6
7
8
9
10
<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文件中进行相关配置的编写,主要是关于数据库的连接信息

配置信息如下:

1
2
3
4
5
# 配置数据库连接信息
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语句的日志信息,可以添加以下配置:

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

创建Mapper接口

关键字:@Mapper

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

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

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

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

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

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

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

@Param注解

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

1
2
@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接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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. 先去除注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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映射文件的位置:

1
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最好的数据库连接池之一

切换连接池

安装连接池依赖:

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.19</version
</dependency>

修改配置文件:

1
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> 标签

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

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

<choose> 标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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> 标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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> 标签

1
2
3
4
5
6
7
8
9
<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> 标签

1
2
3
4
5
6
7
8
<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> 标签

1
2
3
4
5
6
7
8
<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> 标签

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

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

SQL复用标签

<sql><include> 标签

1
2
3
4
5
<sql id="userColumns">id, username, password, email</sql>

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

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

常用动态SQL模式

1. 动态查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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. 动态更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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. 批量插入

1
2
3
4
5
6
<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查询

1
2
3
4
5
6
7
<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 查询注解

1
2
3
4
5
@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 插入注解

1
2
3
4
5
6
7
@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 更新注解

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

@Delete 删除注解

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

2. 结果映射注解

@Results@Result

1
2
3
4
5
6
7
@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提供者类

1
2
3
4
5
6
7
8
9
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();
}
}

使用提供者

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

4. 多参数处理

使用@Param

1
2
@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数据访问层,提高开发效率。