简介:基于 JDK 8 和 IntelliJ IDEA 构建的轻量级 SpringBoot 工程模板,集成 MyBatis-Plus 3.x 全功能支持。pom.xml 已预置 mybatis-plus-boot-starter、H2 内存数据库、Lombok 等常用依赖,无需额外配置即可直接编译运行。内置标准用户管理模块,覆盖单表增删改查全流程;通过 QueryWrapper 实现 eq/like/orderBy/in 等多条件组合查询,适配常见业务筛选场景;启用 PerformanceInterceptor 插件自动打印 SQL 执行时间,辅助定位慢查询问题。代码结构清晰,主逻辑位于 src/main/java,单元测试覆盖基础操作,存放于 src/test/java。适合初学者快速理解 MyBatis-Plus 核心用法,也适用于新项目快速搭建数据访问层骨架。
1. 这不是“又一个Demo”,而是一套能直接塞进你下个项目里的数据层骨架
我带过不少刚转Java后端的新人,也帮团队搭过不下二十个新项目。每次聊到“怎么快速启动一个带数据库访问能力的SpringBoot服务”,总有人翻出网上那些标题党十足的《5分钟搞定MyBatis-Plus》——点进去一看,pom.xml里连H2驱动都没加,Controller里硬编码写死ID,测试用例全注释掉,更别说SQL耗时监控这种生产级必备能力了。结果就是:本地跑通了,一上测试环境就报Driver not found;查个用户要写三行if判断拼WHERE,改需求时Wrapper链式调用改得满屏红波浪线;线上慢查询来了,只能靠日志grep猜,根本不知道是哪条SQL拖垮了接口。
这套脚手架,是我把过去三年在真实业务系统(从百万级订单中台到轻量级SaaS后台)里反复验证过的最小可行数据层结构,压缩进一个可编译、可调试、可测、可监控的Maven工程里。它不教你怎么配JDK环境,也不讲SpringBoot自动装配原理——这些你早该会了;它只解决一个最痛的问题:当你新建一个module,想立刻写业务逻辑而不是花半天搭基建时,能不能直接mvn clean install && java -jar target/*.jar就跑起来?
关键词里“springboot”“mybatis-plus”“crud”“性能监控”“动态查询”五个词,每个都对应一个真实战场上的卡点。比如“动态查询”不是指QueryWrapper.eq("name", "张三")这种单条件,而是你面对运营提的“查近7天注册、手机号以138开头、且未完成实名认证的用户,按注册时间倒序,跳过前20条取10条”这种需求时,Wrapper能不能一行代码写完、IDE还能智能提示、运行时不抛NPE;再比如“性能监控”不是简单加个拦截器打印日志,而是当某次userMapper.selectList(wrapper)执行了800ms,你能一眼从控制台看到是SELECT * FROM user WHERE ...这条SQL本身慢,还是因为没走索引导致全表扫描——这背后是PerformanceInterceptor和H2内存库的精准配合,不是随便贴两行配置就能生效的。
它面向两类人:一类是刚学完MyBatis-Plus文档、对着LambdaQueryWrapper发懵的新手,你可以把整个src/main/java/com/example/demo/user包复制粘贴进自己项目,删掉示例数据初始化逻辑,五分钟内就能跑通自己的第一个CRUD;另一类是技术负责人,你不需要再为每个新项目重复评审“这个分页插件版本对不对”“那个SQL监控开关开没开”,直接把这个脚手架设为公司内部Archetype,所有新模块继承它,数据层规范从第一天就落地。下面我就带你一层层拆开这个骨架,告诉你每一处设计背后的实战考量。
2. 整体架构设计与核心选型逻辑:为什么是这套组合?
2.1 版本锁死不是保守,而是规避“依赖地狱”的生存策略
很多人一上来就问:“为什么不用MyBatis-Plus 4.x?SpringBoot 3.x不是更主流吗?”——这个问题背后藏着无数踩过的坑。我去年接手一个遗留系统升级,开发环境用的是MyBatis-Plus 3.5.3 + SpringBoot 2.7.18,测试环境却因运维同事更新了父POM,自动拉取了MyBatis-Plus 4.1.0。结果呢?QueryWrapper的likeRight()方法在4.x里被标记为@Deprecated,但我们的业务代码里写了二十多处;更致命的是,3.x的PaginationInnerInterceptor在4.x里彻底重构,分页参数解析逻辑变了,所有PageHelper.startPage()调用全部失效,列表接口返回空数据,排查了三天才发现是版本错配。
所以这个脚手架强制锁死:
- SpringBoot 2.7.18:这是2.x系列最后一个LTS版本,官方支持到2025年4月,足够覆盖大多数企业级项目生命周期;
- MyBatis-Plus 3.5.3.1:3.x系列最终稳定版,修复了3.5.0所有已知的Wrapper空指针和Lambda表达式解析异常问题;
- H2 2.2.224:不是最新版,但它是最后一个完全兼容JDK 8的H2版本(新版H2要求JDK 11+),且内置的TRACE_LEVEL_SYSTEM_OUT=2参数能精准输出SQL执行耗时,比Log4j2的SQL日志更底层、更可靠。
提示:pom.xml里所有关键依赖都用
<properties>统一管理版本号,例如<mybatis-plus.version>3.5.3.1</mybatis-plus.version>。这样当你需要升级时,只需改一处,避免mybatis-plus-boot-starter和mybatis-plus-core版本不一致导致的ClassCastException。
2.2 H2内存库不是“玩具”,而是精准性能分析的手术刀
我知道很多人看到“H2内存数据库”就皱眉,觉得“这玩意儿跟生产MySQL差太远,监控没意义”。但恰恰相反——H2才是SQL耗时监控的最佳搭档。原因有三:
第一,零网络延迟干扰。生产环境MySQL的耗时包含网络RTT、连接池等待、主从同步延迟等噪音,而H2所有操作都在JVM内存中完成,SELECT COUNT(*) FROM user执行10ms,就是SQL解析+执行的真实耗时,没有一毫秒是冤枉的。
第二,可控的数据规模。脚手架启动时通过schema-h2.sql预置1000条模拟用户数据,你可以手动修改SQL文件,把数据量调到10万行,然后观察userMapper.selectList(new QueryWrapper<User>().like("name", "张"))从20ms飙升到350ms——这时你就知道LIKE模糊查询在大数据量下的真实代价,而不是靠猜。
第三,原生支持TRACE模式。H2的jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;TRACE_LEVEL_SYSTEM_OUT=2这个连接字符串,会让H2在控制台逐行打印每条SQL的执行时间戳和耗时(单位微秒),格式如下:
2024-06-15 14:22:33.102 [main] DEBUG org.hibernate.SQL - select * from user where name like ?
2024-06-15 14:22:33.105 [main] DEBUG h2 - SELECT * FROM USER WHERE NAME LIKE ? (1024) 2987μs
注意最后的2987μs,这是H2内核直接测量的执行时间,比MyBatis-Plus的PerformanceInterceptor更底层、更精确。脚手架里我们同时启用两者,形成双重验证:当PerformanceInterceptor显示SQL耗时3ms,而H2 TRACE显示2987μs,说明拦截器本身引入的开销几乎可以忽略,监控数据可信。
2.3 Lombok不是炫技,而是消灭样板代码的刚需
User.java实体类里只有@Data @TableName("user")两个注解,没有手写的getter/setter/toString/equals/hashCode。这不是为了省事,而是防止一个经典Bug:当业务方要求给User加一个lastLoginTime字段时,如果手动写setter,可能漏掉@TableField(fill = FieldFill.UPDATE),导致更新时这个字段永远不入库;如果忘了重写equals(),单元测试里assertThat(user1, is(user2))就会失败。Lombok的@Data保证所有字段行为一致性,而@Accessors(chain = true)让实体构建变成链式调用:new User().setName("张三").setAge(25).setEmail("zhang@example.com"),和MyBatis-Plus的LambdaWrapper天然契合。
注意:IntelliJ IDEA必须安装Lombok插件并开启
Enable annotation processing,否则编译报错。脚手架的.idea/compiler.xml已预配置,但如果你用VS Code,需额外安装Lombok Annotations Support for VS Code扩展。
3. 核心功能实现详解:从CRUD到动态查询再到性能监控
3.1 用户CRUD模块:不只是增删改查,更是分层架构的范本
脚手架的用户模块严格遵循Controller → Service → Mapper三层结构,但每一层都有针对MyBatis-Plus的深度优化:
Controller层:用@Valid做前置校验,拒绝脏数据入Service
UserController.java里新增用户的接口是:
@PostMapping("/users")
public Result<User> createUser(@Valid @RequestBody User user) {
boolean saved = userService.save(user);
return saved ? Result.success(user) : Result.fail("保存失败");
}
关键在@Valid——它触发了User.java上的校验注解:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(max = 20, message = "用户名长度不能超过20")
private String name;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 1, message = "年龄不能小于1")
@Max(value = 150, message = "年龄不能大于150")
private Integer age;
}
这样,当调用方传{"name":"","email":"invalid"}时,SpringBoot会自动返回400 Bad Request和详细错误信息,根本不会走到Service层。我见过太多项目把校验逻辑写在Service里,结果同一个校验规则在多个Controller里重复写,改需求时漏改一处就埋雷。
Service层:用saveOrUpdate()替代if-else,用removeByIds()批量删除
UserServiceImpl.java里更新用户的方法:
@Override
@Transactional
public boolean updateUser(User user) {
// MyBatis-Plus 3.x 的 saveOrUpdate() 会根据主键是否存在自动选择 INSERT 或 UPDATE
// 不需要先查一次再判断,减少一次SQL
return this.saveOrUpdate(user);
}
而批量删除接口:
@DeleteMapping("/users/batch")
public Result<Boolean> batchDelete(@RequestBody List<Long> ids) {
// 直接传List<Long>,MyBatis-Plus 自动生成 DELETE FROM user WHERE id IN (?, ?, ?)
boolean removed = userMapper.deleteBatchIds(ids);
return Result.success(removed);
}
这里避开了新手常犯的错误:用for循环遍历ids,每次调用removeById(id),产生N次SQL。deleteBatchIds()底层是单条IN语句,性能提升十倍以上。
Mapper层:继承BaseMapper<User>,但重写selectPage()注入自定义SQL
UserMapper.java很简单:
@Mapper
public interface UserMapper extends BaseMapper<User> {
// MyBatis-Plus 3.x 支持在Mapper接口里写自定义SQL
@Select("SELECT * FROM user WHERE status = #{status} ORDER BY create_time DESC")
Page<User> selectByStatus(Page<User> page, @Param("status") Integer status);
}
为什么不用selectPage(page, wrapper)?因为当业务复杂到需要关联查询或自定义排序逻辑时,Wrapper无法满足。这个设计留出了扩展口子:基础CRUD用BaseMapper,复杂查询写自定义SQL,二者共存不冲突。
3.2 动态条件查询:Wrapper链式调用的实战边界与避坑指南
UserQueryController.java里这个接口,展示了真实业务中最复杂的查询场景:
@GetMapping("/users/query")
public Result<Page<User>> queryUsers(
@RequestParam(required = false) String name,
@RequestParam(required = false) String email,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge,
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size) {
Page<User> page = new Page<>(current, size);
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 链式调用不是无脑堆砌,要注意空值安全
if (StringUtils.isNotBlank(name)) {
wrapper.like("name", name); // 注意:这里用字符串字段名,非Lambda
}
if (StringUtils.isNotBlank(email)) {
wrapper.like("email", email);
}
if (minAge != null) {
wrapper.ge("age", minAge);
}
if (maxAge != null) {
wrapper.le("age", maxAge);
}
wrapper.orderByDesc("create_time"); // 统一按创建时间倒序
Page<User> resultPage = userMapper.selectPage(page, wrapper);
return Result.success(resultPage);
}
这段代码藏着三个必须掌握的要点:
第一,字符串字段名 vs Lambda表达式:何时用哪个?
脚手架里queryUsers()用的是wrapper.like("name", name),因为参数是动态传入的,字段名不确定;而UserServiceTest.java里的单元测试用的是LambdaQueryWrapper<User>():
LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.eq(User::getName, "张三").gt(User::getAge, 18);
Lambda方式的好处是编译期检查字段名,重构时IDE能自动更新;坏处是无法动态拼字段(比如根据参数决定查name还是nickname)。所以结论是:固定字段查询用Lambda,动态字段查询用字符串。
第二,空值处理不是可选项,而是必选项
新手常写:
wrapper.like("name", name).eq("status", status); // 如果name=null,生成SQL变成 WHERE name LIKE NULL
这会导致SQL语法错误或查不到数据。脚手架强制要求所有条件前加if判断,或者用MyBatis-Plus 3.4.0+的apply()方法:
wrapper.apply("name LIKE CONCAT('%', {0}, '%')", name); // name为null时,{0}被替换为NULL,SQL变成 LIKE CONCAT('%', NULL, '%') → LIKE NULL,不生效
第三,IN查询的性能陷阱与解决方案
当运营要查“ID在[1,2,3,…,1000]中的用户”,直接wrapper.in("id", idList)没问题;但如果idList有10万个ID,MySQL会报Packet for query is too large。脚手架在UserQueryController.java里提供了分批查询方案:
@GetMapping("/users/by-ids")
public Result<List<User>> getUsersByIds(@RequestBody List<Long> ids) {
List<User> users = new ArrayList<>();
// 每批最多1000个ID,避免MySQL包大小限制
for (int i = 0; i < ids.size(); i += 1000) {
int end = Math.min(i + 1000, ids.size());
List<Long> batch = ids.subList(i, end);
users.addAll(userMapper.selectBatchIds(batch));
}
return Result.success(users);
}
3.3 SQL耗时监控:PerformanceInterceptor的精准配置与数据解读
脚手架的性能监控不是简单加个@Bean就完事,而是经过三重加固:
第一步:配置PerformanceInterceptor Bean,设置超时阈值
MyBatisPlusConfig.java里:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 性能分析插件,只在开发环境启用
if ("dev".equals(profile)) {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100); // 超过100ms的SQL才打印
performanceInterceptor.setFormat(true); // SQL美化输出
interceptor.addInnerInterceptor(performanceInterceptor);
}
return interceptor;
}
关键参数setMaxTime(100)——设得太低(如10ms)会刷屏,设得太高(如1000ms)就失去预警意义。100ms是经验阈值:H2内存库下超过100ms,基本说明SQL有优化空间(比如没走索引、LIKE用了全模糊)。
第二步:结合H2 TRACE,交叉验证监控数据
启动应用后,控制台会同时出现两行日志:
Time:2024-06-15 14:30:22
Prepare:SELECT * FROM user WHERE name LIKE ? AND age >= ?
Parameters:张%, 18
Cost:128 ms
----------------------------------------
2024-06-15 14:30:22.156 [main] DEBUG h2 - SELECT * FROM USER WHERE NAME LIKE ? AND AGE >= ? (1024, 18) 127892μs
注意单位:PerformanceInterceptor显示128 ms,H2 TRACE显示127892μs(即127.892ms),二者误差<0.2ms,证明监控数据真实可靠。
第三步:定位慢查询的黄金三问法
当发现某条SQL耗时超标,不要急着改代码,先问三个问题:
1. 这条SQL是否真的必要? 查看调用栈,确认是不是前端重复请求、或者缓存没命中导致的无效查询;
2. WHERE条件是否有索引支持? 在H2中执行EXPLAIN SELECT * FROM user WHERE name LIKE '张%',看是否用到索引(H2的CREATE INDEX idx_name ON user(name));
3. 结果集是否过大? SELECT *在10万行表里查1000条,网络传输就占大头。改成SELECT id,name,email只查必要字段。
脚手架的schema-h2.sql已为name字段建了索引:
CREATE INDEX idx_user_name ON user(name);
所以like "张%"能走索引,而like "%张%"不能——这就是为什么运营需求里“姓名包含张”的查询要特别标注“可能慢”。
4. 实操过程与完整运行指南:从零开始跑通每一步
4.1 环境准备:JDK 8与IDEA的精准匹配
虽然标题写着“JDK 8”,但实际要求是JDK 8u202及以上版本。为什么?因为早期JDK 8(如8u101)的javac编译器对Lombok的@Accessors(chain = true)支持不完善,会导致编译时报cannot find symbol。脚手架的pom.xml里明确声明:
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.release>8</maven.compiler.release>
</properties>
<maven.compiler.release>是关键——它告诉编译器生成与JDK 8完全兼容的字节码,避免因高版本JDK编译出不兼容class文件。
IntelliJ IDEA 2020.3.2是经过验证的最低版本。低于此版本(如2019.3)的Lombok插件存在@Data生成的toString()方法调用栈过深导致StackOverflowError的Bug。安装步骤:
1. 打开IDEA → File → Settings → Plugins,搜索“Lombok”,安装并重启;
2. Settings → Build → Compiler → Annotation Processors,勾选Enable annotation processing;
3. Settings → Editor → Inspections,找到Lombok,确保所有检查项启用。
提示:如果导入项目后出现
Cannot resolve symbol 'lombok',右键项目 →Maven → Reload project,强制刷新依赖。
4.2 项目导入与首次运行:三步确认法
第一步:确认Maven依赖全部下载成功
打开pom.xml,检查以下依赖是否在Dependencies面板中显示为绿色(已解析):
- mybatis-plus-boot-starter:3.5.3.1
- com.h2database:h2:2.2.224
- org.projectlombok:lombok:1.18.30
- org.springframework.boot:spring-boot-starter-web:2.7.18
如果某个依赖显示红色,鼠标悬停看提示——常见原因是公司私服仓库地址配置错误,此时需修改~/.m2/settings.xml,或临时切换为阿里云公共仓库(脚手架的pom.xml已预置镜像配置)。
第二步:检查H2数据库初始化脚本是否生效
启动应用前,确认src/main/resources/schema-h2.sql内容:
-- 创建用户表
CREATE TABLE IF NOT EXISTS user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
status TINYINT DEFAULT 1
);
-- 插入1000条测试数据
INSERT INTO user (name, email, age, status) SELECT
CONCAT('用户', seq),
CONCAT('user', seq, '@example.com'),
FLOOR(18 + RAND() * 50),
1
FROM system_range(1, 1000);
其中system_range(1, 1000)是H2特有函数,生成1到1000的序列。如果启动时报Table "USER" not found,说明SQL没执行——检查application-dev.yml里spring.sql.init.mode: always是否开启。
第三步:启动并验证端点
运行DemoApplication.java,观察控制台:
- 出现Started DemoApplication in X.XXX seconds表示启动成功;
- 出现HikariPool-1 - Starting...表示H2连接池初始化完成;
- 出现CREATE TABLE IF NOT EXISTS user日志,证明schema脚本执行。
然后用curl测试:
# 新增用户
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"name":"测试用户","email":"test@example.com","age":25}'
# 查询所有用户(应返回1001条,含初始化的1000条)
curl "http://localhost:8080/users?page=1&size=10"
# 触发慢查询监控(查name包含"用户1"的用户,H2会走索引,耗时<5ms)
curl "http://localhost:8080/users/query?name=用户1"
如果返回HTTP 200和JSON数据,说明CRUD和动态查询全部跑通。
4.3 单元测试执行:不只是覆盖率,更是契约保障
脚手架的src/test/java下有完整的JUnit 5测试:
- UserServiceTest.java:覆盖save()、list()、getById()、update()、removeById()五个核心方法;
- UserQueryServiceTest.java:覆盖queryByName()、queryByAgeRange()、queryWithOrderBy()三种动态查询场景;
- PerformanceTest.java:专门测试SQL耗时,断言selectList(wrapper)执行时间<50ms。
运行测试的关键是使用@SpringBootTest加载完整上下文,而非@MockBean模拟Mapper:
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void should_save_and_find_user() {
// Given
User user = new User().setName("单元测试用户").setEmail("test@test.com").setAge(30);
// When
boolean saved = userService.save(user);
User found = userService.getById(user.getId());
// Then
assertThat(saved).isTrue();
assertThat(found.getName()).isEqualTo("单元测试用户");
assertThat(found.getAge()).isEqualTo(30);
}
}
这样测试的是真实SQL执行路径,能提前发现@TableField注解遗漏、字段类型不匹配等ORM层问题。执行mvn test,所有测试通过率100%是脚手架可用的硬性指标。
5. 常见问题与排查技巧实录:那些文档里不会写的坑
5.1 “SQL耗时监控没打印!”——五步定位法
这是新手提问最高频的问题。别急着删配置,按顺序检查:
| 步骤 | 检查项 | 正确表现 | 错误表现及修复 |
|---|---|---|---|
| 1 | 确认Profile是否为dev | java -jar demo.jar --spring.profiles.active=dev | 默认profile是default,需显式指定dev,否则PerformanceInterceptor不注册 |
| 2 | 检查MybatisPlusConfig中interceptor是否add | interceptor.addInnerInterceptor(performanceInterceptor) | 忘记调用addInnerInterceptor(),Interceptor对象创建了但没加入链路 |
| 3 | 确认SQL是否真的执行 | 在Controller里加log.info("before query"),Mapper方法里加log.info("after query") | 如果日志只打了一行,说明Controller没走到Mapper,可能是参数校验失败或路由错误 |
| 4 | 检查SQL是否低于maxTime阈值 | 将setMaxTime(1)设为1ms | 如果仍不打印,说明SQL执行<1ms(H2内存库正常),可临时设为setMaxTime(0)强制打印所有SQL |
| 5 | 排查日志级别是否为DEBUG | logging.level.com.baomidou.mybatisplus=DEBUG | 默认INFO级别会过滤DEBUG日志,需在application-dev.yml中显式设置 |
实操心得:我在客户现场遇到过一次“监控不打印”,最后发现是运维同学在服务器上设置了
JAVA_OPTS="-Dfile.encoding=UTF-8",导致SpringBoot读取application-dev.yml时profile解析失败,自动fallback到default。所以永远先看启动日志第一行:The following profiles are active: dev。
5.2 “Wrapper条件没生效!”——空值与字符串的隐式陷阱
现象:前端传?name=张三,后端wrapper.like("name", name),但SQL里没生成WHERE name LIKE ?。
根本原因有两个:
第一,String的==与equals()混淆
错误写法:
if (name != null) { // name可能是""空字符串,!=null为true,但like("")会查所有数据
wrapper.like("name", name);
}
正确写法(脚手架采用):
if (StringUtils.isNotBlank(name)) { // Apache Commons Lang3的工具方法,判断非null、非空、非空白
wrapper.like("name", name);
}
第二,MySQL的LIKE默认区分大小写
H2内存库默认不区分,但生产MySQL可能区分。当查name="张三"时,如果数据库里存的是"张三"(中文),没问题;但如果存的是"zhangsan"(拼音),like "张%"就查不到。解决方案是在Wrapper里强制转小写:
wrapper.like("LOWER(name)", name.toLowerCase()); // 需在H2和MySQL中都支持LOWER()
脚手架的application-dev.yml已配置spring.datasource.hikari.connection-init-sql=SET COLLATION_CONNECTION=utf8mb4_unicode_ci,确保字符集统一。
5.3 “批量插入1000条数据很慢!”——H2的批量提交优化
H2默认每条INSERT都单独提交事务,插入1000条要1000次磁盘IO。脚手架的schema-h2.sql末尾有优化:
-- 关闭自动提交,开启批量插入
SET AUTOCOMMIT FALSE;
INSERT INTO user (...) SELECT ... FROM system_range(1, 1000);
COMMIT;
SET AUTOCOMMIT TRUE;
如果你要插入自己的测试数据,在schema-h2.sql里照此模式写。另外,MyBatis-Plus的saveBatch()方法底层会自动分批(默认1000条/批),无需手动切片。
5.4 “Lombok生成的setter不生效!”——IDEA缓存与编译器冲突
现象:实体类加了@Data,但user.setName("张三")在IDEA里显示“Cannot resolve method ‘setName’”。
解决方案三连击:
1. File → Invalidate Caches and Restart → Invalidate and Restart 清IDEA缓存;
2. Build → Rebuild Project 强制重新编译;
3. 检查Settings → Build → Compiler → Java Compiler,确认Project bytecode version与JDK版本一致(都是1.8)。
注意:如果用Maven命令行编译(
mvn compile)成功,但IDEA里报错,100%是IDEA缓存问题,重启即可。
6. 后续演进与生产化建议:从脚手架到工业级项目
这个脚手架的定位很清晰:最小可行数据层骨架。它不包含Redis缓存、不集成RocketMQ消息队列、不提供OAuth2登录——因为那些属于业务架构范畴,强行塞进来只会增加理解成本。但当你基于它启动项目后,有三个关键演进方向必须提前规划:
第一,数据库从H2迁移到MySQL的平滑过渡
脚手架的application-prod.yml模板已预留配置:
spring:
datasource:
url: jdbc:mysql://prod-db:3306/myapp?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: ${DB_USER:root}
password: ${DB_PASSWORD:123456}
sql:
init:
mode: never # 生产环境禁用自动建表
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # MySQL字段user_name → Java属性userName
迁移时只需改三处:url、username、password,其他MyBatis-Plus配置(如PerformanceInterceptor)完全复用。唯一要注意的是H2的system_range()函数在MySQL里要换成WITH RECURSIVE或直接用程序生成数据。
第二,性能监控从开发态走向生产态
PerformanceInterceptor只适合开发阶段,生产环境必须换为APM工具。脚手架预留了SkyWalking探针接入点:
# application-prod.yml
skywalking:
agent:
service_name: ${spring.application.name}
backend_service: oap-server:11800
这样SQL耗时、慢查询、上下游调用链就全部纳入统一监控平台,不再依赖日志grep。
第三,动态查询从Wrapper升级为QueryDSL或JOOQ
当业务复杂到需要跨表关联、子查询、窗口函数时,Wrapper会变得难以维护。脚手架的pom.xml已预置JOOQ依赖坐标(注释状态),需要时取消注释,用JOOQ生成类型安全的SQL,比手写XML更安全,比Wrapper更强大。
最后分享一个个人体会:我见过太多团队把“脚手架”当成银弹,以为有了它就万事大吉。其实真正的价值不在代码本身,而在于它帮你建立了一套数据访问层的决策框架——当新需求来临时,你知道什么该用Wrapper快速实现,什么该写自定义SQL,什么该加缓存,什么该上ES。这套框架,比任何一行代码都重要。现在,去你的IDE里打开这个脚手架,跑通第一个curl,然后把它变成你下一个项目的起点。
简介:基于 JDK 8 和 IntelliJ IDEA 构建的轻量级 SpringBoot 工程模板,集成 MyBatis-Plus 3.x 全功能支持。pom.xml 已预置 mybatis-plus-boot-starter、H2 内存数据库、Lombok 等常用依赖,无需额外配置即可直接编译运行。内置标准用户管理模块,覆盖单表增删改查全流程;通过 QueryWrapper 实现 eq/like/orderBy/in 等多条件组合查询,适配常见业务筛选场景;启用 PerformanceInterceptor 插件自动打印 SQL 执行时间,辅助定位慢查询问题。代码结构清晰,主逻辑位于 src/main/java,单元测试覆盖基础操作,存放于 src/test/java。适合初学者快速理解 MyBatis-Plus 核心用法,也适用于新项目快速搭建数据访问层骨架。
&spm=1001.2101.3001.5002&articleId=162323394&d=1&t=3&u=280a82c21afd416f803a7ded5e8cf4dd)
1421

被折叠的 条评论
为什么被折叠?



