共1026行
2026-07-07 09:03:04
早期 Java 持久层方案:
MyBatis 2010年由iBatis更名而来,半自动ORM:
传统SSM(Spring+SpringMVC+MyBatis)痛点:
SpringBoot 基于Spring,核心思想约定优于配置:
mybatis-spring-boot-starter:官方整合包,自动完成:
<!-- SpringBoot父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
</parent>
<dependencies>
<!-- web 可选,后台接口必备 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis整合Boot -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 连接池HikariCP(boot自带,无需额外引入) -->
<!-- lombok简化实体get/set -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>CREATE DATABASE IF NOT EXISTS boot_mybatis;
USE boot_mybatis;
CREATE TABLE t_user (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32) NOT NULL,
age INT,
email VARCHAR(50)
);
INSERT INTO t_user(username,age,email) VALUES
('张三',20,'zhangsan@qq.com'),
('李四',22,'lisi@163.com');# 数据源配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/boot_mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 你的数据库密码
# MyBatis配置
mybatis:
# mapper xml文件存放路径
mapper-locations: classpath:mapper/*.xml
# 实体类别名包,xml中可直接写类名不用全限定名
type-aliases-package: com.demo.entity
configuration:
map-underscore-to-camel-case: true # 开启下划线转驼峰 t_user -> tUser
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印执行SQL日志package com.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 扫描Mapper接口包,所有Mapper自动注册Bean
@MapperScan("com.demo.mapper")
@SpringBootApplication
public class MyBatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisDemoApplication.class, args);
}
}package com.demo.entity;
import lombok.Data;
@Data
public class User {
private Integer id;
private String username;
private Integer age;
private String email;
}package com.demo.mapper;
import com.demo.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
// 查询所有用户
List<User> selectAll();
// 根据id查询
User selectById(@Param("uid") Integer id);
// 新增用户
int insert(User user);
// 修改
int update(User user);
// 删除
int deleteById(Integer id);
}<?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">
<!-- namespace绑定对应Mapper接口全类名 -->
<mapper namespace="com.demo.mapper.UserMapper">
<select id="selectAll" resultType="User">
select id, username, age, email from t_user
</select>
<select id="selectById" resultType="User">
select * from t_user where id = #{uid}
</select>
<insert id="insert">
insert into t_user(username, age, email)
values(#{username}, #{age}, #{email})
</insert>
<update id="update">
update t_user
set username=#{username}, age=#{age}, email=#{email}
where id=#{id}
</update>
<delete id="deleteById">
delete from t_user where id = #{id}
</delete>
</mapper>package com.demo.service;
import com.demo.entity.User;
import java.util.List;
public interface UserService {
List<User> getAllUser();
User getUserById(Integer id);
int addUser(User user);
}package com.demo.service.impl;
import com.demo.entity.User;
import com.demo.mapper.UserMapper;
import com.demo.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public List<User> getAllUser() {
return userMapper.selectAll();
}
@Override
public User getUserById(Integer id) {
return userMapper.selectById(id);
}
@Override
public int addUser(User user) {
return userMapper.insert(user);
}
}package com.demo.controller;
import com.demo.entity.User;
import com.demo.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/list")
public List<User> list() {
return userService.getAllUser();
}
@GetMapping("/{id}")
public User getById(@PathVariable Integer id) {
return userService.getUserById(id);
}
@PostMapping("/add")
public String add(@RequestBody User user) {
int rows = userService.addUser(user);
return rows > 0 ? "新增成功" : "新增失败";
}
}GET http://localhost:8080/user/listGET http://localhost:8080/user/1{
"username":"王五",
"age":25,
"email":"wangwu@qq.com"
}控制台会自动打印完整执行SQL、参数、返回行数。
单纯使用SpringBoot+MyBatis做分页有两种原生方案,都有明显缺陷:
limit offset,size,还要单独写一条count统计总数;PageHelper 是国内开源的MyBatis分页插件,专门解决MyBatis分页繁琐问题:
limit语句,自动生成count查询获取总条数、总页数;pagehelper-spring-boot-starter 是官方适配SpringBoot的自动装配包,零配置整合,不用手动注册拦截器Bean。PageHelper.startPage(pageNum, pageSize) 设置分页参数;PageInterceptor 拦截查询SQL;select count(*) from (原业务sql) 统计总记录;limit ?,?;PageInfo对象,包含:当前页、每页条数、总条数、总页数、是否有上/下一页等分页全部信息;配合上文SpringBoot+MyBatis项目,新增依赖:
<!-- pagehelper 分页插件 starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/boot_mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.demo.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# PageHelper分页配置
pagehelper:
# 数据库方言,自动识别也可手动指定 mysql/oracle
helper-dialect: mysql
# 开启合理化分页:pageNum<=1 自动查第一页,pageNum>总页数 查询最后一页
reasonable: true
# 支持分页参数传递:pageNum/pageSize 从请求参数自动获取
support-methods-arguments: true
# 开启count统计,默认true
count: true
# 分页参数名,前端可传 pageNum pageSize
params: pageNum=pageNum;pageSize=pageSizeUserMapper.java
package com.demo.mapper;
import com.demo.entity.User;
import java.util.List;
public interface UserMapper {
// 普通全量查询,无需手动加limit
List<User> selectAll();
// 带条件查询分页(示例:根据用户名模糊查询)
List<User> selectByUsername(String username);
}UserMapper.xml
<mapper namespace="com.demo.mapper.UserMapper">
<select id="selectAll" resultType="User">
select id, username, age, email from t_user
</select>
<select id="selectByUsername" resultType="User">
select * from t_user
<where>
<if test="username != null and username != ''">
username like concat('%',#{username},'%')
</if>
</where>
</select>
</mapper>package com.demo.service;
import com.demo.entity.User;
import com.github.pagehelper.PageInfo;
public interface UserService {
// 无条件分页
PageInfo<User> pageUser(Integer pageNum, Integer pageSize);
// 带条件分页
PageInfo<User> pageUserByUsername(Integer pageNum, Integer pageSize, String username);
}package com.demo.service.impl;
import com.demo.entity.User;
import com.demo.mapper.UserMapper;
import com.demo.service.UserService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public PageInfo<User> pageUser(Integer pageNum, Integer pageSize) {
// 核心一行:开启分页,参数1=当前页码,参数2=每页条数
PageHelper.startPage(pageNum, pageSize);
// 紧随其后的第一条Mapper查询会自动分页
List<User> userList = userMapper.selectAll();
// 封装分页全部数据,返回PageInfo
return new PageInfo<>(userList);
}
@Override
public PageInfo<User> pageUserByUsername(Integer pageNum, Integer pageSize, String username) {
PageHelper.startPage(pageNum, pageSize);
List<User> list = userMapper.selectByUsername(username);
return new PageInfo<>(list);
}
}package com.demo.controller;
import com.demo.entity.User;
import com.demo.service.UserService;
import com.github.pagehelper.PageInfo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
/**
* 无条件分页接口
* 示例地址:http://localhost:8080/user/page?pageNum=1&pageSize=2
*/
@GetMapping("/page")
public PageInfo<User> page(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "2") Integer pageSize
){
return userService.pageUser(pageNum, pageSize);
}
/**
* 条件分页接口
* 示例地址:http://localhost:8080/user/page/search?pageNum=1&pageSize=2&username=张
*/
@GetMapping("/page/search")
public PageInfo<User> pageSearch(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "2") Integer pageSize,
@RequestParam(required = false) String username
){
return userService.pageUserByUsername(pageNum, pageSize, username);
}
}访问接口后返回JSON示例:
{
"pageNum": 1, // 当前页码
"pageSize": 2, // 每页条数
"size": 2, // 当前页实际数据条数
"startRow": 1,
"endRow": 2,
"total": 10, // 总记录数
"pages": 5, // 总页数
"list": [ // 当前页数据集合
{"id":1,"username":"张三","age":20,"email":"zhangsan@qq.com"},
{"id":2,"username":"李四","age":22,"email":"lisi@163.com"}
],
"prePage": 0, // 上一页页码
"nextPage": 2, // 下一页页码
"isFirstPage": true, // 是否第一页
"isLastPage": false, // 是否最后一页
"hasPreviousPage": false, // 是否有上一页
"hasNextPage": true // 是否有下一页
}PageHelper.startPage(1,2);
userMapper.selectById(1); // 这条会分页
List<User> list = userMapper.selectAll(); // 这条不分页reasonable: true 合理化分页PageHelper.startPage(1,10,false) 第三个参数关闭count,提升查询性能。// 第三个参数false:不执行count查询,只分页数据
PageHelper.startPage(pageNum, pageSize, false);配置文件开启 support-methods-arguments: true 后,Controller不用手动接收pageNum/pageSize,直接传递到Service:
// Service层方法
PageInfo<User> page(String username, @Param("pageNum") Integer pageNum, @Param("pageSize") Integer pageSize);Mapper XML中的left join、子查询、多条件动态SQL无需任何修改,插件自动解析拼接limit。
Mapper.xml 是 MyBatis 核心 SQL 映射文件,作用:绑定 Mapper 接口、编写 SQL、处理参数、封装返回结果、动态拼接 SQL,配合 SpringBoot+MyBatis 使用。
每个 xml 第一行必须写,用于 IDE 提示、语法校验
<?xml version="1.0" encoding="UTF-8"?>
<!-- MyBatis 3.x DTD约束 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper>整个文件只有一个根标签,核心属性:
<mapper namespace="com.demo.mapper.UserMapper">
<!-- 所有SQL标签写在这里 -->
</mapper>namespace:必填,值 = Mapper 接口的全限定类名<select> / <insert> / <update> / <delete><resultMap>(自定义字段映射、多表关联)<sql> + <include><if> <where> <set> <foreach> <choose> <trim><parameterMap>(极少用,淘汰)完整标准结构模板:
<?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.demo.mapper.UserMapper">
<!-- 1. 可复用SQL片段 -->
<sql id="userColumn">id,username,age,email</sql>
<!-- 2. 自定义结果映射(复杂场景用) -->
<resultMap id="UserResultMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
</resultMap>
<!-- 3. 查询语句 -->
<select id="selectById" resultType="User">
select <include refid="userColumn"/> from t_user where id = #{id}
</select>
<!-- 4. 新增 -->
<insert id="insert">...</insert>
<!-- 5. 修改 -->
<update id="update">...</update>
<!-- 6. 删除 -->
<delete id="deleteById">...</delete>
</mapper>属性只有一个核心:namespace
规则:
示例:
接口:com.demo.mapper.UserMapper
xml:<mapper namespace="com.demo.mapper.UserMapper">
| 属性 | 作用 |
|---|---|
id |
对应 Mapper 接口方法名,必须唯一 |
parameterType |
传入参数类型(实体、Integer、String、Map,可省略自动推断) |
resultType |
返回单行/多行数据类型(简单场景,实体/基本类型) |
resultMap |
复杂返回映射,优先级高于 resultType,和 <resultMap> id 绑定 |
useCache |
是否开启二级缓存,默认true |
flushCache |
执行后清空缓存,增删改默认true |
<select> 查询标签(最常用)<!-- resultType:别名User,对应type-aliases-package下的实体 -->
<select id="selectAll" resultType="User">
select id, username, age, email from t_user
</select>fetchSize:批量拉取数据大小timeout:SQL执行超时时间order by 直接写在SQL内即可#{变量}:预编译占位符,防SQL注入(推荐)${变量}:字符串直接拼接,存在注入风险,仅用于表名、排序字段<!-- 单参数 -->
<select id="selectById" resultType="User">
select * from t_user where id = #{id}
</select>
<!-- 多参数:接口使用@Param("name") -->
<select id="selectByNameAge" resultType="User">
select * from t_user where username = #{name} and age = #{age}
</select><insert> 新增标签专属属性:
useGeneratedKeys="true":开启自增主键回填keyProperty="id":把数据库自增id赋值给实体id字段<!-- 新增并自动回填主键id到User实体 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into t_user(username, age, email)
values(#{username}, #{age}, #{email})
</insert><update> 更新标签<update id="update">
update t_user
set username=#{username}, age=#{age}, email=#{email}
where id=#{id}
</update><delete> 删除标签<delete id="deleteById">
delete from t_user where id = #{id}
</delete>抽取重复列、重复查询条件,多处复用,减少冗余代码。
<sql id="片段ID"> 定义公共片段<include refid="片段ID"/> 引入片段<!-- 抽取查询列 -->
<sql id="userCols">
id, username, age, email
</sql>
<!-- 抽取查询条件 -->
<sql id="userWhere">
<if test="username != null">
and username like concat('%',#{username},'%')
</if>
</sql>
<select id="listUser" resultType="User">
select <include refid="userCols"/>
from t_user
where 1=1
<include refid="userWhere"/>
</select><resultMap id="唯一标识" type="实体类">
<id>:主键字段,MyBatis做缓存、去重必需<result>:普通字段映射<association>:一对一关联<collection>:一对多集合<resultMap id="UserMap" type="User">
<!-- column:数据库字段,property:实体属性 -->
<id column="id" property="id"/>
<result column="user_name" property="username"/>
<result column="user_age" property="age"/>
</resultMap>
<!-- 使用resultMap代替resultType -->
<select id="getUser" resultMap="UserMap">
select id,user_name,user_age from t_user where id=#{id}
</select><resultMap id="UserDeptMap" type="User">
<id column="u_id" property="id"/>
<result column="username" property="username"/>
<!-- 一对一关联Dept实体 -->
<association property="dept" javaType="Dept">
<id column="d_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap><resultMap id="UserOrderMap" type="User">
<id column="id" property="id"/>
<!-- 一对多,集合类型List<Order> -->
<collection property="orderList" ofType="Order">
<id column="o_id" property="orderId"/>
<result column="order_no" property="orderNo"/>
</collection>
</resultMap>test 属性写 OGNL 表达式,判断参数是否存在、值是否相等
<select id="listByCondition" resultType="User">
select * from t_user
where 1=1
<if test="username != null and username != ''">
and username like concat('%',#{username},'%')
</if>
<if test="age != null">
and age = #{age}
</if>
</select>自动处理:
and/orwhere 1=1<select id="listByWhere" resultType="User">
select * from t_user
<where>
<if test="username != null">
username like concat('%',#{username},'%')
</if>
<if test="age != null">
and age = #{age}
</if>
</where>
</select>更新时多个字段可选更新,自动删除最后多余逗号
<update id="updateSelective">
update t_user
<set>
<if test="username != null">username=#{username},</if>
<if test="age != null">age=#{age},</if>
<if test="email != null">email=#{email}</if>
</set>
where id=#{id}
</update>属性:
<delete id="deleteBatch">
delete from t_user
where id in
<foreach collection="ids" item="uid" open="(" close=")" separator=",">
#{uid}
</foreach>
</delete><insert id="insertBatch">
insert into t_user(username,age) values
<foreach collection="userList" item="u" separator=",">
(#{u.username},#{u.age})
</foreach>
</insert><select id="listChoose" resultType="User">
select * from t_user
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="username != null">
username = #{username}
</when>
<otherwise>
age >= 18
</otherwise>
</choose>
</where>
</select>prefix:开头添加字符串
prefixOverrides:去掉开头多余and
suffixOverrides:去掉末尾逗号
<!-- 模拟where标签 -->
<trim prefix="where" prefixOverrides="and|or">
<if test="name != null">and name=#{name}</if>
</trim>
<!-- 模拟set标签 -->
<trim prefix="set" suffixOverrides=",">
<if test="name != null">name=#{name},</if>
</trim>#{param}
?${param}
<!-- 动态排序只能用${} -->
<select id="listOrder" resultType="User">
select * from t_user
order by ${sortColumn} ${sortType}
</select>mapper-locations: classpath:mapper/*.xml,xml 统一放在 resources/mapper 下;map-underscore-to-camel-case: true 可省去大量 resultMap 字段映射;<where>,不要手写 where 1=1;<set> 避免逗号语法错误;<foreach>,不要循环单条操作;#{};useGeneratedKeys="true" keyProperty="id" 才能回填ID。<?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.demo.mapper.UserMapper">
<!-- 复用列片段 -->
<sql id="userCol">id,username,age,email</sql>
<!-- 自定义结果映射 -->
<resultMap id="UserResult" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="age" property="age"/>
<result column="email" property="email"/>
</resultMap>
<!-- 条件分页查询 -->
<select id="listUser" resultMap="UserResult">
select <include refid="userCol"/>
from t_user
<where>
<if test="username != null and username != ''">
username like concat('%',#{username},'%')
</if>
<if test="minAge != null">
age >= #{minAge}
</if>
</where>
order by id desc
</select>
<!-- 新增用户回填主键 -->
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user(username,age,email)
values(#{username},#{age},#{email})
</insert>
<!-- 动态更新 -->
<update id="updateUser">
update t_user
<set>
<if test="username != null">username=#{username},</if>
<if test="age != null">age=#{age},</if>
<if test="email != null">email=#{email}</if>
</set>
where id=#{id}
</update>
<!-- 批量删除 -->
<delete id="batchDelete">
delete from t_user
where id in
<foreach collection="ids" item="uid" open="(" close=")" separator=",">
#{uid}
</foreach>
</delete>
</mapper>