概述

  • 目标:用最少代码完成单表 CRUD、分页查询,并给出 JOIN 查询的常见做法。
  • 环境:Spring Boot 3.x + MyBatis-Plus 3.5+ + MySQL 8.x。

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.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 启动类与包扫描

@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 {
  @Bean
  public 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 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 方式。
  • 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">
      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 关联)

  • 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">
      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 能力并封装业务逻辑。
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 {
  @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,进行参数校验与异常转换。
@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)。
最后修改:2025 年 09 月 11 日
如果觉得我的文章对你有用,请随意赞赏
END
本文作者:
文章标题:软件工程实践四:MyBatis-Plus 教程(连接、分页、查询)
本文地址:https://blog.ybyq.wang/archives/1104.html
版权说明:若无注明,本文皆Xuan's blog原创,转载请保留文章出处。