Appearance
MyBatis-Plus
1. MyBatis-Plus 简介
1.1 什么是 MyBatis-Plus
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
1.2 特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅通过少量配置即可实现单表大部分 CRUD 操作
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件
- 支持主键自动生成:支持多达 4 种主键策略
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用
- 支持自定义全局通用操作:支持全局通用方法注入
- 内置代码生成器:采用代码或者 Maven 插件可快速生成代码
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作
- 内置性能分析插件:可输出 SQL 语句以及其执行时间
- 内置全局拦截插件:提供全表 delete、update 操作智能分析阻断
2. 快速开始
2.1 环境要求
- JDK 8+
- Maven 3.0+
- MyBatis
- Spring Boot 2.x/3.x(可选)
2.2 引入依赖
Maven:
xml
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
</dependency>
<!-- 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.6</version>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>Gradle:
gradle
dependencies {
implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.6'
implementation 'com.baomidou:mybatis-plus-generator:3.5.6'
implementation 'org.apache.velocity:velocity-engine-core:2.3'
}2.3 配置
application.yml:
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
mybatis-plus:
configuration:
# 日志实现
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 驼峰下划线转换
map-underscore-to-camel-case: true
global-config:
db-config:
# 主键类型
id-type: auto
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除值
logic-delete-value: 1
# 逻辑未删除值
logic-not-delete-value: 02.4 启动类配置
java
@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}3. 核心功能
3.1 实体类注解
java
import com.baomidou.mybatisplus.annotation.*;
import java.time.LocalDateTime;
@Data
@TableName("user") // 指定表名
public class User {
@TableId(type = IdType.AUTO) // 主键策略
private Long id;
private String name;
private Integer age;
private String email;
@TableField(fill = FieldFill.INSERT) // 字段填充策略
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@Version // 乐观锁注解
private Integer version;
@TableLogic // 逻辑删除注解
private Integer deleted;
// 排除非表字段的三种方式
@TableField(exist = false)
private String remark;
// transient 关键字
private transient String tempField;
// static 字段
private static String staticField;
}主键策略 IdType:
| 值 | 描述 |
|---|---|
| AUTO | 数据库 ID 自增 |
| NONE | 无状态,该类型为未设置主键类型 |
| INPUT | 插入前自行 set 主键值 |
| ASSIGN_ID | 分配 ID(主键类型为 Number 或 String) |
| ASSIGN_UUID | 分配 UUID(主键类型为 String) |
3.2 Mapper 接口
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserMapper extends BaseMapper<User> {
// 自定义方法
List<User> selectAllByName(String name);
// 注解方式
@Select("SELECT * FROM user WHERE age > #{age}")
List<User> selectByAge(@Param("age") Integer age);
}3.3 Service 接口
java
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {
// 自定义业务方法
List<User> customMethod(String name);
}java
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public List<User> customMethod(String name) {
// 自定义实现
return baseMapper.selectAllByName(name);
}
}4. CRUD 接口
4.1 Mapper CRUD
java
@Autowired
private UserMapper userMapper;
// 插入
User user = new User();
user.setName("张三");
user.setAge(20);
user.setEmail("zhangsan@example.com");
int result = userMapper.insert(user);
// 根据ID删除
int result = userMapper.deleteById(1L);
// 根据条件删除
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三");
int result = userMapper.delete(wrapper);
// 根据ID更新
User user = new User();
user.setId(1L);
user.setName("李四");
int result = userMapper.updateById(user);
// 根据条件更新
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("name", "张三").set("age", 25);
int result = userMapper.update(null, updateWrapper);
// 根据ID查询
User user = userMapper.selectById(1L);
// 查询所有
List<User> users = userMapper.selectList(null);
// 查询一条
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三");
User user = userMapper.selectOne(wrapper);4.2 Service CRUD
java
@Autowired
private UserService userService;
// 插入
boolean success = userService.save(user);
// 批量插入
boolean success = userService.saveBatch(userList);
// 根据ID删除
boolean success = userService.removeById(1L);
// 根据条件删除
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三");
boolean success = userService.remove(wrapper);
// 根据ID更新
boolean success = userService.updateById(user);
// 根据条件更新
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("name", "张三").set("age", 25);
boolean success = userService.update(updateWrapper);
// 根据ID查询
User user = userService.getById(1L);
// 查询所有
List<User> users = userService.list();
// 查询一条
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三");
User user = userService.getOne(wrapper);
// 分页查询
Page<User> page = new Page<>(1, 10);
Page<User> result = userService.page(page, wrapper);5. 条件构造器
5.1 QueryWrapper
java
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 等于
wrapper.eq("name", "张三");
// 不等于
wrapper.ne("name", "张三");
// 大于
wrapper.gt("age", 18);
// 大于等于
wrapper.ge("age", 18);
// 小于
wrapper.lt("age", 65);
// 小于等于
wrapper.le("age", 65);
// BETWEEN
wrapper.between("age", 18, 65);
// NOT BETWEEN
wrapper.notBetween("age", 18, 65);
// LIKE
wrapper.like("name", "张");
// NOT LIKE
wrapper.notLike("name", "张");
// LIKE LEFT
wrapper.likeLeft("name", "三");
// LIKE RIGHT
wrapper.likeRight("name", "张");
// IN
wrapper.in("age", Arrays.asList(18, 19, 20));
// NOT IN
wrapper.notIn("age", Arrays.asList(18, 19, 20));
// IS NULL
wrapper.isNull("email");
// IS NOT NULL
wrapper.isNotNull("email");
// 分组
wrapper.groupBy("age");
// 排序
wrapper.orderByAsc("age");
wrapper.orderByDesc("create_time");
// HAVING
wrapper.having("count(*) > 1");
// 嵌套条件
wrapper.and(w -> w.eq("name", "张三").or().eq("name", "李四"));
wrapper.or(w -> w.eq("age", 18).eq("age", 20));
// 选择字段
wrapper.select("id", "name", "age");5.2 LambdaQueryWrapper
java
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 使用Lambda表达式,避免硬编码
wrapper.eq(User::getName, "张三")
.gt(User::getAge, 18)
.like(User::getEmail, "@example.com")
.orderByAsc(User::getAge)
.select(User::getId, User::getName, User::getAge);5.3 UpdateWrapper
java
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
// 设置更新字段
wrapper.set("name", "李四")
.set("age", 25)
.eq("id", 1L);
// Lambda方式
LambdaUpdateWrapper<User> lambdaWrapper = new LambdaUpdateWrapper<>();
lambdaWrapper.set(User::getName, "李四")
.set(User::getAge, 25)
.eq(User::getId, 1L);6. 分页查询
6.1 配置分页插件
java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}6.2 分页查询使用
java
// 方式一:使用Page对象
Page<User> page = new Page<>(1, 10); // 当前页,每页大小
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18);
Page<User> result = userMapper.selectPage(page, wrapper);
// 获取分页信息
System.out.println("总记录数:" + result.getTotal());
System.out.println("总页数:" + result.getPages());
System.out.println("当前页:" + result.getCurrent());
System.out.println("每页大小:" + result.getSize());
List<User> records = result.getRecords();
// 方式二:使用Service
Page<User> page = new Page<>(1, 10);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18);
Page<User> result = userService.page(page, wrapper);
// 不查询总记录数(性能优化)
Page<User> page = new Page<>(1, 10, false);7. 高级功能
7.1 自动填充
java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}7.2 乐观锁
java
// 实体类中添加版本字段
public class User {
@Version
private Integer version;
}
// 使用乐观锁
User user = userMapper.selectById(1L);
user.setName("新名字");
user.setEmail("new@example.com");
// 此时version会自动+1
userMapper.updateById(user);7.3 逻辑删除
java
// 实体类中添加逻辑删除字段
public class User {
@TableLogic
private Integer deleted;
}
// 删除操作会自动变为更新
userMapper.deleteById(1L);
// 实际执行:UPDATE user SET deleted=1 WHERE id=1 AND deleted=0
// 查询会自动过滤已删除数据
userMapper.selectList(null);
// 实际执行:SELECT * FROM user WHERE deleted=07.4 枚举处理
java
// 定义枚举
public enum GenderEnum {
MALE(1, "男"),
FEMALE(2, "女");
private final Integer code;
private final String desc;
GenderEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
// getter...
}
// 实体类中使用枚举
public class User {
@EnumValue // 标记数据库存储的值
private GenderEnum gender;
}
// 配置枚举处理器
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// ... 其他配置
return interceptor;
}
@Bean
public MybatisConfiguration mybatisConfiguration() {
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
return configuration;
}
}8. 代码生成器
8.1 快速代码生成
java
public class CodeGenerator {
public static void main(String[] args) {
// 代码生成器
AutoGenerator generator = new AutoGenerator();
// 全局配置
GlobalConfig globalConfig = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
globalConfig.setOutputDir(projectPath + "/src/main/java");
globalConfig.setAuthor("Your Name");
globalConfig.setOpen(false);
globalConfig.setSwagger2(true); // 实体属性 Swagger2 注解
generator.setGlobalConfig(globalConfig);
// 数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatis_plus");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("root");
generator.setDataSource(dataSourceConfig);
// 包配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setModuleName("system");
packageConfig.setParent("com.example");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setServiceImpl("service.impl");
packageConfig.setController("controller");
generator.setPackageInfo(packageConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude("user", "role"); // 表名
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("t_"); // 表前缀
generator.setStrategy(strategy);
// 执行生成
generator.execute();
}
}8.2 自定义模板
java
// 自定义模板配置
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setEntity("templates/entity.java");
templateConfig.setMapper("templates/mapper.java");
templateConfig.setService("templates/service.java");
templateConfig.setServiceImpl("templates/serviceImpl.java");
templateConfig.setController("templates/controller.java");
generator.setTemplate(templateConfig);9. 多数据源配置
9.1 依赖引入
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>9.2 配置多数据源
yaml
spring:
datasource:
dynamic:
primary: master # 设置默认的数据源或者数据源组
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver9.3 使用注解切换数据源
java
@Service
public class UserService {
@DS("master") // 使用主数据源
public void addUser(User user) {
userMapper.insert(user);
}
@DS("slave") // 使用从数据源
public User getUserById(Long id) {
return userMapper.selectById(id);
}
}10. 性能优化
10.1 SQL 性能分析插件
java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// SQL分析插件
interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());
return interceptor;
}10.2 分页优化
java
// 使用不查询总记录数的分页(大数据量时)
Page<User> page = new Page<>(1, 10, false);
// 自定义分页SQL(复杂查询时)
@Select("SELECT * FROM user ${ew.customSqlSegment}")
List<User> selectPageCustom(@Param(Constants.WRAPPER) Wrapper<User> wrapper, Page<User> page);10.3 查询优化
java
// 只查询需要的字段
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "name", "email");
// 使用Lambda避免字段名硬编码
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.select(User::getId, User::getName, User::getEmail);11. 常见问题
11.1 字段映射问题
java
// 解决数据库字段名与实体类属性名不一致
public class User {
@TableField("user_name") // 数据库字段名
private String name;
// 忽略非表字段
@TableField(exist = false)
private String tempField;
}11.2 主键策略问题
java
public class User {
// 雪花算法ID
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// 数据库自增
@TableId(type = IdType.AUTO)
private Long id;
}11.3 批量操作性能
java
// 批量插入
List<User> userList = new ArrayList<>();
// ... 添加数据
boolean success = userService.saveBatch(userList, 1000); // 每批1000条
// 批量更新
boolean success = userService.updateBatchById(userList, 1000);12. 最佳实践
12.1 项目结构
src/main/java/com/example/
├── entity/ # 实体类
├── mapper/ # Mapper接口
├── service/ # Service接口
│ └── impl/ # Service实现
├── controller/ # 控制器
└── config/ # 配置类12.2 统一返回结果
java
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
}12.3 全局异常处理
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
return Result.error(500, e.getMessage());
}
}13. 扩展功能
13.1 自定义注入方法
java
public interface MyBaseMapper<T> extends BaseMapper<T> {
int alwaysDeleteSome();
int deleteAll();
}
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
methodList.add(new DeleteAll());
methodList.add(new AlwaysDeleteSome());
return methodList;
}
}13.2 多租户支持
java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 从上下文中获取租户ID
return new LongValue(1L);
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 忽略不需要租户隔离的表
return "tenant".equals(tableName);
}
});
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}Db 静态工具类
1. 静态工具类 Db 介绍
MyBatis Plus 提供了 Db 静态工具类,其中包含与 IService 中方法签名基本一致的静态方法,可以帮助我们实现 CRUD 功能,同时避免 Service 之间的循环依赖问题。
主要优势:
- 避免循环依赖:无需注入 Service,直接通过静态方法操作数据库
- 使用便捷:提供 Lambda 查询和更新方式,代码简洁
- 功能丰富:包含常用的 CRUD 操作方法
2. 基本使用
2.1 根据 id 查询用户
java
@Test
public void testQueryById() {
User user = Db.getById(1L, User.class);
System.out.println(user);
}2.2 条件查询:名字包含"o"且余额 ≥1000 的用户
java
@Test
public void testQueryByName() {
List<User> userList = Db.lambdaQuery(User.class)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000)
.list();
userList.forEach(System.out::println);
}2.3 条件更新:更新用户名为"Rose"的余额为 2000
java
@Test
public void testUpdate() {
Db.lambdaUpdate(User.class)
.set(User::getBalance, 2000)
.eq(User::getUsername, "Rose")
.update();
}3. 实战案例:查询用户及收货地址
3.1 需求描述
改造根据 id 查询用户的接口,在查询用户的同时返回用户的收货地址列表。
3.2 实现步骤
步骤 1:导入 AddressVO
将 AddressVO.java 复制到 com.itheima.mp.domain.vo 包下:
java
package com.itheima.mp.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "收货地址VO")
public class AddressVO {
@ApiModelProperty("地址id")
private Long id;
@ApiModelProperty("用户id")
private Long userId;
@ApiModelProperty("省")
private String province;
@ApiModelProperty("市")
private String city;
@ApiModelProperty("区/县")
private String town;
@ApiModelProperty("详细地址")
private String street;
@ApiModelProperty("联系人")
private String contact;
@ApiModelProperty("手机号")
private String mobile;
@ApiModelProperty("是否是默认地址")
private Boolean isDefault;
}步骤 2:改造 UserVO
在 UserVO 中添加地址列表属性:
java
package com.itheima.mp.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(description = "用户VO")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private String info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private Integer status;
@ApiModelProperty("账户余额")
private Integer balance;
@ApiModelProperty("收货地址列表")
private List<AddressVO> addresses;
}步骤 3:修改 UserController
java
@RestController
@RequestMapping("/users")
@Api(tags = "用户管理接口")
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;
/**
* 根据用户id查询用户
* @param id 用户id
*/
@ApiOperation("根据用户id查询用户")
@GetMapping("/{id}")
public UserVO queryById(@PathVariable("id") Long id) {
return userService.queryUserAndAddressById(id);
}
}步骤 4:新增 IUserService 接口方法
java
public interface IUserService extends IService<User> {
/**
* 根据用户id查询用户和地址
* @param id 用户id
* @return userVO
*/
UserVO queryUserAndAddressById(Long id);
}步骤 5:实现 UserServiceImpl
使用 Db 工具类查询收货地址,避免循环依赖:
java
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public UserVO queryUserAndAddressById(Long id) {
// 查询用户
User user = getById(id);
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
// 使用Db工具类查询用户的收货地址列表,避免循环依赖
List<Address> addressList = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id)
.list();
userVO.setAddresses(BeanUtil.copyToList(addressList, AddressVO.class));
return userVO;
}
}4. 扩展练习:批量查询用户及地址
需求:改造根据用户 id 批量查询用户并返回用户的收货地址列表
java
@Override
public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
// 1. 查询用户列表
List<User> userList = this.listByIds(ids);
List<UserVO> userVOS = BeanUtil.copyToList(userList, UserVO.class);
// 2. 使用Db工具类查询地址列表,并按用户id分组
List<Address> addressList = Db.lambdaQuery(Address.class)
.in(Address::getUserId, ids)
.list();
Map<Long, List<AddressVO>> addressVoMap = BeanUtil.copyToList(addressList, AddressVO.class)
.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
// 3. 将地址列表设置到对应的用户VO中
for (UserVO userVO : userVOS) {
userVO.setAddresses(addressVoMap.get(userVO.getId()));
}
return userVOS;
}5. 注意事项
- Mapper 要求:使用
Db工具类时,必须有对应实体类的 Mapper - 事务管理:
Db操作默认在事务中执行 - 性能考虑:对于复杂查询,建议使用自定义 Mapper 方法
- 代码简洁性:简单的单表操作使用
Db,复杂业务逻辑使用 Service