Spring MyBatis
MyBatis
- MyBatis是一款优秀的持久层框架,用于简化JDBC的开发
- 官网:MyBatis 3
快速入门
引入MyBatis依赖
可以在创建SpringBoot项目时,勾选相关依赖:

注意:由于是与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映射的方式:
- 先去除注解
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映射文件更好
- 创建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 可以根据方法参数自动推断参数类型。不省略的好处在于可读性更好
- 最重要的一步,将XML文件存放在指定位置:

辅助配置
配置XML映射文件的位置:
mybatis.mapper-locations=classpath:mapper/*.xml
java目录和resource目录编译后都会存放在类路径下(classpath)
所以配置中表达的意思是在resource/mapper目录下,查找所有的xml文件
推荐IDEA MyBatis插件:MyBatisX
JDBC与MyBatis

数据库连接池
- 数据库连接池是个**容器**,负责分配、管理数据库连接(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>
注意事项
- 参数引用:使用
#{}防止SQL注入,使用${}直接拼接(慎用) - 集合处理:collection属性可为list、array或Map的key
- OGNL表达式:test属性中可使用复杂的OGNL表达式
- 空值处理:注意字符串空值和null值的区别
- 性能考虑:复杂动态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);
最佳实践建议
- 简单SQL使用注解:对于简单的CRUD操作,直接使用注解更简洁
- 复杂SQL使用XML:对于复杂的动态SQL,建议使用XML配置
- 主键回填:插入操作记得使用
@Options(useGeneratedKeys=true) - 参数命名:多参数方法使用
@Param明确参数名称 - 结果映射:字段名与属性名不一致时使用
@Results注解
这些注解可以帮助您快速构建MyBatis数据访问层,提高开发效率。