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');
接口测试
单表分页查询用户
多表连接分页查询
项目特点
- 使用MyBatis Plus进行单表操作,简化了CRUD操作
- 使用MyBatis Plus Join进行多表连接查询,避免了传统的复杂SQL编写
- 集成了分页功能,支持对单表和多表查询结果进行分页
- 集成了Swagger/Knife4j,提供API文档界面
- 包含SQL打印功能,便于调试和查看执行的SQL语句
- 使用Hutool工具库简化常用操作
- 采用分层架构设计,代码结构清晰
使用说明
- 确保MySQL数据库已启动
- 执行sql/db.sql创建表结构和测试数据
- 修改application.yml中的数据库连接信息
- 启动Spring Boot应用
- 访问 http://localhost:8080/doc.html 查看API文档并测试接口