软件工程实践四:MyBatis-Plus 教程(连接、分页、查询)

文章目录

    • 概述
    • [1. 依赖与连接配置](#1. 依赖与连接配置)
      • [1.1 Maven 依赖(片段)](#1.1 Maven 依赖(片段))
      • [1.2 application.yml 连接](#1.2 application.yml 连接)
      • [1.3 启动类与包扫描](#1.3 启动类与包扫描)
      • [1.4 分页插件配置](#1.4 分页插件配置)
    • [2. 示例模型与 Mapper](#2. 示例模型与 Mapper)
      • [2.1 表结构(示例)](#2.1 表结构(示例))
      • [2.2 实体 Entity](#2.2 实体 Entity)
      • [2.3 Mapper 接口](#2.3 Mapper 接口)
    • [3. 常见查询(QueryWrapper / Lambda)](#3. 常见查询(QueryWrapper / Lambda))
      • [3.1 基础查询](#3.1 基础查询)
      • [3.2 LambdaQueryWrapper(更安全的字段引用)](#3.2 LambdaQueryWrapper(更安全的字段引用))
      • [3.3 按条件可选拼装](#3.3 按条件可选拼装)
    • [4. 分页查询(Page)](#4. 分页查询(Page))
    • [5. JOIN(连接)查询的常见做法](#5. JOIN(连接)查询的常见做法)
      • [5.1 一对一(User ↔ UserProfile)](#5.1 一对一(User ↔ UserProfile))
      • [5.2 一对多(User ↔ Orders)](#5.2 一对多(User ↔ Orders))
      • [5.3 多对多(User ↔ Role,经 user_role 关联)](#5.3 多对多(User ↔ Role,经 user_role 关联))
    • [6. Service 层(IService / ServiceImpl)](#6. Service 层(IService / ServiceImpl))
    • [7. Controller 层(CRUD + 分页)](#7. Controller 层(CRUD + 分页))
    • [8. 实用补充与优化建议](#8. 实用补充与优化建议)
    • [9. 小结](#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 依赖(片段)

xml 复制代码
<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 连接

yaml 复制代码
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  mapper-locations: classpath*:mapper/**/*.xml

1.3 启动类与包扫描

java 复制代码
@SpringBootApplication
@MapperScan("com.example.demo.mapper") // 若在接口上使用 @Mapper,可不加 @MapperScan
public class DemoApplication {
  public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
}

1.4 分页插件配置

java 复制代码
@Configuration
public class MybatisPlusConfig {
  @Bean
  public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
  }
}

2. 示例模型与 Mapper

2.1 表结构(示例)

sql 复制代码
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

java 复制代码
@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 接口

java 复制代码
public interface UserMapper extends BaseMapper<User> {}

3. 常见查询(QueryWrapper / Lambda)

3.1 基础查询

java 复制代码
@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(更安全的字段引用)

java 复制代码
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 按条件可选拼装

java 复制代码
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)。

java 复制代码
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 中直接返回分页结果:

java 复制代码
@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)

sql 复制代码
CREATE TABLE `user_profile` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `user_id` BIGINT NOT NULL UNIQUE,
  `phone` VARCHAR(32),
  `address` VARCHAR(255)
);
  • DTO
java 复制代码
@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 接口
java 复制代码
public interface UserProfileQueryMapper {
  UserWithProfileDTO selectUserWithProfile(@Param("userId") Long userId);
}
  • XML(resources/mapper/UserProfileQueryMapper.xml)
xml 复制代码
<?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.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">
    SELECT
      u.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_address
    FROM t_user u
    LEFT JOIN user_profile p ON p.user_id = u.id
    WHERE u.id = #{userId}
  </select>
</mapper>

5.2 一对多(User ↔ Orders)

下例演示嵌套集合映射;若只需扁平结果,可参考 5.1/5.3 的 DTO 方式。

sql 复制代码
CREATE TABLE `order` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `user_id` BIGINT NOT NULL,
  `amount` DECIMAL(6,2)
);
  • DTO
java 复制代码
@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 接口
java 复制代码
public interface UserOrderNestedMapper {
  UserWithOrdersDTO selectUsersWithOrders(@Param("userId") Long userId);
}
  • XML(resources/mapper/UserOrderNestedMapper.xml)
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">
    SELECT
      u.id AS u_id, u.name AS u_name,
      o.id AS o_id, o.amount AS o_amount
    FROM t_user u
    LEFT 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 关联)

sql 复制代码
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
java 复制代码
@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 接口
java 复制代码
public interface UserRoleMapper {
  List<UserWithRolesDTO> selectUsersWithRoles(@Param("userIds") List<Long> userIds);
}
  • XML(resources/mapper/UserRoleMapper.xml)
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">
    SELECT
      u.id AS u_id, u.name AS u_name,
      r.id AS r_id, r.name AS r_name
    FROM user u
    LEFT JOIN user_role ur ON ur.user_id = u.id
    LEFT JOIN role r ON r.id = ur.role_id
    <where>
      <if test="userIds != null and userIds.size &gt; 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 能力并封装业务逻辑。

java 复制代码
public interface UserService extends IService<User> {
  Page<User> pageUsers(int page, int size, Integer minAge);
}
java 复制代码
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
  @Override
  public 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,进行参数校验与异常转换。

java 复制代码
@RestController
@RequestMapping("/api/users")
public class UserController {

  private final UserService userService;
  public UserController(UserService userService) { this.userService = userService; }

  @GetMapping
  public 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);
  }

  @PostMapping
  public 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
  • 统一异常处理
    • 通过 @RestControllerAdvice + @ExceptionHandler 统一返回错误结构(包含 code/message)。
  • DTO 与实体分离
    • 建议 Controller 使用请求/响应 DTO,Service 处理实体,避免直接暴露数据库结构。
  • 分页与排序安全
    • 对前端传入的排序字段做白名单;分页 size 设置上限。
  • 事务管理
    • 在需要的 Service 方法上使用 @Transactional,避免在 Controller 层开启事务。

9. 小结

  • 连接:配置数据源 + @MapperScan
  • 分页:注册 MybatisPlusInterceptorPaginationInnerInterceptor,使用 Page<T>
  • Service:基于 IService/ServiceImpl 封装业务;
  • Controller:提供 RESTful CRUD 与分页,配合校验与异常处理;
  • 查询:QueryWrapper/LambdaQueryWrapper 完成单表条件,JOIN 用自定义 SQL(注解/XML)。

作者:xuan

个人博客:https://blog.ybyq.wang

欢迎访问我的博客,获取更多技术文章和教程。

相关推荐
小枫编程2 小时前
Spring Boot 与前端文件上传跨域问题:Multipart、CORS 与网关配置
前端·spring boot·后端
一又四分之一.2 小时前
spring、springboot、springCloud
spring boot·spring·spring cloud
王维志3 小时前
LiteDB详解
数据库·后端·mongodb·sqlite·c#·json·database
wuyunhang1234563 小时前
Redis----缓存策略和注意事项
redis·缓存·mybatis
玉衡子3 小时前
七、InnoDB底层原理与日志机制
java·mysql
cyforkk3 小时前
MySQL 唯一约束:从基础到实战,解决数据重复的核心工具
数据库·mysql
小枫编程4 小时前
Spring Boot 调度任务在分布式环境下的坑:任务重复执行与一致性保证
spring boot·分布式·后端
Java水解4 小时前
spring中的@SpringBootTest注解详解
spring boot·后端
快乐肚皮4 小时前
SQL调优全攻略:从原理到实战
mysql