引言
在现代企业级应用开发中,Spring Boot 和 MyBatis Plus 已经成为了构建高性能、可维护性良好的 Java 应用程序的首选技术栈。Spring Boot 提供了自动配置和快速开发的能力,而 MyBatis Plus 则在 MyBatis 基础上提供了更丰富的 CRUD 操作和代码生成能力。
本文将深入探讨如何在企业级应用开发中运用 Spring Boot 和 MyBatis Plus 的最佳实践,涵盖从项目结构设计到数据库操作优化、事务管理、分页查询等关键技能,帮助开发者提升团队开发效率和代码质量。
项目结构设计与基础配置
1.1 项目结构规划
一个良好的项目结构是企业级应用开发的基础。以下是一个典型的 Spring Boot + MyBatis Plus 项目结构:
src/main/java
├── com.example.demo
│ ├── DemoApplication.java
│ ├── config
│ │ ├── MybatisPlusConfig.java
│ │ └── SwaggerConfig.java
│ ├── controller
│ │ └── UserController.java
│ ├── entity
│ │ └── User.java
│ ├── mapper
│ │ └── UserMapper.java
│ ├── service
│ │ ├── impl
│ │ │ └── UserServiceImpl.java
│ │ └── UserService.java
│ └── common
│ ├── enums
│ ├── exception
│ ├── result
│ └── util
└── application.yml
1.2 核心依赖配置
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
</dependencies>
1.3 数据库配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
sql:
init:
mode: always
schema: classpath:db/schema.sql
data: classpath:db/data.sql
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
table-prefix: t_
实体类设计与注解使用
2.1 基础实体类设计
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("t_user")
public class User extends BaseEntity {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("username")
private String username;
@TableField("password")
private String password;
@TableField("email")
private String email;
@TableField("phone")
private String phone;
@TableField("status")
private Integer status;
@TableField("create_time")
private LocalDateTime createTime;
@TableField("update_time")
private LocalDateTime updateTime;
@TableLogic
@TableField("deleted")
private Integer deleted;
}
2.2 基础实体类基类
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class BaseEntity {
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
}
2.3 实体类注解详解
@TableName: 指定表名@TableId: 指定主键字段及生成策略@TableField: 指定字段映射关系@TableLogic: 指定逻辑删除字段@FieldFill: 指定字段填充策略
Mapper 层设计与优化
3.1 Mapper 接口设计
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper extends BaseMapper<User> {
/**
* 根据用户名查询用户
*/
User selectByUsername(@Param("username") String username);
/**
* 分页查询用户列表
*/
IPage<User> selectUserPage(Page<User> page, @Param("username") String username);
/**
* 批量删除用户
*/
void deleteBatchIds(@Param("ids") List<Long> ids);
/**
* 根据条件查询用户列表
*/
List<User> selectListByCondition(@Param("condition") User condition);
}
3.2 自定义 SQL 优化
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface UserMapper extends BaseMapper<User> {
/**
* 使用原生 SQL 查询用户统计信息
*/
@Select("SELECT u.*, COUNT(o.id) as order_count FROM t_user u " +
"LEFT JOIN t_order o ON u.id = o.user_id " +
"WHERE u.deleted = 0 AND u.status = 1 " +
"GROUP BY u.id " +
"ORDER BY u.create_time DESC")
IPage<User> selectUserWithOrderCount(Page<User> page);
/**
* 复杂条件查询
*/
List<User> selectUsersByConditions(@Param("username") String username,
@Param("email") String email,
@Param("status") Integer status);
}
3.3 Mapper 优化策略
- 继承 BaseMapper: 使用 MyBatis Plus 提供的通用 Mapper
- 合理使用注解: 避免过度复杂的 XML 配置
- 批量操作: 使用批量插入、更新、删除方法
- 缓存机制: 合理使用二级缓存
Service 层最佳实践
4.1 Service 接口设计
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.common.result.Result;
import java.util.List;
public interface UserService {
/**
* 分页查询用户列表
*/
Result<IPage<User>> getUserPage(int current, int size, String username);
/**
* 根据ID查询用户
*/
Result<User> getUserById(Long id);
/**
* 创建用户
*/
Result<User> createUser(User user);
/**
* 更新用户
*/
Result<User> updateUser(User user);
/**
* 删除用户
*/
Result<Boolean> deleteUser(Long id);
/**
* 批量删除用户
*/
Result<Boolean> deleteBatch(List<Long> ids);
/**
* 根据用户名查询用户
*/
Result<User> getUserByUsername(String username);
}
4.2 Service 实现类
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.common.result.Result;
import com.example.demo.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public Result<IPage<User>> getUserPage(int current, int size, String username) {
try {
Page<User> page = new Page<>(current, size);
QueryWrapper<User> wrapper = new QueryWrapper<>();
if (username != null && !username.isEmpty()) {
wrapper.like("username", username);
}
wrapper.eq("deleted", 0);
wrapper.orderByDesc("create_time");
IPage<User> userPage = this.page(page, wrapper);
return Result.success(userPage);
} catch (Exception e) {
log.error("查询用户分页列表异常", e);
throw new BusinessException("查询用户列表失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<User> createUser(User user) {
try {
// 数据校验
validateUser(user);
// 设置默认值
user.setStatus(1);
user.setDeleted(0);
// 保存用户
this.save(user);
return Result.success(user);
} catch (Exception e) {
log.error("创建用户异常", e);
throw new BusinessException("创建用户失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<User> updateUser(User user) {
try {
if (user.getId() == null) {
throw new BusinessException("用户ID不能为空");
}
User existUser = this.getById(user.getId());
if (existUser == null) {
throw new BusinessException("用户不存在");
}
// 更新用户信息
this.updateById(user);
return Result.success(user);
} catch (Exception e) {
log.error("更新用户异常", e);
throw new BusinessException("更新用户失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> deleteUser(Long id) {
try {
if (id == null) {
throw new BusinessException("用户ID不能为空");
}
User user = this.getById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 逻辑删除
user.setDeleted(1);
this.updateById(user);
return Result.success(true);
} catch (Exception e) {
log.error("删除用户异常", e);
throw new BusinessException("删除用户失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> deleteBatch(List<Long> ids) {
try {
if (ids == null || ids.isEmpty()) {
throw new BusinessException("删除ID列表不能为空");
}
// 批量逻辑删除
List<User> users = this.listByIds(ids);
users.forEach(user -> user.setDeleted(1));
this.updateBatchById(users);
return Result.success(true);
} catch (Exception e) {
log.error("批量删除用户异常", e);
throw new BusinessException("批量删除用户失败");
}
}
@Override
public Result<User> getUserByUsername(String username) {
try {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
wrapper.eq("deleted", 0);
User user = this.getOne(wrapper);
return Result.success(user);
} catch (Exception e) {
log.error("根据用户名查询用户异常", e);
throw new BusinessException("查询用户失败");
}
}
private void validateUser(User user) {
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
throw new BusinessException("用户名不能为空");
}
if (user.getPassword() == null || user.getPassword().trim().isEmpty()) {
throw new BusinessException("密码不能为空");
}
if (user.getEmail() == null || user.getEmail().trim().isEmpty()) {
throw new BusinessException("邮箱不能为空");
}
}
}
4.3 事务管理最佳实践
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class OrderServiceImpl {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 使用注解方式管理事务
*/
@Transactional(rollbackFor = Exception.class, timeout = 30)
public void createOrderWithUser(Order order, User user) {
// 保存订单
orderMapper.insert(order);
// 更新用户信息
userMapper.updateById(user);
// 可能抛出异常的业务逻辑
if (order.getAmount() < 0) {
throw new RuntimeException("订单金额不能为负数");
}
}
/**
* 使用编程式事务管理
*/
public void createOrderWithProgrammaticTransaction(Order order) {
transactionTemplate.execute(status -> {
try {
orderMapper.insert(order);
// 其他业务逻辑
return null;
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
});
}
}
数据库操作优化
5.1 查询优化策略
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
public class UserQueryService {
/**
* 避免 N+1 查询问题
*/
public List<User> getUsersWithOrders() {
// 使用关联查询,避免 N+1 问题
return userMapper.selectUserWithOrderCount(new Page<>());
}
/**
* 复合条件查询优化
*/
public IPage<User> searchUsers(UserSearchDTO searchDTO) {
Page<User> page = new Page<>(searchDTO.getCurrent(), searchDTO.getSize());
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 动态条件构建
if (searchDTO.getUsername() != null && !searchDTO.getUsername().isEmpty()) {
wrapper.like("username", searchDTO.getUsername());
}
if (searchDTO.getEmail() != null && !searchDTO.getEmail().isEmpty()) {
wrapper.like("email", searchDTO.getEmail());
}
if (searchDTO.getStatus() != null) {
wrapper.eq("status", searchDTO.getStatus());
}
if (searchDTO.getStartTime() != null) {
wrapper.ge("create_time", searchDTO.getStartTime());
}
if (searchDTO.getEndTime() != null) {
wrapper.le("create_time", searchDTO.getEndTime());
}
wrapper.eq("deleted", 0);
wrapper.orderByDesc("create_time");
return userMapper.selectPage(page, wrapper);
}
}
5.2 批量操作优化
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class BatchOperationService {
/**
* 批量插入优化
*/
public void batchInsertUsers(List<User> users) {
// 使用批量插入,提高性能
userMapper.insertBatchSomeColumn(users);
}
/**
* 批量更新优化
*/
public void batchUpdateUsers(List<User> users) {
// 使用批量更新
userMapper.updateBatchById(users);
}
/**
* 分批处理大数据量
*/
public void processLargeData(List<Long> ids) {
int batchSize = 1000;
for (int i = 0; i < ids.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, ids.size());
List<Long> batchIds = ids.subList(i, endIndex);
// 处理批次数据
processBatch(batchIds);
}
}
private void processBatch(List<Long> batchIds) {
// 批次处理逻辑
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.in("id", batchIds);
List<User> users = userMapper.selectList(wrapper);
// 处理用户数据
users.forEach(user -> {
// 业务逻辑处理
});
}
}
5.3 索引优化建议
-- 用户表索引优化
CREATE TABLE t_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL COMMENT '用户名',
email VARCHAR(100) NOT NULL COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
status TINYINT DEFAULT 1 COMMENT '状态',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '删除标志',
-- 索引优化
INDEX idx_username (username),
INDEX idx_email (email),
INDEX idx_phone (phone),
INDEX idx_status (status),
INDEX idx_create_time (create_time),
INDEX idx_deleted (deleted)
) COMMENT '用户表';
分页查询实现
6.1 基础分页查询
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.common.result.Result;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/page")
public Result<IPage<User>> getUserPage(
@RequestParam(defaultValue = "1") int current,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String username) {
IPage<User> page = userService.getUserPage(current, size, username);
return Result.success(page);
}
/**
* 自定义分页查询
*/
@GetMapping("/custom-page")
public Result<IPage<User>> getCustomPage(
@RequestParam(defaultValue = "1") int current,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String username,
@RequestParam(required = false) String email) {
Page<User> page = new Page<>(current, size);
QueryWrapper<User> wrapper = new QueryWrapper<>();
if (username != null && !username.isEmpty()) {
wrapper.like("username", username);
}
if (email != null && !email.isEmpty()) {
wrapper.like("email", email);
}
wrapper.eq("deleted", 0);
wrapper.orderByDesc("create_time");
IPage<User> userPage = userMapper.selectPage(page, wrapper);
return Result.success(userPage);
}
}
6.2 高级分页查询
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.dto.UserSearchDTO;
import com.example.demo.common.result.Result;
public class AdvancedPaginationService {
/**
* 复杂条件分页查询
*/
public IPage<User> advancedSearch(UserSearchDTO searchDTO) {
Page<User> page = new Page<>(searchDTO.getCurrent(), searchDTO.getSize());
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 多条件组合查询
buildSearchConditions(wrapper, searchDTO);
// 排序
buildOrderConditions(wrapper, searchDTO);
// 执行查询
return userMapper.selectPage(page, wrapper);
}
private void buildSearchConditions(QueryWrapper<User> wrapper, UserSearchDTO searchDTO) {
if (searchDTO.getUsername() != null && !searchDTO.getUsername().isEmpty()) {
wrapper.like("username", searchDTO.getUsername());
}
if (searchDTO.getEmail() != null && !searchDTO.getEmail().isEmpty()) {
wrapper.like("email", searchDTO.getEmail());
}
if (searchDTO.getPhone() != null && !searchDTO.getPhone().isEmpty()) {
wrapper.like("phone", searchDTO.getPhone());
}
if (searchDTO.getStatus() != null) {
wrapper.eq("status", searchDTO.getStatus());
}
if (searchDTO.getStartTime() != null) {
wrapper.ge("create_time", searchDTO.getStartTime());
}
if (searchDTO.getEndTime() != null) {
wrapper.le("create_time", searchDTO.getEndTime());
}
wrapper.eq("deleted", 0);
}
private void buildOrderConditions(QueryWrapper<User> wrapper, UserSearchDTO searchDTO) {
if (searchDTO.getSortField() != null && !searchDTO.getSortField().isEmpty()) {
if ("createTime".equalsIgnoreCase(searchDTO.getSortField())) {
wrapper.orderByDesc("create_time");
} else if ("username".equalsIgnoreCase(searchDTO.getSortField())) {
wrapper.orderByAsc("username");
}
} else {
wrapper.orderByDesc("create_time");
}
}
}
异常处理与统一返回
7.1 统一异常处理
import com.example.demo.common.exception.BusinessException;
import com.example.demo.common.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 业务异常处理
*/
@ExceptionHandler(BusinessException.class)
public Result<String> handleBusinessException(BusinessException e) {
log.error("业务异常: {}", e.getMessage(), e);
return Result.error(e.getMessage());
}
/**
* 系统异常处理
*/
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
log.error("系统异常: ", e);
return Result.error("系统异常,请稍后重试");
}
/**
* 参数验证异常处理
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<String> handleValidationException(MethodArgumentNotValidException e) {
StringBuilder errorMsg = new StringBuilder();
e.getBindingResult().getFieldErrors().forEach(error -> {
errorMsg.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; ");
});
return Result.error(errorMsg.toString());
}
}
7.2 统一返回结果
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
public static <T> Result<T> success() {
return new Result<>(200, "操作成功", null);
}
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null);
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
}
性能监控与日志管理
8.1 SQL 日志监控
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 自动填充创建时间
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
}
@Override
public void updateFill(MetaObject metaObject) {
// 自动填充更新时间
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
8.2 性能监控配置
# MyBatis Plus 性能监控
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启 SQL 性能分析
safe-row-count: true
# 设置 SQL 执行时间阈值(毫秒)
query-timeout: 3000
# 开启缓存
cache-enabled: true
# 开启二级缓存
local-cache-scope: statement
# 日志配置
logging:
level:
com.example.demo.mapper: debug
com.example.demo.service: debug
org.apache.ibatis: debug
安全与权限控制
9.1 权限控制实现
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/users")
public class AdminUserController {
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public Result<List<User>> getAllUsers() {
// 管理员权限查询所有用户
return Result.success(userService.getAllUsers());
}
@PostMapping
@PreAuthorize("hasAuthority('USER_CREATE')")
public Result<User> createUser(@RequestBody User user) {
// 创建用户权限检查
return userService.createUser(user);
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAuthority('USER_DELETE')")
public Result<Boolean> deleteUser(@PathVariable Long id) {
// 删除用户权限检查
return userService.deleteUser(id);
}
}
9.2 数据安全处理
import com.example.demo.common.util.SecurityUtil;
import org.springframework.stereotype.Service;
@Service
public class SecureUserService {
public User getUserById(Long id) {
User user = userService.getById(id);
if (user != null) {
// 敏感信息脱敏处理
user.setPassword(null);
user.setPhone(SecurityUtil.maskPhone(user.getPhone()));
user.setEmail(SecurityUtil.maskEmail(user.getEmail()));
}
return user;
}
public List<User> getUserList() {
List<User> users = userService.list();
users.forEach(user -> {
user.setPassword(null);
user.setPhone(SecurityUtil.maskPhone(user.getPhone()));
user.setEmail(SecurityUtil.maskEmail(user.getEmail()));
});
return users;
评论 (0)