假如你一定要写注释,请确保它描述了离你最近的代码。
简介 官网:MyBatis 3 | 简介 – mybatis
MyBatis 是一款优秀的 持久层框架 ,用于简化 JDBC 开发
主要作用:
封装了 JDBC 的繁琐操作(如连接管理、SQL 执行、结果映射)。
通过 XML 或注解 配置 SQL 与 Java 对象的映射关系。
在分层架构中的位置:
1 Controller(控制层) → Service(业务层) → DAO(持久层) → 数据库
DAO(Data Access Object) :通过 MyBatis 与数据库交互。
Mybatis入门 快速入门 创建项目 新建一个SpringBoot项目用于Mybatis入门:
勾选Mybatis框架以及连接Mysql的驱动:
创建成功后项目中有这两个依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 <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 >
创建数据模型 假设数据库中有如下的用户表。
我们创建对应的用户类:Model/User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class User { private Integer id; private String name; private Short age; private Short gender; private String phone; public User () { } public User (Integer id, String name, Short age, Short gender, String phone) { this .id = id; this .name = name; this .age = age; this .gender = gender; this .phone = phone; } }
配置Mybatis 配置Mybatis四要素:
要素
配置键
作用
示例值
驱动类名
spring.datasource.driver-class-name
指定 JDBC 驱动类,用于连接数据库
com.mysql.cj.jdbc.Driver
数据库 URL
spring.datasource.url
定义数据库类型、地址、端口、库名及连接参数
jdbc:mysql://localhost:3306/mybatis
用户名
spring.datasource.username
数据库登录账号
root
密码
spring.datasource.password
数据库登录密码
1234
src/main/resources/application.properties
:
1 2 3 4 spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/mybatis spring.datasource.username =root spring.datasource.password =你的密码
编写SQL语句 Mapper/UserMapper.java
:
1 2 3 4 5 @Mapper public interface UserMapper { @Select("SELECT * FROM USER") public List<User> selectAll () ; }
声明一个接口并使用 @Mapper
进行注解后,Mybatis会自动生成运行对象,并将对象交给IOC管理 。调用对应的方法自动执行 @Select
注解中的SQL语句,并将查询结构封装为对象返回。
测试程序 src/test/java/top/ulna520/mybatisquickstart/MybatisQuickStartApplicationTests.java
:
1 2 3 4 5 6 7 8 9 10 11 12 13 @SpringBootTest class MybatisQuickStartApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads () { List<User> users = userMapper.selectAll(); users.forEach(System.out::println); } }
运行结果:
1 2 3 User{id =1, name ='Alice' , age =25, gender =2, phone ='13800000001' } User{id =2, name ='Bob' , age =30, gender =1, phone ='13800000002' } User{id =3, name ='Charlie' , age =28, gender =0, phone ='13800000003' }
JDBC 定义 :
JDBC :Java DataBase Connectivity
使用 Java 语言 操作 关系型数据库 的一套 API 规范 。
本质 :
Sun 公司 官方定义的一套操作所有关系型数据库的 统一接口规范 。
各数据库厂商根据规范提供 驱动实现类 (打包成驱动 jar 包)。
开发者调用 JDBC 接口编程,真正执行的代码 是驱动 jar 包中的实现类。
数据库连接池 数据库连接池 是一个 容器 ,用于分配 和管理 数据库连接(Connection
对象)。
作用 :
复用 已有连接,避免频繁创建/销毁连接带来的性能开销。
统一管理 连接的生命周期,防止连接泄漏。
核心机制
初始化 :启动时创建一定数量的数据库连接,存放在连接池中。
获取连接 :应用程序向连接池请求可用连接,而不是直接新建连接。
归还连接 :使用完毕后将连接放回连接池,而不是关闭物理连接。
回收机制 :
对空闲时间超过最大空闲时间 的连接进行释放。
避免因未释放连接导致的 连接泄漏 。
常见连接池 :
实现
特点
C3P0
较早期,稳定性好,性能一般
DBCP
Apache 提供,配置简单
Druid
阿里巴巴开源,功能强大,性能优秀,监控能力强
HikariCP
高性能,轻量级,Spring Boot 默认连接池
更改数据库连接池:直接引入依赖
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > ${druid-version}</version > </dependency >
lombok Lombok 是一个 Java 类库,通过注解自动生成常用方法(构造器、getter/setter、equals、hashCode、toString 等),并可自动生成日志变量。
作用:简化开发,减少样板代码,提高开发效率
注解
功能
@Getter
/ @Setter
为所有 属性生成 get
/ set
方法
@ToString
自动生成易读的 toString()
方法
@EqualsAndHashCode
根据类的非静态字段自动重写 equals()
和 haMshCode()
方法
@Data
综合注解,等价于 @Getter
+ @Setter
+ @ToString
+ @EqualsAndHashCode
@NoArgsConstructor
生成无参构造方法
@AllArgsConstructor
生成包含所有字段(除 static
修饰字段)的全参构造方法
依赖示例:
1 2 3 4 5 6 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 最新版本</version > <scope > provided</scope > </dependency >
scope=provided
表示编译期可用,运行期不需要 Lombok.jar。
Mybatis-crud 首先我们准备一个Mapper注解接口:
1 2 3 4 @Mapper public interface EmpMapper { }
接下来我们写的增删改查都是其中的一个方法。
删除 SQL语句:
1 DELETE FROM emp WHERE id = 1 ;
接口方法:
1 2 @Delete("DELETE FROM emp WHERE id = #{id}") public void delete (Integer id) ;
也可以是
1 2 @Delete("DELETE FROM emp WHERE id = #{id}") public Integer delete (Integer id) ;
此时返回删除的数据条数。
打开Mypatis日志 application.properties
:
1 2 mybatis.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl
Mybatis日志:
预编译SQL 普通 SQL vs 预编译 SQL:
特性
普通 SQL(Statement)
预编译 SQL(PreparedStatement)
SQL 发送次数
每次执行都发送完整 SQL
首次发送 SQL 模板,后续只传参数
编译次数
每次执行都重新解析、优化、编译
首次编译后缓存执行计划,后续复用
性能
多次重复执行时性能较低
多次重复执行时性能更高
安全性
易受 SQL 注入攻击
参数化传值,防止 SQL 注入
适用场景
一次性执行的 SQL
多次执行的相似 SQL
代码可维护性
需要频繁拼接字符串
SQL 模板与参数分离,结构清晰
SQL注入 采用普通SQL的语句:
1 SELECT COUNT (* ) FROM user where username = '' AND password = '' ;
普通sql语句会直接将我们传入的用户名 和密码 拼接到SQL语句中。
例如我们输入:ulna
123456
1 SELECT COUNT (* ) FROM user where username = 'ulna' AND password = '123456' ;
但是如果我们输入:ulna
'OR '1'='1
1 SELECT COUNT (* ) FROM user where username = 'ulna' AND password = '' OR '1' = '1' ;
此时由于 '1' = '1'
条件恒成立,所以用户名和密码错误也可以登录成功。
参数占位符
写法
处理方式
是否预编译
安全性
典型用途
#{}
在执行 SQL 前会被替换为 ?
占位符,由 JDBC 进行参数绑定
✅ 是
高(防 SQL 注入)
传递参数值(数字、字符串、日期等)
${}
在执行 SQL 前直接进行字符串拼接,将参数值原样拼入 SQL
❌ 否
低(易受 SQL 注入)
动态拼接表名、列名、排序字段等
新增 sql语句:
1 2 INSERT INTO emp (username, name, gender, /image, job, entrydate, dept_id, create_time, update_time) VALUES('songyuanqiao' , '宋元桥' , '男' , '1.jpg' , '工程师' , '2012-10-09' , 2 , '2022-10-01 00:00:00' , '2022-10-01 00:00:00' );
Mybatis接口方法示例:
1 2 3 @Insert("INSERT INTO emp(username, name, gender, /image, job, entrydate, dept_id, create_time, update_time) " + "VALUES(#{username}, #{name}, #{gender}, #{/image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})") void insert (Emp emp) ;
Mybatis日志:
主键返回 在数据添加成功后,需要获取插入数据库数据的主键 。
1 2 3 4 @Options(keyProperty = "id", useGeneratedKeys = true) @Insert("INSERT INTO emp(username, name, gender, /image, job, entrydate, dept_id, create_time, update_time) " + "VALUES(#{username}, #{name}, #{gender}, #{/image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})") void insert (Emp emp) ;
@Options
useGeneratedKeys = true
→ 启用 JDBC 的自动生成主键功能
keyProperty = "id"
→ 指定实体类中接收主键的属性名
添加这个注解后,就会将自动生成的主键值封装到传入对象的 id
属性中
更新 sql语句:
1 2 3 4 5 6 7 8 9 10 UPDATE empSET username = 'songdaxia' , name = '宋大侠' , gender = 1 , / image = '1.jpg' , job = 2 , entrydate = '2012-01-01' , dept_id = 2 , update_time = '2022-10-11 12:12:12' WHERE id = 19 ;
接口方法:
1 2 @Update("UPDATE emp SET username=#{username}, name=#{name}, gender=#{gender}, /image=#{/image}, job=#{job}, entrydate=#{entrydate}, dept_id=#{deptId}, update_time=#{updateTime} WHERE id=#{id}") void update (Emp emp) ;
查询 数据封装 在 MyBatis 中,Java 实体类(POJO)与数据库表字段之间的映射关系是数据持久化的核心。
如果字段名与列名不一致,MyBatis 无法自动映射,需要额外配置。
java实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Emp { private Integer id; private String username; private String password; private String name; private String gender; private String /image; private String job; private LocalDate entryDate; private Integer deptId; private LocalDateTime createTime; private LocalDateTime updateTime; }
数据库表:
1 2 3 4 5 6 7 8 9 10 11 12 13 CREATE TABLE emp ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY , username VARCHAR (20 ), password VARCHAR (20 ), name VARCHAR (10 ), gender CHAR (1 ), / image VARCHAR (300 ), job VARCHAR (10 ), entry_date DATE , dept_id INT UNSIGNED, create_time DATETIME, update_time DATETIME );
默认规则 :Java 属性名与数据库列名 完全一致 时,MyBatis 会自动映射。
下划线转驼峰 :
数据库列名:entry_date
Java 属性名:entryDate
方法一 :给字段起别名
1 2 3 4 / / 功能:给字段起别名,让取出与对象属性一致@Select ("select id, username, password, name, gender, mname, job, entrydate, " + "dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}") public Emp getById(Integer id);
方法二 :@Results
注解
1 2 3 4 5 6 7 8 @Results({ @Result(column = "dept_id", property = "deptId"), @Result(column = "create_time", property = "createTime"), @Result(column = "update_time", property = "updateTime") }) @Select("select * from emp where id = #{id}") public Emp getById (Integer id) ;
方法三 :开启Mybatis的驼峰命名与下划线自动映射开关(推荐)
1 2 mybatis.configuration.map-underscore-to-camel-case =true
简单查询 SQL语句:
1 SELECT * FROM emp WHERE id = 1
Mybatis接口:
1 2 @Select("SELECT * FROM emp WHERE id = #{id}") Emp selectById (Integer id) ;
只要我们开启了上述的自动映射,这里无需额外操作。
条件查询 示例:姓名模糊匹配 + 性别精确匹配 + 入职时间范围筛选 + 更新时间降序
SQL语句:
1 2 3 4 5 6 SELECT * FROM emp WHERE name LIKE '%${name}%' AND gender = #{gender} AND entrydate BETWEEN #{begin } AND #{end } ORDER BY update_time DESC ;
Mybatis接口语句:
1 2 3 4 5 6 @Select("SELECT * FROM emp " + "WHERE name LIKE '%${name}%' " + "AND gender = #{gender} " + "AND emp_date BETWEEN #{begin} AND #{end} " + "ORDER BY update_time DESC") public List<Emp> list (String name, Short gender, LocalDate begin, LocalDate end) ;
为什么不能直接使用#{name}
#{}
会被替换成 ?
占位符,'%#{name}%'
会变成:
这是无效的 SQL,因为 ?
放在 ''
中不能被识别为参数。
解决办法:
用 concat
或在 Java 代码里提前拼好 %
:
1 WHERE name LIKE CONCAT('%' , #{name}, '%' )
在Java传参前:
1 name = "%" + name + "%";
然后在SQL里直接:
参数说明 SpringBoot2.x 集成 MyBatis
写法 :方法参数名可直接作为 SQL 占位符使用
示例 :
1 2 3 4 5 6 @Select ("SELECT * FROM emp " + "WHERE name LIKE #{name} " + "AND gender = #{gender} " + "AND update_time BETWEEN #{begin} AND #{end} " + "ORDER BY update_time DESC") List< Emp> list(String name, Short gender, LocalDate begin , LocalDate end );
独立Mybatis(非 SpringBoot 环境)
写法 :必须使用 @Param
显式绑定参数名
示例 :
1 2 3 4 5 6 7 8 9 @Select("SELECT * FROM emp " + "WHERE name LIKE CONCAT('%', #{name}, '%') " + "AND gender = #{gender} " + "AND update_time BETWEEN #{begin} AND #{end} " + "ORDER BY update_time DESC") List<Emp> list (@Param("name") String var1, @Param("gender") Short var2, @Param("begin") LocalDate var3, @Param("end") LocalDate var4) ;
XML配置文件 XML映射文件规范:
文件位置 必须与 Mapper
接口包名相同
namespace 必须与 Mapper 接口的全限定类名 一致 例:namespace="com.itheima.mapper.EmpMapper"
id 必须与 Mapper 接口方法名 一致 例:<select id="list">
对应 EmpMapper.list(...)
resultType 与方法返回值类型一致(通常是实体类全限定名)
实战示例 文件包名与位置:
基本结构:
1 2 3 4 5 6 7 <?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 ="top/ulna520/springbootmybatiscrud/Mapper/EmpMapper.java" > </mapper >
我们将这个方法改为XML配置:
1 2 @Select("SELECT * FROM emp WHERE id = #{id}") Emp selectById (Integer id) ;
我们在xml中添加对应的Select配置:
1 2 3 4 5 6 7 8 9 <?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 ="top.ulna520.springbootmybatiscrud.Mapper.EmpMapper" > <select id ="selectById" resultType ="top.ulna520.springbootmybatiscrud.Model.Emp" > SELECT * FROM emp WHERE id = #{id} </select > </mapper >
然后将EmpMapper中的注解删除,只保留签名方法:
1 Emp selectById (Integer id) ;
插件推荐——MybatisX
安装插件后:
会自动匹配Mybatis接口与XML配置。可以点击左侧的小鸟图标方便的跳转。
动态SQL 动态 SQL 适用于根据用户输入或外部条件动态拼接查询语句的场景,常用于:
多条件筛选(如姓名、性别、日期范围)
后台管理系统的列表查询
提高 SQL 灵活性,避免硬编码
if 用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。
原始sql:
1 2 3 4 5 select * from pm_memo where title like concat('%' , #{name}, '%' ) and explore = #{explore} and entrydate between #{fbegin} and #{fend} order by update_time desc
使用if标签进行改造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="list" resultType ="com.itheima.pojo.Emp" > select id, username, password, name, gender, /image, job, entrydate, dept_id, create_time, update_time from emp where <if test ="name != null" > name like concat('%',#{name},'%') </if > <if test ="gender != null" > and gender = #{gender} </if > <if test ="begin != null and end != null" > and entrydate between #{begin} and #{end} </if > order by update_time desc</select >
只有当test中的条件为ture时,<if>
中的SQL语句才会被拼接。
但是此时如果所有的条件都为 false
,就会导致存在 where
但where中条件为空。SQL语句报错。
WHERE
自动添加 WHERE
关键字 :当内部有条件时自动生成 WHERE
,否则不生成。
自动去除首个 AND
/ OR
:避免 SQL 语法错误,如 WHERE AND ...
。
提升 SQL 可读性与健壮性 :无需手动判断是否需要加 WHERE
或处理连接符。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select id ="list" resultType ="com.itheima.pojo.Emp" > select id, username, password, name, gender, /image, job, entrydate, dept_id, create_time, update_time from emp <where > <if test ="name != null" > name like concat('%',#{name},'%') </if > <if test ="gender != null" > and gender = #{gender} </if > <if test ="begin != null and end != null" > and entrydate between #{begin} and #{end} </if > </where > order by update_time desc</select >
✅ 如果第一个条件没有 and
,后续条件可以保留 and
,MyBatis 会自动处理连接逻辑。
SET 在 MyBatis 中,<set></set>
标签主要用于 动态生成 SQL 的 SET
子句 ,尤其适合 UPDATE
语句。它的核心功能有两个:
自动去掉多余的逗号
即使你在 <if></if>
中的 SQL 末尾加了 ,
,<set></set>
会帮你在生成 SQL 时去掉最后一个多余的逗号,避免语法错误。
条件更新
结合 <if></if>
标签,只有在参数不为空(或满足条件)时,对应的字段才会出现在 SET
子句中,实现 部分字段的动态更新 。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <update id ="update2" > update emp <set > <if test ="username != null" > username = #{username},</if > <if test ="name != null" > name = #{name},</if > <if test ="gender != null" > gender = #{gender},</if > <if test ="/image != null" > /image = #{/image},</if > <if test ="job != null" > job = #{job},</if > <if test ="entrydate != null" > entrydate = #{entrydate},</if > <if test ="deptId != null" > dept_id = #{deptId},</if > <if test ="updateTime != null" > update_time = #{updateTime}</if > </set > where id = #{id}</update >
foreach 在 MyBatis 中,<foreach></foreach>
标签用于 遍历集合类型参数 ,动态生成 SQL 片段,常用于:
IN
查询(如批量删除、批量查询)
批量插入
拼接多个字段或条件
常见属性:
属性名
说明
collection
要遍历的集合名称(如 List、数组、或 Map 的某个键)
item
每次遍历的元素变量名
index
当前元素的索引(可选)
separator
每个元素之间的分隔符(如 ,
)
open
遍历开始前添加的字符串(如 (
)
close
遍历结束后添加的字符串(如 )
)
collection
名称必须和传入参数一致,若是Map类型,要用 collection="mapKey"
示例:
接口方法:
1 public void deleteByIds (List<Integer> ids) ;
xml映射文件:
1 2 3 4 5 6 <delete id ="deleteByIds" > delete from emp where id in <foreach collection ="ids" item ="id" separator ="," open ="(" close =")" > #{id} </foreach > </delete >
sql&include SQL 片段 (<sql></sql>
标签)
用于定义可重用的 SQL 语句片段,避免多处重复编写相同列或条件。
必须有唯一 id 作为引用标识
引用 SQL 片段 (<include></include>
标签)
通过 refid
属性引入指定的 <sql></sql>
片段内容。
会在编译映射文件时直接替换成对应 SQL 字符串。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <sql id ="commonsSelect" > id, username, password, name, gender, /image, job, dept_id, create_time, update_time</sql > <select id ="list" resultType ="com.thitima.pojo.Emp" > select <include refid ="commonsSelect" /> from emp <where > <if test ="name != null" > and name like #{name} </if > <if test ="gender != null and gender != ''" > and gender = #{gender} </if > <if test ="deptId != null" > and dept_id = #{deptId} </if > <if test ="begin != null and end != null" > and create_time between #{begin} and #{end} </if > </where > </select >
refid
必须匹配 :引用名应与 <sql id="..."></sql>
完全一致。
PageHelper PageHelper 是一个基于 MyBatis 的无侵入式分页插件。它的核心作用是自动在SQL中追加分页语句(如 LIMIT
),并提供分页信息封装、避免手写分页逻辑。
快速集成 1 2 3 4 5 <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper-spring-boot-starter</artifactId > <version > 1.4.7</version > </dependency >
使用示例 创建简单查询语句 1 2 @Select("SELECT * FROM emp") public List<Emp> list () ;
在Service层使用Pagehelper 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service public class EmpService { @Autowired public EmpMapper empMapper; public List<Emp> findAll (int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<Emp> empList = empMapper.list(); Page<Emp> page = (Page<Emp>) empList; return page.getResult(); } }
PageHelper常用方法:
方法
作用
示例
startPage(int pageNum, int pageSize)
按页码分页
PageHelper.startPage(1, 10)
startPage(int pageNum, int pageSize, String orderBy)
分页并指定排序规则
PageHelper.startPage(1, 10, "id desc")
offsetPage(long offset, long limit)
按偏移量分页(适合无限滚动等场景)
PageHelper.offsetPage(20, 10)
orderBy(String orderBy)
单独设置排序规则(不分页也可用)
PageHelper.orderBy("create_time desc")
clearPage()
清除当前线程分页参数,避免影响后续查询
PageHelper.clearPage()
Page对象常用方法:
方法
返回值类型
说明
getResult()
List<T>
当前页数据列表
getTotal()
long
总记录数
getPages()
int
总页数
getPageNum()
int
当前页码
getPageSize()
int
每页记录数
getStartRow()
int
当前页起始行号(从 1 开始)
getEndRow()
int
当前页结束行号
isCount()
boolean
是否执行了 count
查询
PageInfo(对Page进行封装类 )常用方法:
方法
返回值类型
说明
getList()
List<T>
当前页数据
getTotal()
long
总记录数
getPages()
int
总页数
getPageNum()
int
当前页码
getPageSize()
int
每页记录数
isHasNextPage()
boolean
是否有下一页
isHasPreviousPage()
boolean
是否有上一页
getNavigatePages()
int
导航页码数量(可配置)
getNavigatepageNums()
int[]
导航页码数组
isIsFirstPage()
boolean
是否为第一页
isIsLastPage()
boolean
是否为最后一页
分页查询示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 import com.github.pagehelper.PageHelper;import com.github.pagehelper.PageInfo;public PageInfo<User> listUsers (int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize, "create_time desc" ); List<User> users = userMapper.selectAll(); return new PageInfo <>(users); }
调用:
1 2 3 4 PageInfo<User> pageInfo = listUsers(2 , 5 ); System.out.println("总记录数: " + pageInfo.getTotal()); System.out.println("总页数: " + pageInfo.getPages()); System.out.println("当前页数据: " + pageInfo.getList());