mybatis-plus使用记录
MyBatis-Plus 学习笔记
一、 快速入门
MyBatis-Plus (MP) 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
1. 引入 Maven 依赖
要使用 MyBatis-Plus,首先需要在项目的 pom.xml
文件中引入相关依赖。
官网坐标查看:https://baomidou.com/getting-started/install/
代码段
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>最新版本</version>
</dependency>
2. Mapper 层开发
与 MyBatis 不同,使用 MP 后,Mapper 接口只需继承 BaseMapper
即可获得强大的 CRUD 功能,无需编写基础的 SQL 语句。
- 创建 Mapper 接口: 让你的 Mapper 接口继承
BaseMapper<T>
。 - 指定泛型: 泛型
T
必须是与数据库表对应的实体类(Entity)。
Java
@Mapper
public interface UserMapper extends BaseMapper<User> {// 复杂的、自定义的SQL可以写在这里
}
3. Service 层开发
在 Service 层,通过 Spring 的依赖注入(如 @Autowired
)引入 Mapper,然后就可以直接调用继承自 BaseMapper
的各种方法。
Java
@Service
public class UserServiceImpl implements IUserService {@Autowiredprivate UserMapper userMapper;public User getUserById(Long id) {// 直接调用 BaseMapper 提供的方法return userMapper.selectById(id);}
}
二、 核心概念与配置
1. 实体类与表映射
默认原则:
MP 默认采用驼峰命名与下划线命名的映射规则。 例如,Java 实体类的 userName
字段会自动映射到数据库表的 user_name
字段。
手动映射注解:
当实体类属性名与数据库字段名不一致,或有特殊需求时,可以使用注解进行手动映射。
@TableName
: 指定实体类对应的表名。@TableId
: 声明该字段为主键。type = IdType.AUTO
: 设置为主键自增。如果不设置,MP 默认会使用“雪花算法”生成一个长整型的 ID。
@TableField
: 声明非主键的字段。value = "db_column_name"
: 指定映射的数据库字段名。exist = false
: 表示该字段在数据库表中不存在,只是业务逻辑需要。
何时需要使用TableField:
Java
@Data
@TableName("sys_user") // 指定表名
public class User {@TableId(type = IdType.AUTO) // 主键自增private Long id;@TableField("user_name") // 字段名映射private String name;private Integer age;@TableField(exist = false) // 数据库中无此字段private String gender;
}
2. 常见配置
可以在 application.yml
或 application.properties
中添加 MP 的相关配置。
YAML
mybatis-plus:type-aliases-package: com.example.project.domain # 实体类别名扫描包mapper-locations: classpath*:/mapper/**/*.xml # Mapper XML 文件位置configuration:map-underscore-to-camel-case: true # 开启驼峰下划线转换log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
三、 条件构造器 (Wrapper)
条件构造器是 MP 的精髓所在,它能让我们以面向对象的方式构建复杂的 SQL 查询条件,而无需拼接字符串。
1. QueryWrapper
用于构建查询条件(SELECT
语句的 WHERE
部分)。
Java
// 查询年龄大于等于20,且状态为1的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", 20).eq("status", 1).orderByDesc("create_time");List<User> users = userMapper.selectList(queryWrapper);
2. UpdateWrapper
主要用于构建更新条件,可以精细化控制 UPDATE
语句的 SET
和 WHERE
部分。
Java
// 将所有状态为1的用户的年龄更新为18岁
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("age", 18) // 设置要更新的字段和值.eq("status", 1); // 设置更新条件userMapper.update(null, updateWrapper); // entity传null,表示不使用实体对象更新
3. Lambda 版 Wrapper
LambdaQueryWrapper
和 LambdaUpdateWrapper
是更推荐的使用方式。它们利用 Java 8 的 Lambda 表达式,可以避免硬编码字段名字符串,提高了代码的可读性和安全性。
- 传统方式:
.eq("status", 1)
- Lambda方式:
.eq(User::getStatus, 1)
Java
// 使用 LambdaUpdateWrapper
userMapper.update(null, new LambdaUpdateWrapper<User>().set(User::getAge, 18).eq(User::getStatus, 1)
);
4.自定义sql:
当where条件前面的内容比较难写,需要拼接的时候,用该方式,调用的是自定义的方法
四、 Service 层封装
MP 对 IService
接口也提供了默认实现 ServiceImpl
,进一步简化开发。
1. 接口与实现
- 接口: 业务接口继承
IService<T>
。 - 实现类: 继承
ServiceImpl<M, T>
,并实现自己的业务接口。
Java
// 接口 IUserService.java
public interface IUserService extends IService<User> {// 自定义复杂业务方法PageResult<UserDTO> getUserListPage(UserQuery query);
}// 实现类 UserServiceImpl.java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {// ServiceImpl已经注入了baseMapper,可以直接使用// 实现自定义方法@Overridepublic PageResult<UserDTO> getUserListPage(UserQuery query) {// ... 复杂逻辑long total = count(); // 调用IService提供的方法List<User> users = baseMapper.selectUserWithDetails(query); // 调用Mapper自定义方法// ...}
}
2. 常用方法
ServiceImpl
提供了一系列便捷的单表操作方法:
getOne(wrapper)
: 查询单个对象。list(wrapper)
: 查询对象列表。count(wrapper)
: 查询总记录数。save(entity)
: 新增。updateById(entity)
: 根据 ID 更新。removeById(id)
: 根据 ID 删除。
使用示例: 在 Controller 中注入 IUserService
后,可以直接调用这些方法。
五、 分页查询
MP 内置了强大的分页插件,使用非常简单。
1. 添加分页插件配置
需要将分页插件作为一个 Spring Bean 进行配置。
Java
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}
2. 在 Service/Controller 中使用
分页查询的核心是构建一个 Page
对象。
Java
public Page<User> findUsersByPage(int pageNum, int pageSize) {// 1. 创建 Page 对象,传入当前页码和每页数量Page<User> page = new Page<>(pageNum, pageSize);// 2. 调用 Mapper 的 selectPage 方法// MP 会自动拦截 SQL,并拼接上 LIMIT 分页语句userMapper.selectPage(page, null); // wrapper为null表示查询所有// 3. page 对象中就包含了分页数据和总数return page;
}
查询结果 Page<T>
对象包含了丰富的分页信息,如:
getRecords()
: 获取当前页的数据列表。getTotal()
: 获取总记录数。getPages()
: 获取总页数。getCurrent()
: 获取当前页码。getSize()
: 获取每页显示数量。
六、使用流程
决策流程:由上至下的选择策略
您可以将其视为一个决策层级,永远从最顶层(最抽象、最方便)的方法开始尝试,只有当前层级无法满足需求时,才往下一個层级移动。
- 首先,考虑
**IService**
的内置方法:这个功能是否为一个简单的单表 CRUD? - 如果不是,考虑自定义
**Service**
方法:这个功能是否为一个复杂的业务流程(例如多个步骤、需要事务控制、数据转换)? - **在自定义
**Service**
方法中,考虑 ****BaseMapper**
:我是否需要调用 Mapper 层的方法来执行数据库操作? - 最后,考虑自定义
**Mapper**
方法 + SQL:这个查询是否非常复杂(例如多表 JOIN),以至于Wrapper
无法满足?
1. 何时使用 IService
中封装的方法(默认首选)
这是指 ServiceImpl
中已经为您准备好的方法,如 save()
, getById()
, removeById()
, list()
, page()
, count()
等。
- 核心用途:处理所有标准的、单一数据表的“增、删、改、查”(CRUD) 操作。
- 使用时机:
- 当您执行的只是一个基础的、针对单一数据表的数据操作时。
- 查询条件可以用
QueryWrapper
或LambdaQueryWrapper
轻松构建。 - 这应该是您的第一选择与默认方法,通常由 Controller 层直接调用,或在简单的业务方法中被使用。
示例:在 Controller 中根据 ID 获取用户信息。
- Java
@GetMapping("/{id}")
public UserDTO findUserById(@PathVariable Long id) {// IService 的 getById 是最完美的选择,因为这是一个简单的单表查询User user = userService.getById(id); //// 可能会搭配 hutool 做对象转换return BeanUtil.copyProperties(user, UserDTO.class); //
}
2. 何时使用自定义 Service
方法(业务逻辑层)
这是在您自己的 IUserService
接口中定义,并在 UserServiceImpl
中实现的方法。
- 核心用途:封装复杂的业务逻辑,让 Controller 层保持简洁。一个业务逻辑通常不仅仅是一次数据库查询。
- 使用时机:
- 当一个功能需要多个步骤才能完成时(例如:先新增订单,再更新库存)。
- 当方法需要加入事务控制 (
@Transactional
) 时。 - 当逻辑中包含非数据库操作时(例如:调用外部 API、发送邮件、文件处理)。
- 当需要进行数据转换与组合时(例如:查询多个表,将结果组合成一个视图对象 DTO/VO)。
- 当您想将一段复杂的查询逻辑重复使用时。
示例:一个复杂的分页查询,它不仅需要查询数据,还可能需要处理查询参数、调用 Mapper 进行复杂查询,并对结果进行封装。
- Java
// 在 IUserService.java 中定义
public interface IUserService extends IService<User> {PageResult<User> getUserList(QueryParams params);
}// 在 UserServiceImpl.java 中实现
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic PageResult<User> getUserList(QueryParams params) {// 1. 处理业务逻辑...// 2. 调用 Mapper 层进行数据库操作List<User> users = baseMapper.selectUserWithDetails(params); //// 3. 调用 IService 的内置方法获取总数long total = count(); //// 4. 封装结果并返回return new PageResult<>(users, total);}
}
3. 何时使用 BaseMapper
中的方法(基础数据访问)
这是 IService
底层实现所依赖的基础 CRUD 方法,例如 selectById()
, selectList()
, insert()
等。
- 核心用途:作为
ServiceImpl
与 Mapper 层沟通的桥梁。 - 使用时机:
- 在现代 MyBatis-Plus 开发中,您很少会直接使用
**baseMapper**
的内置方法(如baseMapper.selectById()
),因为在ServiceImpl
中可以直接使用this.getById()
,后者更方便且功能一致。 baseMapper
对象最主要的用途是:在自定义的**Service**
方法中,调用您在**Mapper**
层自定义的方法(即下一点描述的情况)。
- 在现代 MyBatis-Plus 开发中,您很少会直接使用
- 示例:如上一个例子所示,
baseMapper.selectUserWithDetails(params)
就是通过baseMapper
来调用在UserMapper.xml
中自定义的 SQL 查询。
4. 何时使用自定义 Mapper
方法并自定义 SQL(终极武器)
这是在您的 UserMapper
接口中定义一个新方法,并在对应的 XML 文件中编写其完整的 SQL 语句。
- 核心用途:执行任何
Wrapper
产生不了的复杂 SQL 查询,提供对 SQL 的完全控制权。 - 使用时机(当
**Wrapper**
不敷使用时):- 需要进行多表
**JOIN**
关联查询时。 - 需要使用复杂的子查询或
UNION
操作时。 - 需要使用特定数据库的函数时(例如 MySQL 的
JSON_EXTRACT
或FIND_IN_SET
)。 - 有非常复杂的
GROUP BY
和HAVING
条件时。 - 对于性能要求极高,需要手动优化和调整 SQL 的场景。
- 需要进行多表
示例:在 UserMapper.xml
中定义一个关联查询。
- XML
<select id="selectUserWithDetails" resultType="com.example.UserVO">SELECTu.id,u.name,d.dept_nameFROMuser uLEFT JOINdepartment d ON u.dept_id = d.idWHEREu.status = #{params.status}
</select>
然后在 ServiceImpl
中通过 baseMapper
调用它。
总结对照表
方法类型 | 核心用途 | 使用时机 | 示例 |
---|---|---|---|
**IService** ** 内置方法** | 标准、单表 CRUD | 默认首选。所有简单的单表操作。 | userService.getById(1L); |
自定义 **Service** 方法 | 封装复杂业务逻辑 | 涉及多步骤、事务、外部调用、数据转换等。 | userService.createUserAndSendEmail(user); |
**BaseMapper** ** 内置方法** | IService 的底层实现 | 较少直接用,主要用于在 Service 中调用自定义 Mapper 方法。 | baseMapper.selectUserWithDetails(params); |
自定义 **Mapper** 方法 + SQL | 处理 Wrapper 无法应对的复杂 SQL | 最后手段。多表 JOIN、复杂子查询、SQL 优化。 | 在 XML 中写 JOIN 查询。 |