引言
在现代Java企业级应用开发中,Spring Boot与MyBatis Plus的组合已经成为主流的技术栈选择。Spring Boot凭借其"约定优于配置"的理念,极大地简化了Spring应用的初始搭建和开发过程;而MyBatis Plus作为MyBatis的增强工具,在保留MyBatis原有特性的同时,提供了更加便捷的CRUD操作和丰富的插件功能。
本文将从实际项目需求出发,系统性地介绍Spring Boot与MyBatis Plus的最佳实践方案,涵盖代码自动生成、SQL优化、分页查询、事务管理等核心功能,为开发者提供一套完整的开发指南。
环境准备与依赖配置
项目基础环境
在开始之前,我们需要准备以下开发环境:
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Plus Starter -->
<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>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置文件设置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
代码自动生成实践
MyBatis Plus Generator核心配置
MyBatis Plus提供了强大的代码生成器,可以快速生成Mapper、Entity、Service等基础代码:
@Configuration
public class CodeGenerator {
public static void main(String[] args) {
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("author");
gc.setOpen(false);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("password");
mpg.setDataSource(dSource);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("user");
pc.setParent("com.example.demo");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude("user_info");
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.execute();
}
}
自定义代码生成模板
为了满足特定需求,我们可以自定义代码生成模板:
public class CustomCodeGenerator {
public static void generateCustomCode() {
AutoGenerator mpg = new AutoGenerator();
// 设置自定义模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setEntity("/templates/entity.java.vm");
templateConfig.setMapper("/templates/mapper.java.vm");
templateConfig.setService("/templates/service.java.vm");
templateConfig.setServiceImpl("/templates/serviceImpl.java.vm");
templateConfig.setController("/templates/controller.java.vm");
mpg.setTemplate(templateConfig);
// 其他配置...
mpg.execute();
}
}
实体类设计与注解使用
基础实体类结构
@TableName("user_info")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
@TableField("user_name")
private String userName;
/**
* 邮箱
*/
private String email;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField("update_time")
private LocalDateTime updateTime;
/**
* 状态:0-禁用,1-启用
*/
private Integer status;
}
高级注解使用
@TableName("user_info")
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserInfo extends Model<UserInfo> {
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 用户名(唯一)
*/
@TableField("user_name")
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
private String userName;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
private String email;
/**
* 密码
*/
@TableField(exist = false)
private String password;
/**
* 创建时间
*/
@TableField("create_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField("update_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
/**
* 版本号(用于乐观锁)
*/
@Version
@TableField("version")
private Integer version;
/**
* 逻辑删除字段
*/
@TableLogic
@TableField("deleted")
private Integer deleted;
}
Mapper层设计与使用
基础Mapper接口
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
/**
* 根据用户名查询用户信息
*/
UserInfo selectByUsername(@Param("username") String username);
/**
* 批量插入用户
*/
int insertBatchSomeColumn(List<UserInfo> entityList);
/**
* 分页查询用户列表
*/
IPage<UserInfo> selectUserPage(Page<UserInfo> page, @Param("status") Integer status);
}
自定义SQL方法
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
/**
* 复杂条件查询
*/
@Select("SELECT * FROM user_info WHERE status = #{status} AND create_time >= #{startTime}")
List<UserInfo> selectByStatusAndTime(@Param("status") Integer status,
@Param("startTime") LocalDateTime startTime);
/**
* 使用XML配置复杂SQL
*/
List<UserInfo> selectUserWithRole(@Param("userId") Long userId);
}
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.UserInfoMapper">
<select id="selectUserWithRole" resultType="com.example.demo.entity.UserInfo">
SELECT u.*, r.role_name
FROM user_info u
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN role r ON ur.role_id = r.id
WHERE u.id = #{userId}
</select>
<select id="selectUserPage" resultType="com.example.demo.entity.UserInfo">
SELECT * FROM user_info
<where>
<if test="status != null">
status = #{status}
</if>
</where>
ORDER BY create_time DESC
</select>
</mapper>
Service层最佳实践
基础Service实现
@Service
@Transactional(rollbackFor = Exception.class)
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
/**
* 根据用户名获取用户信息
*/
@Override
public UserInfo getUserByUsername(String username) {
return userInfoMapper.selectByUsername(username);
}
/**
* 分页查询用户列表
*/
@Override
public IPage<UserInfo> getUserPage(int current, int size, Integer status) {
Page<UserInfo> page = new Page<>(current, size);
return userInfoMapper.selectUserPage(page, status);
}
/**
* 批量删除用户(逻辑删除)
*/
@Override
public boolean deleteBatchByIds(List<Long> ids) {
List<UserInfo> updateList = ids.stream()
.map(id -> {
UserInfo user = new UserInfo();
user.setId(id);
user.setDeleted(1);
return user;
})
.collect(Collectors.toList());
return this.updateBatchById(updateList);
}
}
高级Service功能
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 带缓存的用户查询
*/
@Override
public UserInfo getUserWithCache(Long userId) {
String cacheKey = "user_info:" + userId;
UserInfo user = (UserInfo) redisTemplate.opsForValue().get(cacheKey);
if (user == null) {
user = this.getById(userId);
if (user != null) {
redisTemplate.opsForValue().set(cacheKey, user, 30, TimeUnit.MINUTES);
}
}
return user;
}
/**
* 事务管理的复杂业务逻辑
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean createUserWithRole(UserInfo userInfo, Long roleId) {
try {
// 插入用户
this.save(userInfo);
// 关联角色
UserRole userRole = new UserRole();
userRole.setUserId(userInfo.getId());
userRole.setRoleId(roleId);
userRole.setCreateTime(LocalDateTime.now());
// 这里需要注入UserRoleMapper
// userRoleMapper.insert(userRole);
return true;
} catch (Exception e) {
log.error("创建用户失败", e);
throw new RuntimeException("创建用户失败");
}
}
/**
* 批量操作优化
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean batchUpdateStatus(List<Long> ids, Integer status) {
List<UserInfo> updateList = ids.stream()
.map(id -> {
UserInfo user = new UserInfo();
user.setId(id);
user.setStatus(status);
user.setUpdateTime(LocalDateTime.now());
return user;
})
.collect(Collectors.toList());
return this.updateBatchById(updateList, 1000);
}
}
分页查询优化
基础分页查询
@RestController
@RequestMapping("/user")
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
/**
* 分页查询用户列表
*/
@GetMapping("/page")
public Result<IPage<UserInfo>> getUserPage(
@RequestParam(defaultValue = "1") int current,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) Integer status) {
IPage<UserInfo> page = userInfoService.getUserPage(current, size, status);
return Result.success(page);
}
}
高级分页查询优化
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
/**
* 复杂条件分页查询
*/
@Override
public IPage<UserInfo> getUserPageWithCondition(Page<UserInfo> page,
String keyword,
Integer status,
LocalDateTime startTime,
LocalDateTime endTime) {
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.like("user_name", keyword).or().like("email", keyword);
}
if (status != null) {
wrapper.eq("status", status);
}
if (startTime != null && endTime != null) {
wrapper.between("create_time", startTime, endTime);
}
return this.page(page, wrapper);
}
/**
* 性能优化的分页查询
*/
@Override
public IPage<UserInfo> getOptimizedUserPage(Page<UserInfo> page,
String keyword,
Integer status) {
// 使用select()只查询需要的字段,减少网络传输
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.select("id", "user_name", "email", "create_time", "status");
if (StringUtils.hasText(keyword)) {
wrapper.like("user_name", keyword);
}
if (status != null) {
wrapper.eq("status", status);
}
return this.page(page, wrapper);
}
}
分页查询性能优化策略
@Component
public class PageQueryOptimizer {
/**
* 大数据量分页优化
*/
public IPage<UserInfo> optimizeLargeDataPage(Page<UserInfo> page,
String keyword,
Integer status) {
// 1. 使用索引优化
// 确保查询字段有合适的索引
// 2. 限制查询范围
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.like("user_name", keyword);
}
if (status != null) {
wrapper.eq("status", status);
}
// 3. 使用游标分页优化大数据量
return this.page(page, wrapper);
}
/**
* 分页查询缓存策略
*/
public IPage<UserInfo> getCachedPageResult(Page<UserInfo> page,
String cacheKey) {
String redisKey = "page_cache:" + cacheKey;
String cachedResult = (String) redisTemplate.opsForValue().get(redisKey);
if (cachedResult != null) {
// 反序列化缓存结果
return JSON.parseObject(cachedResult, new TypeReference<IPage<UserInfo>>() {});
}
// 执行查询并缓存
IPage<UserInfo> result = this.page(page);
redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(result), 5, TimeUnit.MINUTES);
return result;
}
}
SQL优化策略
基础SQL优化原则
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> {
/**
* 优化前的查询(存在性能问题)
*/
public List<UserInfo> getOptimizationBefore() {
// 不好的写法:全表扫描 + 多次查询
return this.list();
}
/**
* 优化后的查询
*/
public List<UserInfo> getOptimizationAfter() {
// 好的写法:只查询需要的字段,使用索引
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.select("id", "user_name", "email") // 只选择必要字段
.eq("status", 1) // 添加过滤条件
.orderByDesc("create_time"); // 按时间倒序
return this.list(wrapper);
}
}
索引优化实践
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
/**
* 带索引的查询方法
*/
@Select("SELECT id, user_name, email FROM user_info WHERE status = #{status} AND create_time >= #{date}")
List<UserInfo> selectByStatusAndDate(@Param("status") Integer status,
@Param("date") LocalDateTime date);
}
批量操作优化
@Service
public class UserInfoBatchService {
@Autowired
private UserInfoMapper userInfoMapper;
/**
* 优化前的批量插入
*/
public void batchInsertBefore(List<UserInfo> userList) {
for (UserInfo user : userList) {
userInfoMapper.insert(user);
}
}
/**
* 优化后的批量插入
*/
public void batchInsertAfter(List<UserInfo> userList) {
// 使用MyBatis Plus提供的批量插入方法
if (CollectionUtils.isNotEmpty(userList)) {
userInfoMapper.insertBatchSomeColumn(userList);
}
}
/**
* 分批处理大数据量
*/
public void batchProcessInChunks(List<UserInfo> userList, int chunkSize) {
List<List<UserInfo>> chunks = Lists.partition(userList, chunkSize);
for (List<UserInfo> chunk : chunks) {
userInfoMapper.insertBatchSomeColumn(chunk);
// 添加适当的休眠,避免数据库压力过大
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
事务管理最佳实践
基础事务配置
@Service
@Transactional(rollbackFor = Exception.class)
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Autowired
private UserRoleMapper userRoleMapper;
/**
* 用户注册事务
*/
public boolean registerUser(UserInfo userInfo, List<Long> roleIds) {
try {
// 1. 插入用户
userInfoMapper.insert(userInfo);
// 2. 关联角色
for (Long roleId : roleIds) {
UserRole userRole = new UserRole();
userRole.setUserId(userInfo.getId());
userRole.setRoleId(roleId);
userRoleMapper.insert(userRole);
}
return true;
} catch (Exception e) {
log.error("用户注册失败", e);
throw new RuntimeException("用户注册失败");
}
}
}
事务传播行为使用
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
/**
* 外部调用,使用REQUIRED传播行为
*/
@Transactional(propagation = Propagation.REQUIRED)
public void updateUserWithRoles(Long userId, String userName, List<Long> roleIds) {
// 更新用户信息
UserInfo user = new UserInfo();
user.setId(userId);
user.setUserName(userName);
userInfoMapper.updateById(user);
// 更新角色关联(可能抛出异常)
updateRoleAssociations(userId, roleIds);
}
/**
* 内部方法,使用REQUIRES_NEW传播行为
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void updateRoleAssociations(Long userId, List<Long> roleIds) {
// 删除旧关联
userRoleMapper.deleteByUserId(userId);
// 插入新关联
for (Long roleId : roleIds) {
UserRole userRole = new UserRole();
userRole.setUserId(userId);
userRole.setRoleId(roleId);
userRoleMapper.insert(userRole);
}
}
}
异常处理与事务回滚
@Service
public class UserService {
@Transactional(rollbackFor = Exception.class)
public boolean complexBusinessOperation(UserInfo userInfo, List<Role> roles) {
try {
// 1. 验证用户信息
validateUserInfo(userInfo);
// 2. 插入用户
userInfoMapper.insert(userInfo);
// 3. 处理角色关联
handleRoleAssociations(userInfo.getId(), roles);
// 4. 记录操作日志
logUserOperation(userInfo.getId(), "CREATE");
return true;
} catch (BusinessException e) {
// 业务异常,记录日志但不回滚事务
log.warn("业务异常: {}", e.getMessage());
throw e;
} catch (Exception e) {
// 系统异常,回滚事务
log.error("系统异常", e);
throw new RuntimeException("操作失败");
}
}
private void validateUserInfo(UserInfo userInfo) throws BusinessException {
if (userInfo.getUserName() == null || userInfo.getUserName().isEmpty()) {
throw new BusinessException("用户名不能为空");
}
if (userInfo.getEmail() == null || !isValidEmail(userInfo.getEmail())) {
throw new BusinessException("邮箱格式不正确");
}
}
}
数据库连接池优化
HikariCP配置优化
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
hikari:
# 连接池名称
pool-name: MyHikariCP
# 最小空闲连接数
minimum-idle: 5
# 最大连接数
maximum-pool-size: 20
# 连接超时时间(毫秒)
connection-timeout: 30000
# 空闲连接超时时间(毫秒)
idle-timeout: 600000
# 连接最大存活时间(毫秒)
max-lifetime: 1800000
# 自动提交
auto-commit: true
# 连接测试查询
connection-test-query: SELECT 1
# 验证连接有效性的时间间隔
validation-timeout: 5000
连接池监控配置
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public HikariDataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test_db");
config.setUsername("root");
config.setPassword("password");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
// 添加监控配置
config.setPoolName("MyHikariCP");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useServerPrepStmts", "true");
return new HikariDataSource(config);
}
}
缓存策略与性能优化
Redis缓存集成
@Service
public class UserInfoCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserInfoMapper userInfoMapper;
/**
* 带缓存的用户查询
*/
public UserInfo getUserByIdWithCache(Long userId) {
String cacheKey = "user_info:" + userId;
// 先从缓存获取
UserInfo user = (UserInfo) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
log.debug("从缓存获取用户信息: {}", userId);
return user;
}
// 缓存未命中,查询数据库
log.debug("从数据库获取用户信息: {}", userId);
user = userInfoMapper.selectById(userId);
if (user != null) {
// 设置缓存(5分钟过期)
redisTemplate.opsForValue().set(cacheKey, user, 5, TimeUnit.MINUTES);
}
return user;
}
/**
* 缓存更新策略
*/
public void updateUserCache(UserInfo userInfo) {
String cacheKey = "user_info:" + userInfo.getId();
redisTemplate.opsForValue().set(cacheKey, userInfo, 5, TimeUnit.MINUTES);
}
/**
* 缓存删除策略
*/
public void deleteUserCache(Long userId) {
String cacheKey = "user_info:" + userId;
redisTemplate.delete(cacheKey);
}
}
查询缓存优化
@Service
public class CachedQueryService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 分页查询缓存
*/
public IPage<UserInfo> getCachedUserPage(int current, int size, Integer status) {
String cacheKey = "user_page:" + current + ":" + size + ":" + status;
// 尝试从缓存获取
String cachedResult = (String) redisTemplate.opsForValue().get(cacheKey);
if (cachedResult != null) {
return JSON.parseObject(cachedResult, new TypeReference<IPage<UserInfo>>() {});
}
// 缓存未命中,执行查询
Page<UserInfo> page = new Page<>(current, size);
IPage<UserInfo> result = userInfoMapper.selectUserPage(page, status);
// 缓存结果
redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(result), 10, TimeUnit.MINUTES);
return result;
}
}
监控与日志管理
SQL执行监控
@Component
public class SqlMonitor {
private static final Logger logger = LoggerFactory.getLogger(SqlMonitor.class);
/**
* SQL执行时间监控
*/
public void monitorSqlExecution(String sql, long executionTime) {
if (executionTime > 1000) { // 超过1秒的SQL记录警告日志
logger.warn("SQL执行时间过长: {}ms", executionTime);
logger.warn("SQL内容: {}", sql);
} else if (executionTime > 500) {
logger.info("SQL执行时间较长: {}ms",
评论 (0)