单元1 · MyBatis 快速入门
ORM 概念、环境搭建、第一个 Mapper
- ORM:对象关系映射,将表记录映射为 Java 对象。
- 核心对象:SqlSessionFactory 构建 SqlSession,执行 SQL 与事务。
- 依赖:mybatis + 数据库驱动,Boot 项目用 mybatis-spring-boot-starter。
实训1.1 引入 MyBatis 依赖
在 pom.xml 中引入 mybatis 与 mysql 驱动依赖。
mybatis 核心依赖 + mysql-connector-j 驱动;Spring Boot 集成可用 mybatis-spring-boot-starter。
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.15</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
</dependency>
</dependencies>
实训1.2 MyBatis 配置文件
编写 mybatis-config.xml,配置数据源环境。
environments 定义数据库环境,default 指定默认环境;transactionManager 事务管理器。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/helpme"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
实训1.3 SqlSession 工厂与 CRUD
通过 SqlSessionFactory 获取 SqlSession,执行查询并输出结果。
SqlSessionFactoryBuilder 读取配置构建工厂;openSession 获取会话,selectList 查询。
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
public class MyBatisDemo {
public static void main(String[] args) throws Exception {
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
try (SqlSession session = factory.openSession()) {
List<Map<String, Object>> users = session.selectList("com.helpme.mapper.UserMapper.findAll");
for (Map<String, Object> u : users) {
System.out.println(u);
}
}
}
}
单元2 · 核心配置与映射文件
namespace、resultType、参数占位
- namespace:XML 映射文件命名空间绑定 Mapper 接口。
- resultType:自动映射,列名与属性名一致时可用。
- resultMap:显式定义列到属性的映射,支持复杂对象。
实训2.1 Mapper 接口与 XML 绑定
创建 UserMapper 接口与 UserMapper.xml,实现按 id 查询。
XML namespace 必须等于接口全限定名,方法 id 对应 SQL 语句 id。
public interface UserMapper {
User findById(Long id);
}
实训2.2 resultType 映射
编写按 id 查询的 select 语句,resultType 指定为 User 类型。
resultType 用于简单映射,列名与属性名一致时自动映射;#{} 预编译占位。
<?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.helpme.mapper.UserMapper">
<select id="findById" parameterType="long" resultType="com.helpme.entity.User">
SELECT id, name, age FROM user WHERE id = #{id}
</select>
</mapper>
实训2.3 resultMap 复杂映射
使用 resultMap 处理列名 user_name 与属性 userName 的映射。
resultMap 显式定义列到属性的映射关系,column 数据库列、property 实体属性。
<mapper namespace="com.helpme.mapper.UserMapper">
<resultMap id="userMap" type="com.helpme.entity.User">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<result column="age" property="age"/>
</resultMap>
<select id="findById" resultMap="userMap">
SELECT id, user_name, age FROM user WHERE id = #{id}
</select>
</mapper>
单元3 · 动态 SQL
: 按条件动态拼接 SQL 片段。/ 自动处理 AND/OR 前缀与 SET 尾逗号。: : 遍历集合拼 IN 条件或批量插入。
实训3.1 条件拼接
实现按可选条件(name、age)查询用户列表。
<select id="findByCondition" resultType="com.helpme.entity.User">
SELECT * FROM user
WHERE 1 = 1
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</select>
实训3.2 与
用
<select id="findByCondition" resultType="com.helpme.entity.User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
<update id="updateUser">
UPDATE user
<set>
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
</set>
WHERE id = #{id}
</update>
实训3.3 批量操作
使用
foreach 的 collection 为参数集合名,item 为循环变量,open/close/separator 控制拼接。
<select id="findByIds" resultType="com.helpme.entity.User">
SELECT * FROM user
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
单元4 · 参数与结果映射
@Param、Map 参数、自动映射
- @Param:多参数显式命名,XML 中引用参数名。
- Map 参数:适合动态参数,key 对应 #{} 占位名。
- 自增主键:useGeneratedKeys + keyProperty 回填。
实训4.1 @Param 多参数
实现按 name 和 age 两个参数查询的方法。
多参数时用 @Param 命名,XML 中 #{} 引用参数名。
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
List<User> findByNameAndAge(@Param("name") String name, @Param("age") int age);
}
实训4.2 Map 参数传递
用 Map 传递查询参数实现登录校验查询。
Map 的 key 对应 #{} 中的占位名,适合动态参数多的场景。
import org.apache.ibatis.annotations.Param;
import java.util.Map;
public interface UserMapper {
User findByMap(Map<String, Object> params);
}
实训4.3 返回自增主键
insert 后获取自增主键并回填到实体。
useGeneratedKeys="true" keyProperty="id" 将数据库自增主键回填到实体属性。
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(name, age) VALUES(#{name}, #{age})
</insert>
单元5 · 关联查询
一对一、一对多、多对多
- association:一对一关联对象映射。
- collection:一对多/多对多集合映射,ofType 指定元素类型。
- JOIN:多表关联通过 SQL JOIN 查询。
实训5.1 一对一查询
查询订单同时关联出下单用户信息(orders 表与 user 表)。
association 映射单个关联对象,property 关联属性名,javaType 指定类型。
<resultMap id="orderMap" type="com.helpme.entity.Order">
<id column="id" property="id"/>
<result column="order_no" property="orderNo"/>
<association property="user" javaType="com.helpme.entity.User">
<id column="uid" property="id"/>
<result column="user_name" property="name"/>
</association>
</resultMap>
<select id="findOrderWithUser" resultMap="orderMap">
SELECT o.id, o.order_no, u.id AS uid, u.user_name
FROM orders o
JOIN user u ON o.user_id = u.id
WHERE o.id = #{id}
</select>
实训5.2 一对多查询
查询用户及其订单列表(一个用户多个订单)。
collection 映射集合属性,ofType 指定集合元素类型。
<resultMap id="userOrdersMap" type="com.helpme.entity.User">
<id column="id" property="id"/>
<result column="user_name" property="name"/>
<collection property="orders" ofType="com.helpme.entity.Order">
<id column="oid" property="id"/>
<result column="order_no" property="orderNo"/>
</collection>
</resultMap>
<select id="findUserWithOrders" resultMap="userOrdersMap">
SELECT u.id, u.user_name, o.id AS oid, o.order_no
FROM user u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.id = #{id}
</select>
实训5.3 多对多查询
查询学生及其选修课程列表(学生、课程、选课关系三表)。
多对多通过中间表 JOIN,collection 映射课程集合。
<resultMap id="studentCoursesMap" type="com.helpme.entity.Student">
<id column="id" property="id"/>
<result column="stu_name" property="name"/>
<collection property="courses" ofType="com.helpme.entity.Course">
<id column="cid" property="id"/>
<result column="course_name" property="name"/>
</collection>
</resultMap>
<select id="findStudentWithCourses" resultMap="studentCoursesMap">
SELECT s.id, s.stu_name, c.id AS cid, c.course_name
FROM student s
LEFT JOIN student_course sc ON sc.student_id = s.id
LEFT JOIN course c ON c.id = sc.course_id
WHERE s.id = #{id}
</select>
单元6 · 缓存机制
一级缓存、二级缓存
- 一级缓存:SqlSession 级别,默认开启,会话内共享。
- 二级缓存:namespace 级别,跨会话,需配置开启。
- 失效场景:增删改清缓存、多表关联、分布式需外部缓存。
实训6.1 一级缓存
说明 MyBatis 一级缓存的作用范围并验证同一 SqlSession 内重复查询。
一级缓存默认开启,作用域为 SqlSession;同一会话内相同查询命中缓存。
try (SqlSession session = factory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User u1 = mapper.findById(1L);
User u2 = mapper.findById(1L);
System.out.println("同一对象:" + (u1 == u2));
}
实训6.2 二级缓存配置
在映射文件中开启二级缓存并说明作用域。
二级缓存跨 SqlSession,作用域为 namespace;需实体实现 Serializable。
<mapper namespace="com.helpme.mapper.UserMapper">
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
</mapper>
实训6.3 缓存失效场景
列举导致缓存失效或数据不一致的典型场景。
增删改操作会清空缓存;多表关联查询缓存需谨慎;分布式场景需外部缓存。
// 1. 执行增删改后缓存自动清空
// 2. 多表 JOIN 查询:某表更新时另一 namespace 缓存不失效
// 3. 分布式多实例:本地缓存无法共享,需 Redis 等外部缓存
// 4. 一级缓存与 SqlSession 生命周期绑定
public class CacheNote {
// 以上为要点说明
}
单元7 · 注解开发
@Select、@Insert、@Update、@Delete
- 注解 SQL:@Select/@Insert/@Update/@Delete 直写 SQL。
- @Options:useGeneratedKeys 回填自增主键。
- Provider:@SelectProvider 用 Java 代码拼接动态 SQL。
实训7.1 注解 CRUD
用注解方式实现 UserMapper 的增删改查。
@Select/@Insert/@Update/@Delete 直接写在接口方法上,适合简单 SQL。
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(Long id);
@Select("SELECT * FROM user")
List<User> findAll();
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
int insert(User user);
@Update("UPDATE user SET name=#{name}, age=#{age} WHERE id=#{id}")
int update(User user);
@Delete("DELETE FROM user WHERE id=#{id}")
int delete(Long id);
}
实训7.2 @Options 自增主键
注解方式插入并回填自增主键。
@Options(useGeneratedKeys=true, keyProperty="id") 回填主键。
import org.apache.ibatis.annotations.*;
public interface UserMapper {
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
}
实训7.3 @SelectProvider 动态 SQL
使用 SelectProvider 注解实现动态查询。
Provider 类方法拼接 SQL 字符串,适合复杂动态 SQL 的注解场景。
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.jdbc.SQL;
import java.util.List;
public interface UserMapper {
@SelectProvider(type = UserSqlProvider.class, method = "findByCondition")
List<User> findByCondition(String name, Integer age);
class UserSqlProvider {
public String findByCondition(String name, Integer age) {
return new SQL() {{
SELECT("*");
FROM("user");
if (name != null && !name.isEmpty()) {
WHERE("name = #{name}");
}
if (age != null) {
WHERE("age = #{age}");
}
}}.toString();
}
}
}
单元8 · MyBatis-Plus 快速入门
MP 依赖、BaseMapper、通用 CRUD
- MP starter:mybatis-plus-boot-starter 自动配置。
- BaseMapper:继承即得通用 CRUD 方法。
- IService:Service 层增强,提供批量与链式查询。
实训8.1 引入 MP 依赖
在 Spring Boot 项目引入 mybatis-plus-boot-starter。
mybatis-plus-boot-starter 提供自动配置,替代原生 mybatis starter。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
实训8.2 BaseMapper 通用 CRUD
让 UserMapper 继承 BaseMapper
BaseMapper 提供 insert/selectById/selectList/updateById/deleteById 等通用方法。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 继承通用 CRUD,无需手写 SQL
}
实训8.3 Service 层 IService
创建继承 IService 的 UserService 接口及其实现类。
IService/ServiceImpl 提供批量操作、链式查询等增强能力。
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {
}
单元9 · MP 条件构造器
QueryWrapper、LambdaQueryWrapper
- QueryWrapper:字符串列名条件构造器。
- LambdaQueryWrapper:方法引用属性,编译期校验字段。
- UpdateWrapper:不加载实体直接按条件更新。
实训9.1 QueryWrapper 条件查询
用 QueryWrapper 实现按姓名模糊查询且年龄大于 18 的用户。
QueryWrapper 的 like、gt 方法链式构建 where 条件。
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.util.List;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
public List<User> search(String name, Integer minAge) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", name)
.gt("age", minAge)
.orderByDesc("age");
return list(wrapper);
}
}
实训9.2 LambdaQueryWrapper
用 LambdaQueryWrapper 按属性名(非列名)查询,避免硬编码。
Lambda 写法用 User::getName 引用属性,编译期校验字段名。
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import java.util.List;
public List<User> findByDept(String dept) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getDepartment, dept)
.ge(User::getAge, 20);
return list(wrapper);
}
实训9.3 UpdateWrapper
用 UpdateWrapper 实现按条件更新(不查实体直接改)。
UpdateWrapper 的 set 方法指定更新字段,条件链构建 where。
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
public boolean updateAgeByName(String name, int newAge) {
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("name", name).set("age", newAge);
return update(wrapper);
}
单元10 · MP 分页与逻辑删除
分页插件、@TableLogic
- 分页插件:PaginationInnerInterceptor 自动拼 LIMIT。
- Page 对象:current/size 入参,records/total 出参。
- 逻辑删除:@TableLogic 将 DELETE 转 UPDATE 标记。
实训10.1 分页插件配置
配置 MybatisPlusInterceptor 分页插件。
PaginationInnerInterceptor 注册到 MybatisPlusInterceptor,分页自动拼接 LIMIT。
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
实训10.2 分页查询
使用 Page 对象实现分页查询并输出总记录数。
new Page<>(current, size) 传入分页参数,返回 Page 含 records/total。
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
public Page<User> pageUsers(int current, int size) {
Page<User> page = new Page<>(current, size);
Page<User> result = page(page, null);
System.out.println("总数:" + result.getTotal());
return result;
}
实训10.3 逻辑删除
为 User 实体配置逻辑删除字段 deleted,删除操作变为 UPDATE。
@TableLogic 标注逻辑删除字段;MP 自动把 DELETE 转为 UPDATE deleted=1。
import com.baomidou.mybatisplus.annotation.TableLogic;
public class User {
private Long id;
private String name;
@TableLogic
private Integer deleted;
}
单元11 · MP 代码生成器与扩展
代码生成器、自定义 SQL、主键策略
- 主键策略:IdType.AUTO/ASSIGN_ID 等控制主键生成。
- 自定义 SQL:MP Mapper 可手写注解/XML 方法,兼容原生 MyBatis。
- 代码生成器:FastAutoGenerator 一键生成各层代码。
实训11.1 主键策略
为实体配置 ASSIGN_ID 雪花主键策略。
@TableId(type = IdType.ASSIGN_ID) 使用雪花算法生成分布式 ID。
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
}
实训11.2 自定义 SQL 扩展
在 MP Mapper 中自定义 SQL 实现多表关联查询。
MP 允许在 Mapper 中手写方法 + 注解/XML,与原生 MyBatis 完全兼容。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT u.*, o.order_no FROM user u " +
"LEFT JOIN orders o ON o.user_id = u.id WHERE u.id = #{id}")
User selectUserWithOrder(Long id);
}
实训11.3 代码生成器
简述 MP 代码生成器 AutoGenerator 的配置步骤。
配置数据源、全局策略、包名后一键生成 Entity/Mapper/Service/Controller。
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
public class GeneratorDemo {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/helpme", "root", "123456")
.globalConfig(builder -> builder.author("Helpme").outputDir("/tmp/gen"))
.packageConfig(builder -> builder.parent("com.helpme"))
.strategyConfig(builder -> builder.addInclude("user", "orders"))
.execute();
}
}
单元12 · 综合项目实训
综合运用所学知识完成项目
- 综合应用:实体 + BaseMapper + Wrapper 快速开发。
- 事务:@Transactional 保证多表操作一致性。
实训12.1 图书管理 - 实体与 Mapper
定义 Book 实体(id、title、author、price)与 MP Mapper。
综合运用实体注解 + BaseMapper 通用 CRUD,快速搭建数据层。
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@TableName("book")
public class Book {
@TableId(type = IdType.AUTO)
private Long id;
private String title;
private String author;
private Double price;
// getter / setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
}
实训12.2 图书管理 - 分页检索
实现图书分页 + 按标题模糊查询接口。
Page + LambdaQueryWrapper 组合实现分页条件检索。
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
public Page<Book> searchBooks(int current, int size, String keyword) {
LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>();
wrapper.like(keyword != null && !keyword.isEmpty(), Book::getTitle, keyword)
.orderByDesc(Book::getId);
return bookMapper.selectPage(new Page<>(current, size), wrapper);
}
实训12.3 图书管理 - 事务删除
实现删除图书同时清理关联库存记录的事务方法。
@Transactional 保证多表操作原子性,任一失败整体回滚。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookService {
private final BookMapper bookMapper;
private final StockMapper stockMapper;
public BookService(BookMapper bookMapper, StockMapper stockMapper) {
this.bookMapper = bookMapper;
this.stockMapper = stockMapper;
}
@Transactional
public void deleteBook(Long bookId) {
bookMapper.deleteById(bookId);
stockMapper.deleteByBookId(bookId);
}
}