软件工程实践四: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

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

相关推荐
CS Beginner21 小时前
【搭建】个人博客网站的搭建
java·前端·学习·servlet·log4j·mybatis
JavaTree201721 小时前
【MySQL】mysqldump使用方法
数据库·mysql
HC02201 天前
【保姆级教程】MySQL 5.7 彻底卸载与重新安装全流程(附常见问题解决)
数据库·mysql·mysql5.7·数据库安装·彻底卸载 mysql·mysql 配置教程
lkforce1 天前
mysql表连接,因类型不匹配而导致索引失效的场景
数据库·mysql
超人小子1 天前
mysql重置密码
数据库·mysql·adb
普通网友1 天前
【mysql】锁机制 - 2.行锁间隙锁临键锁
数据库·mysql
zwtahql1 天前
mysql的安装和卸载过程
数据库·mysql
Siren_dream1 天前
在Ubuntu通过命令行安装MySQL(tabby远程)
mysql·ubuntu
李慕婉学姐1 天前
Springboot旅游管理系统8cx8xy5m(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·旅游
JavaTree20171 天前
【Spring Boot】Spring Boot解决循环依赖
java·spring boot·后端