软件工程实践四:MyBatis-Plus 教程(连接、分页、查询)
文章目录
- 概述
- 1. 依赖与连接配置
- 1.1 Maven 依赖(片段)
- 1.2 application.yml 连接
- 1.3 启动类与包扫描
- 1.4 分页插件配置
- 2. 示例模型与 Mapper
- 2.1 表结构(示例)
- 2.2 实体 Entity
- 2.3 Mapper 接口
- 3. 常见查询(QueryWrapper / Lambda)
- 3.1 基础查询
- 3.2 LambdaQueryWrapper(更安全的字段引用)
- 3.3 按条件可选拼装
- 4. 分页查询(Page)
- 5. JOIN(连接)查询的常见做法
- 5.1 一对一(User ↔ UserProfile)
- 5.2 一对多(User ↔ Orders)
- 5.3 多对多(User ↔ Role,经 user_role 关联)
- 6. Service 层(IService / ServiceImpl)
- 7. Controller 层(CRUD + 分页)
- 8. 实用补充与优化建议
- 9. 小结
概述
- 目标:用最少代码完成单表 CRUD、分页查询,并给出 JOIN 查询的常见做法。
- 环境:Spring Boot 3.x + MyBatis-Plus 3.5+ + MySQL 8.x。
原文链接:https://blog.ybyq.wang/archives/1104.html
1. 依赖与连接配置
1.1 Maven 依赖(片段)
<dependencies><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.12</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.0.33</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
</dependencies>
1.2 application.yml 连接
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTCusername: rootpassword: root
mybatis-plus:configuration:map-underscore-to-camel-case: truemapper-locations: classpath*:mapper/**/*.xml
1.3 启动类与包扫描
@SpringBootApplication
@MapperScan("com.example.demo.mapper") // 若在接口上使用 @Mapper,可不加 @MapperScan
public class DemoApplication {public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
}
1.4 分页插件配置
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}
2. 示例模型与 Mapper
2.1 表结构(示例)
CREATE TABLE `t_user` (`id` BIGINT PRIMARY KEY AUTO_INCREMENT,`name` VARCHAR(64) NOT NULL,`age` INT NOT NULL,`email` VARCHAR(128),`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
2.2 实体 Entity
@Data
@TableName("t_user")
public class User {@TableId(type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;@TableField("created_at")private LocalDateTime createdAt;
}
2.3 Mapper 接口
public interface UserMapper extends BaseMapper<User> {}
3. 常见查询(QueryWrapper / Lambda)
3.1 基础查询
@Autowired private UserMapper userMapper;// 主键
User u1 = userMapper.selectById(1L);// 批量主键
List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L));// 条件查询(QueryWrapper)
QueryWrapper<User> qw = new QueryWrapper<>();
qw.select("id", "name", "age").eq("age", 20).likeRight("name", "A").orderByDesc("id");
List<User> list = userMapper.selectList(qw);// 计数
Long count = userMapper.selectCount(new QueryWrapper<User>().ge("age", 18));
3.2 LambdaQueryWrapper(更安全的字段引用)
LambdaQueryWrapper<User> lw = Wrappers.lambdaQuery(User.class).select(User::getId, User::getName).between(User::getAge, 18, 30).like(User::getName, "Tom").orderByAsc(User::getCreatedAt);
List<User> list2 = userMapper.selectList(lw);
3.3 按条件可选拼装
String name = req.getName();
Integer minAge = req.getMinAge();LambdaQueryWrapper<User> w = Wrappers.lambdaQuery(User.class).like(StringUtils.hasText(name), User::getName, name).ge(minAge != null, User::getAge, minAge);
List<User> result = userMapper.selectList(w);
4. 分页查询(Page)
需先注册分页插件(见 1.4)。
int pageNo = 1, pageSize = 10;
Page<User> page = new Page<>(pageNo, pageSize);
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class).ge(User::getAge, 18).orderByDesc(User::getId);Page<User> resultPage = userMapper.selectPage(page, wrapper);
List<User> records = resultPage.getRecords();
long total = resultPage.getTotal();
long pages = resultPage.getPages();
可在 Controller 中直接返回分页结果:
@GetMapping("/users")
public Page<User> list(@RequestParam int page, @RequestParam int size,@RequestParam(required = false) Integer minAge) {Page<User> p = new Page<>(page, size);LambdaQueryWrapper<User> w = Wrappers.lambdaQuery(User.class).ge(minAge != null, User::getAge, minAge).orderByDesc(User::getId);return userMapper.selectPage(p, w);
}
5. JOIN(连接)查询的常见做法
MyBatis-Plus 聚焦单表 CRUD;多表 JOIN 推荐使用自定义 SQL(注解或 XML)。
5.1 一对一(User ↔ UserProfile)
- 表
CREATE TABLE `user_profile` (`id` BIGINT PRIMARY KEY AUTO_INCREMENT,`user_id` BIGINT NOT NULL UNIQUE,`phone` VARCHAR(32),`address` VARCHAR(255)
);
- DTO
@Data
public class UserProfileDTO {private Long id;private Long userId;private String phone;private String address;
}@Data
public class UserWithProfileDTO {private Long id;private String name;private Integer age;private String email;private UserProfileDTO profile;
}
- Mapper 接口
public interface UserProfileQueryMapper {UserWithProfileDTO selectUserWithProfile(@Param("userId") Long userId);
}
- XML(resources/mapper/UserProfileQueryMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserProfileQueryMapper"><resultMap id="UserProfileMap" type="com.example.demo.dto.UserProfileDTO"><id property="id" column="p_id"/><result property="userId" column="p_user_id"/><result property="phone" column="p_phone"/><result property="address" column="p_address"/></resultMap><resultMap id="UserWithProfileMap" type="com.example.demo.dto.UserWithProfileDTO"><id property="id" column="u_id"/><result property="name" column="u_name"/><result property="age" column="u_age"/><result property="email" column="u_email"/><association property="profile" resultMap="UserProfileMap"/></resultMap><select id="selectUserWithProfile" resultMap="UserWithProfileMap">SELECTu.id AS u_id, u.name AS u_name, u.age AS u_age, u.email AS u_email,p.id AS p_id, p.user_id AS p_user_id, p.phone AS p_phone, p.address AS p_addressFROM t_user uLEFT JOIN user_profile p ON p.user_id = u.idWHERE u.id = #{userId}</select>
</mapper>
5.2 一对多(User ↔ Orders)
下例演示嵌套集合映射;若只需扁平结果,可参考 5.1/5.3 的 DTO 方式。
- 表
CREATE TABLE `order` (`id` BIGINT PRIMARY KEY AUTO_INCREMENT,`user_id` BIGINT NOT NULL,`amount` DECIMAL(6,2)
);
- DTO
@Data
public class OrderDTO {private Long id;private Long userId;private BigDecimal amount;
}@Data
public class UserWithOrdersDTO {private Long id;private String name;private List<OrderDTO> orders;
}
- Mapper 接口
public interface UserOrderNestedMapper {UserWithOrdersDTO selectUsersWithOrders(@Param("userId") Long userId);
}
- XML(resources/mapper/UserOrderNestedMapper.xml)
<mapper namespace="com.example.demo.mapper.UserOrderNestedMapper"><resultMap id="OrderMap" type="com.example.demo.dto.OrderDTO"><id property="id" column="o_id"/><result property="amount" column="o_amount"/></resultMap><resultMap id="UserWithOrdersMap" type="com.example.demo.dto.UserWithOrdersDTO"><id property="id" column="u_id"/><result property="name" column="u_name"/><collection property="orders" ofType="com.example.demo.dto.OrderDTO" resultMap="OrderMap"/></resultMap><select id="selectUsersWithOrders" resultMap="UserWithOrdersMap">SELECTu.id AS u_id, u.name AS u_name,o.id AS o_id, o.amount AS o_amountFROM t_user uLEFT JOIN `order` o ON o.user_id = u.id<where>u.id = #{userId}</where>ORDER BY u.id DESC, o.id DESC</select>
</mapper>
5.3 多对多(User ↔ Role,经 user_role 关联)
- 表
CREATE TABLE `role` (`id` BIGINT PRIMARY KEY AUTO_INCREMENT,`name` VARCHAR(64) NOT NULL
);CREATE TABLE `user_role` (`user_id` BIGINT NOT NULL,`role_id` BIGINT NOT NULL,PRIMARY KEY (`user_id`, `role_id`)
);
- DTO
@Data
public class RoleDTO {private Long id;private String name;
}@Data
public class UserWithRolesDTO {private Long id;private String name;private List<RoleDTO> roles;
}
- Mapper 接口
public interface UserRoleMapper {List<UserWithRolesDTO> selectUsersWithRoles(@Param("userIds") List<Long> userIds);
}
- XML(resources/mapper/UserRoleMapper.xml)
<mapper namespace="com.example.demo.mapper.UserRoleMapper"><resultMap id="RoleMap" type="com.example.demo.dto.RoleDTO"><id property="id" column="r_id"/><result property="name" column="r_name"/></resultMap><resultMap id="UserWithRolesMap" type="com.example.demo.dto.UserWithRolesDTO"><id property="id" column="u_id"/><result property="name" column="u_name"/><collection property="roles" ofType="com.example.demo.dto.RoleDTO" resultMap="RoleMap"/></resultMap><select id="selectUsersWithRoles" resultMap="UserWithRolesMap">SELECTu.id AS u_id, u.name AS u_name,r.id AS r_id, r.name AS r_nameFROM user uLEFT JOIN user_role ur ON ur.user_id = u.idLEFT JOIN role r ON r.id = ur.role_id<where><if test="userIds != null and userIds.size > 0">u.id IN<foreach collection="userIds" item="id" open="(" close=")" separator=",">#{id}</foreach></if></where>ORDER BY u.id DESC, r.id DESC</select>
</mapper>
小贴士:嵌套集合映射时,为每个表字段起唯一别名(如 u_id/o_id/r_id
),避免列名冲突导致映射失败。
6. Service 层(IService / ServiceImpl)
通过继承通用接口/实现类,快速获得 CRUD 能力并封装业务逻辑。
public interface UserService extends IService<User> {Page<User> pageUsers(int page, int size, Integer minAge);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Overridepublic Page<User> pageUsers(int page, int size, Integer minAge) {Page<User> p = new Page<>(page, size);LambdaQueryWrapper<User> w = Wrappers.lambdaQuery(User.class).ge(minAge != null, User::getAge, minAge).orderByDesc(User::getId);return this.page(p, w);}
}
7. Controller 层(CRUD + 分页)
控制器依赖 Service,进行参数校验与异常转换。
@RestController
@RequestMapping("/api/users")
public class UserController {private final UserService userService;public UserController(UserService userService) { this.userService = userService; }@GetMappingpublic Page<User> list(@RequestParam int page, @RequestParam int size,@RequestParam(required = false) Integer minAge) {return userService.pageUsers(page, size, minAge);}@GetMapping("/{id}")public User detail(@PathVariable Long id) {return userService.getById(id);}@PostMappingpublic User create(@Valid @RequestBody User body) {userService.save(body);return body;}@PutMapping("/{id}")public User update(@PathVariable Long id, @Valid @RequestBody User body) {body.setId(id);userService.updateById(body);return body;}@DeleteMapping("/{id}")public void remove(@PathVariable Long id) {userService.removeById(id);}
}
8. 实用补充与优化建议
- 参数校验(Jakarta Validation)
- 在实体/DTO 上使用
@NotBlank/@NotNull/@Email/@Min
等注解;控制器方法参数加@Valid
。
- 在实体/DTO 上使用
- 统一异常处理
- 通过
@RestControllerAdvice
+@ExceptionHandler
统一返回错误结构(包含code/message
)。
- 通过
- DTO 与实体分离
- 建议 Controller 使用请求/响应 DTO,Service 处理实体,避免直接暴露数据库结构。
- 分页与排序安全
- 对前端传入的排序字段做白名单;分页
size
设置上限。
- 对前端传入的排序字段做白名单;分页
- 事务管理
- 在需要的 Service 方法上使用
@Transactional
,避免在 Controller 层开启事务。
- 在需要的 Service 方法上使用
9. 小结
- 连接:配置数据源 +
@MapperScan
; - 分页:注册
MybatisPlusInterceptor
的PaginationInnerInterceptor
,使用Page<T>
; - Service:基于
IService/ServiceImpl
封装业务; - Controller:提供 RESTful CRUD 与分页,配合校验与异常处理;
- 查询:
QueryWrapper
/LambdaQueryWrapper
完成单表条件,JOIN 用自定义 SQL(注解/XML)。
作者:xuan
个人博客:https://blog.ybyq.wang
欢迎访问我的博客,获取更多技术文章和教程。