Spring Boot + MyBatis Plus JOIN 分页多表查询项目文档

Spring Boot + MyBatis Plus 分页多表查询项目文档

项目概述

本项目是一个基于Spring Boot + MyBatis Plus的分页多表查询示例项目,演示了单表分页查询和多表连接查询的功能。项目使用了MyBatis Plus及其扩展库MyBatis Plus Join,以及Hutool工具库和Knife4j API文档工具。

技术栈

  • Spring Boot 2.7.18
  • MyBatis Plus 3.5.14
  • MyBatis Plus Join 1.5.5
  • MySQL 8.0.33
  • Hutool 5.8.23
  • Lombok
  • Knife4j 4.4.0

项目结构

复制代码
springboot-mp-demo/
├── pom.xml
├── REMAND.md
├── sql/
│   └── db.sql
└── src/
    └── main/
        ├── java/
        │   └── com/
        │       └── example/
        │           └── demo/
        │               ├── SpringbootMpDemoApplication.java
        │               ├── config/
        │               │   ├── MyBatisPlusConfig.java
        │               │   ├── SqlPrintInnerInterceptor.java
        │               │   └── SwaggerConfig.java
        │               ├── controller/
        │               │   └── UserController.java
        │               ├── entity/
        │               │   ├── Order.java
        │               │   └── User.java
        │               ├── mapper/
        │               │   ├── OrderMapper.java
        │               │   └── UserMapper.java
        │               ├── request/
        │               │   └── UserRequest.java
        │               ├── response/
        │               │   └── UserResponse.java
        │               ├── service/
        │               │   ├── UserService.java
        │               │   └── impl/
        │               │       └── UserServiceImpl.java
        │               └── vo/
        │                   └── UserOrderVO.java
        └── resources/
            └── application.yml

项目配置文件

pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-mp-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-mp-demo</name>
    <description>Spring Boot + MyBatis Plus 分页多表查询样例</description>

    <properties>
        <java.version>1.8</java.version>
        <mybatis-plus.version>3.5.14</mybatis-plus.version>
        <mybatis-plus-join.version>1.5.5</mybatis-plus-join.version>
        <hutool.version>5.8.23</hutool.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Web核心 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- MySQL驱动(适配8.0) -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
            <scope>runtime</scope>
        </dependency>

        <!-- jdk 8+ 引入可选模块 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
            <version>3.5.14</version>
        </dependency>

        <!-- MyBatis Plus 核心 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- MyBatis Plus Join(多表连接) -->
        <dependency>
            <groupId>com.github.yulichang</groupId>
            <artifactId>mybatis-plus-join-boot-starter</artifactId>
            <version>${mybatis-plus-join.version}</version>
        </dependency>

        <!-- Hutool 工具库 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

        <!-- Lombok(简化实体类) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
            <version>4.4.0</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

yaml 复制代码
server:
  port: 8080

spring:
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root # 替换为你的MySQL用户名
    password: 123456 # 替换为你的MySQL密码

# MyBatis Plus 配置
mybatis-plus:
  # Mapper.xml文件路径(如果需要)
  mapper-locations: classpath:mapper/**/*.xml
  # 实体类别名包
  type-aliases-package: com.example.demo.entity
  configuration:
    # 开启驼峰命名自动转换
    map-underscore-to-camel-case: true

# Knife4j API文档配置
knife4j:
  enable: true
  setting:
    language: zh-CN
  production: false
  basic:
    enable: false
    username: admin
    password: 123456

主启动类

SpringbootMpDemoApplication.java

java 复制代码
package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;

/**
 * 项目启动类
 * @MapperScan:扫描Mapper接口所在包
 */
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class SpringbootMpDemoApplication {

    public static void main(String[] args) {
        // 启动Spring Boot应用,并获取应用上下文
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootMpDemoApplication.class, args);

        // 从上下文获取环境配置(包含端口、IP等配置)
        Environment env = context.getEnvironment();

        // 1. 获取项目端口(优先读取配置的server.port,默认8080)
        String port = env.getProperty("server.port", "8080");

        // 2. 获取本机真实IP(排除127.0.0.1,适配多网卡场景)
        String ip = getLocalIpAddress();

        // 3. 打印完整的访问地址
        System.out.println("=========================================================");
        System.out.println("项目启动成功!🎉 接口文档访问地址:http://" + ip + ":" + port+"/doc.html");
        System.out.println("=========================================================");
    }

    /**
     * 获取本机真实的IPv4地址(非回环地址,适配多网卡)
     * @return 本机IP地址,异常时返回127.0.0.1
     */
    private static String getLocalIpAddress() {
        try {
            // 遍历所有网络接口
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface ni = interfaces.nextElement();
                Enumeration<InetAddress> addresses = ni.getInetAddresses();

                // 遍历当前网卡的所有IP地址
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    // 过滤条件:非回环地址(排除127.0.0.1)+ 是IPv4地址(排除IPv6)
                    if (!addr.isLoopbackAddress() && addr.getHostAddress().indexOf(":") == -1) {
                        return addr.getHostAddress();
                    }
                }
            }
            // 如果没找到有效IP,返回本机localhost的IP
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            return "127.0.0.1";
        } catch (Exception e) {
            return "127.0.0.1";
        }
    }

}

实体类

User.java

java 复制代码
package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;

/**
 * 用户实体(对应user表)
 */
@Data
@TableName("mp_user") // 指定数据库表名
@ApiModel(value = "User", description = "用户实体")
public class User {
    /**
     * 主键ID
     */
    @TableId(type=IdType.AUTO)
    @ApiModelProperty(value = "主键ID")
    private Long id;
    
    /**
     * 用户名
     */
    @ApiModelProperty(value = "用户名")
    private String username;
    
    /**
     * 年龄
     */
    @ApiModelProperty(value = "年龄")
    private Integer age;
    
    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;

    /**
     * 扩展字段(JSON格式)
     */
    @ApiModelProperty(value = "扩展字段(JSON格式)")
    private String extJson;

}

Order.java

java 复制代码
package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单实体(对应order表)
 */
@Data
@TableName("mp_order") // 指定数据库表名
public class Order {
    /**
     * 主键ID
     */
    private Long id;
    /**
     * 订单编号
     */
    private String orderNo;
    /**
     * 关联用户ID
     */
    private Long userId;
    /**
     * 订单金额
     */
    private BigDecimal amount;
    /**
     * 创建时间
     */
    private LocalDateTime createTime;
}

Mapper接口

UserMapper.java

java 复制代码
package com.example.demo.mapper;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import com.example.demo.entity.User;
import com.example.demo.vo.UserOrderVO;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Param;

/**
 * 用户Mapper(继承MPJBaseMapper支持多表连接)
 */
public interface UserMapper extends MPJBaseMapper<User> {

}

OrderMapper.java

java 复制代码
package com.example.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.Order;
import com.github.yulichang.base.MPJBaseMapper;

/**
 * 订单Mapper(基础MyBatis Plus Mapper)
 */
public interface OrderMapper extends MPJBaseMapper<Order> {
}

Service层

UserService.java

java 复制代码
package com.example.demo.service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.User;
import com.example.demo.vo.UserOrderVO;

/**
 * 用户Service接口
 */
public interface UserService extends IService<User> {

    /**
     * 单表分页查询用户
     * @param pageNum 页码
     * @param pageSize 页大小
     * @param age 年龄(可选条件)
     * @return 分页结果
     */
    IPage<User> getUserPage(Integer pageNum, Integer pageSize, Integer age);

    /**
     * 多表连接分页查询(用户+订单)
     * @param pageNum 页码
     * @param pageSize 页大小
     * @param username 用户名(可选条件)
     * @return 分页结果
     */
    IPage<UserOrderVO> getUserOrderPage(Integer pageNum, Integer pageSize, String username);
}

UserServiceImpl.java

java 复制代码
package com.example.demo.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.Order;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import com.example.demo.vo.UserOrderVO;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 用户Service实现类
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 单表分页查询用户
     */
    @Override
    public IPage<User> getUserPage(Integer pageNum, Integer pageSize, Integer age) {
        // 1. 构建分页对象
        Page<User> page = new Page<>(pageNum, pageSize);
        // 2. 构建查询条件
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        if (age != null) {
            queryWrapper.ge(User::getAge, age); // 年龄大于等于指定值
        }
        // 3. 分页查询(MyBatis Plus内置分页方法)
        return userMapper.selectPage(page, queryWrapper);
    }

    /**
     * 多表连接分页查询(用户+订单)
     * 使用MyBatis Plus Join实现多表连接
     */
    @Override
    public IPage<UserOrderVO> getUserOrderPage(Integer pageNum, Integer pageSize, String username) {
        // 1. 构建分页对象
        Page<UserOrderVO> page = new Page<>(pageNum, pageSize);

        // 2. 构建多表连接查询条件(MPJ核心用法)
        MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<User>()
                // 主表:user
                .selectAll(User.class) // 查询user表所有字段
                .leftJoin(Order.class, Order::getUserId, User::getId)
                // 自定义查询字段(指定别名,匹配VO的字段)
                .selectAs(User::getId, UserOrderVO::getUserId)
                .selectAs(Order::getId, UserOrderVO::getOrderId)
                .select(Order::getOrderNo)
                .select(Order::getAmount)
                .selectAs(Order::getCreateTime, UserOrderVO::getOrderCreateTime)
                // 条件:用户名模糊查询(Hutool工具类判空)
                .like(StrUtil.isNotBlank(username), User::getUsername, username)
                .orderByDesc(Order::getCreateTime);

        // 3. 多表分页查询
        return userMapper.selectJoinPage(page, UserOrderVO.class, wrapper);
    }
}

Controller层

UserController.java

java 复制代码
package com.example.demo.controller;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.demo.entity.User;
import com.example.demo.request.UserRequest;
import com.example.demo.response.UserResponse;
import com.example.demo.service.UserService;
import com.example.demo.vo.UserOrderVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.HashMap;

/**
 * User API controller
 */
@RestController
@Api(tags = "用户管理")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * Single table pagination query user interface
     * @param pageNum Page number (default 1)
     * @param pageSize Page size (default 10)
     * @param age Age (optional)
     * @return Pagination result
     */
    @GetMapping("/user/page")
    public IPage<User> getUserPage(
            @RequestParam(defaultValue = "1", name = "pageNum") @ApiParam(value = "Page number", defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10", name = "pageSize") @ApiParam(value = "Page size", defaultValue = "10") Integer pageSize,
            @RequestParam(required = false) @ApiParam(value = "Age") Integer age) {
        return userService.getUserPage(pageNum, pageSize, age);
    }

    /**
     * Multi-table join pagination query interface (User + Order)
     * @param pageNum Page number (default 1)
     * @param pageSize Page size (default 10)
     * @param username Username (optional)
     * @return Pagination result
     */
    @GetMapping("/user/joinPage")
    @ApiOperation(value = "联合分页查询", notes = "联合分页查询")
    public IPage<UserOrderVO> getUserOrderPage(
            @RequestParam(defaultValue = "1", name = "pageNum") @ApiParam(value = "Page number", defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10", name = "pageSize") @ApiParam(value = "Page size", defaultValue = "10") Integer pageSize,
            @RequestParam(required = false) @ApiParam(value = "Username") String username) {
        return userService.getUserOrderPage(pageNum, pageSize, username);
    }

    @PostMapping("/user/add")
    @ApiOperation(value = "新增用户", notes = "新增用户")
    public void addUser(@RequestBody @ApiParam(value = "User information") UserRequest request) {
        User user = new User();
        BeanUtils.copyProperties(request, user);
        if(request.getExt() != null && !request.getExt().isEmpty()){
            user.setExtJson(JSONUtil.toJsonStr(request.getExt()));
        }
        userService.save(user);
    }

    @GetMapping("/user/detail")
    @ApiOperation(value = "用户详情", notes = "用户详情")
    public UserResponse getUserDetail(@RequestParam Long id) {
        User user = userService.getById(id);
        UserResponse response = new UserResponse();
        BeanUtils.copyProperties(user, response);
        if(StrUtil.isNotEmpty(user.getExtJson())){
            response.setExt(JSONUtil.toBean(user.getExtJson(), Map.class));
        }
        return response;
    }
}

VO、Request和Response类

UserOrderVO.java

java 复制代码
package com.example.demo.vo;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 用户+订单 多表查询结果VO
 */
@Data
public class UserOrderVO {
    /**
     * 用户ID
     */
    @ApiModelProperty(value = "用户ID")
    private Long userId;
    /**
     * 用户名
     */
    @ApiModelProperty(value = "用户名")
    private String username;
    /**
     * 年龄
     */
    @ApiModelProperty(value = "年龄")
    private Integer age;
    /**
     * 订单ID
     */
    @ApiModelProperty(value = "订单ID")
    private Long orderId;
    /**
     * 订单编号
     */
    @ApiModelProperty(value = "订单编号")
    private String orderNo;
    /**
     * 订单金额
     */
    @ApiModelProperty(value = "订单金额")
    private BigDecimal amount;
    /**
     * 订单创建时间
     */
    @ApiModelProperty(value = "订单创建时间")
    private LocalDateTime orderCreateTime;
}

UserRequest.java

java 复制代码
package com.example.demo.request;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.time.LocalDateTime;
import java.util.Map;

@Data
@ApiModel(value = "UserRequest", description = "用户请求对象")
public class UserRequest {

    /**
     * 用户名
     */
    @ApiModelProperty(value = "用户名")
    private String username;
    
    /**
     * 年龄
     */
    @ApiModelProperty(value = "年龄")
    private Integer age;
    
    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;

    /**
     * 扩展字段
     */
    @ApiModelProperty(value = "扩展字段")
    private Map<Object,Object> ext;

}

UserResponse.java

java 复制代码
package com.example.demo.response;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.time.LocalDateTime;
import java.util.Map;

@Data
@ApiModel(value = "UserResponse", description = "用户响应对象")
public class UserResponse {

    /**
     * 主键ID
     */
    @ApiModelProperty(value = "主键ID")
    private Long id;
    
    /**
     * 用户名
     */
    @ApiModelProperty(value = "用户名")
    private String username;
    
    /**
     * 年龄
     */
    @ApiModelProperty(value = "年龄")
    private Integer age;
    
    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;

    /**
     * 扩展字段(JSON格式)
     */
    @ApiModelProperty(value = "扩展字段(JSON格式)")
    private Map<Object,Object> ext;

}

配置类

MyBatisPlusConfig.java

java 复制代码
package com.example.demo.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MyBatis Plus 配置类(核心:分页插件)
 */
@Configuration
public class MyBatisPlusConfig {

    /**
     * 分页插件(必须配置,否则分页查询无效)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 适配MySQL的分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 添加自定义SQL打印插件
        interceptor.addInnerInterceptor(new SqlPrintInnerInterceptor());
        return interceptor;
    }
}

SqlPrintInnerInterceptor.java

java 复制代码
package com.example.demo.config;



import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.stereotype.Component;

import java.sql.SQLException;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * SQL打印拦截器,用于打印完整的SQL语句和参数值
 */
@Slf4j
@Component
public class SqlPrintInnerInterceptor implements InnerInterceptor {

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

        // 获取SQL语句
        String sql = showSql(ms.getConfiguration(), boundSql);
        log.info("===========================================");
        log.info("SQL: {}", sql);
        log.info("===========================================");
        log.info("Mapper: {}",ms.getId());

    }

    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {

        BoundSql boundSql = ms.getBoundSql(parameter);
        // 获取SQL语句
        String sql = showSql(ms.getConfiguration(), boundSql);
        log.info("===========================================");
        log.info("\nSQL: {}", sql);
        log.info("===========================================");
        log.info("Mapper: {}",ms.getId());
    }

    public static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));

            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }

    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(new Date()) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

}

SwaggerConfig.java

java 复制代码
package com.example.demo.config;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

@Configuration
@EnableSwagger2WebMvc
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("用户管理系统API文档")
                .description("基于Knife4j的API文档")
                .version("1.0")
                .build();
    }
}

数据库SQL

db.sql

sql 复制代码
CREATE TABLE `mp_order` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `order_no` varchar(50) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '关联用户ID',
  `amount` decimal(10,2) NOT NULL COMMENT '订单金额',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单表';

CREATE TABLE `mp_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';

INSERT INTO `mp_order` (`id`, `order_no`, `user_id`, `amount`, `create_time`) VALUES (1, 'ORDER_001', 1, 100.00, '2025-12-24 09:44:08');
INSERT INTO `mp_order` (`id`, `order_no`, `user_id`, `amount`, `create_time`) VALUES (2, 'ORDER_002', 1, 200.00, '2025-12-24 09:44:08');
INSERT INTO `mp_order` (`id`, `order_no`, `user_id`, `amount`, `create_time`) VALUES (3, 'ORDER_003', 2, 150.00, '2025-12-24 09:44:08');

INSERT INTO `mp_user` (`id`, `username`, `age`, `create_time`) VALUES (1, '张三', 20, '2025-12-24 09:44:08');
INSERT INTO `mp_user` (`id`, `username`, `age`, `create_time`) VALUES (2, '李四', 25, '2025-12-24 09:44:08');
INSERT INTO `mp_user` (`id`, `username`, `age`, `create_time`) VALUES (3, '王五', 30, '2025-12-24 09:44:08');

接口测试

单表分页查询用户

多表连接分页查询

项目特点

  1. 使用MyBatis Plus进行单表操作,简化了CRUD操作
  2. 使用MyBatis Plus Join进行多表连接查询,避免了传统的复杂SQL编写
  3. 集成了分页功能,支持对单表和多表查询结果进行分页
  4. 集成了Swagger/Knife4j,提供API文档界面
  5. 包含SQL打印功能,便于调试和查看执行的SQL语句
  6. 使用Hutool工具库简化常用操作
  7. 采用分层架构设计,代码结构清晰

使用说明

  1. 确保MySQL数据库已启动
  2. 执行sql/db.sql创建表结构和测试数据
  3. 修改application.yml中的数据库连接信息
  4. 启动Spring Boot应用
  5. 访问 http://localhost:8080/doc.html 查看API文档并测试接口
相关推荐
sxlishaobin2 小时前
Spring Bean生命周期详解
java·后端·spring
肉丸滚球3 小时前
飞算 JavaAI 转 SpringBoot 项目沉浸式体验:高效开发在线图书借阅平台
java·spring boot·后端
叫我阿柒啊3 小时前
从Java全栈到前端框架:一场真实的技术面试对话
java·vue.js·spring boot·微服务·typescript·前端开发·后端开发
问道飞鱼4 小时前
【Rust编程语言】Rust数据类型全面解析
开发语言·后端·rust·数据类型
泉城老铁4 小时前
目前开源架构需要注意的安全问题
spring boot·后端
ZoeGranger5 小时前
【Spring】IoC 控制反转、DI 依赖注入、配置文件和bean的作用域
后端
马卡巴卡5 小时前
分库分表数据源ShardingSphereDataSource的Connection元数据误用问题分析
后端
superman超哥5 小时前
仓颉动态特性探索:反射API的原理、实战与性能权衡
开发语言·后端·仓颉编程语言·仓颉·仓颉语言·仓颉动态特性·反射api
骑着bug的coder5 小时前
第7讲:索引(下)——失效场景与优化实战
后端·mysql