Java求职第50天复盘:MyBatis-Plus建表与环境排查
发布时间:2026/10/8 9:30:31 锦皓数字建站

今天是我Java求职学习打卡的第50天。说实话坚持到这个天数早就不靠鸡血了靠的是把每天遇到的问题记下来、再回头串起来的习惯。第50天我没有急着学新框架而是把前七周的笔记翻出来做了一次全量回看顺便把最近大家高频搜索的Java问题过了一遍。这篇就当是day50的复盘记录给同样在准备Java求职的朋友做个参考。如果你正处于从零基础向初级/中级Java工程师过渡的阶段我的经验是刷题、背面试题、看源码都只是手段真正决定能不能上岸的是你能不能把零散的知识点连成一张网。第50天就是一个非常好的“织网”时间点所以这篇内容我会用几个实际踩过坑的模块来讲MyBatis-Plus实体类建表SQL怎么写、排序和集合框架怎么突击、JDK多版本和启动失败怎么排查、以及哪些面试基础题值得内化。每块都有真实场景和可复现的步骤不是纯理论堆砌。1. 第50天的复盘视角把50天塞进一天1.1 为什么学满50天必须停下来做一次回看前49天我给自己定的计划是按模块推进第1周Java基础语法和面向对象第2周集合和泛型第3周并发和JVM第4周MySQL和JDBC第5周Spring和Spring Boot第6周MyBatis/MyBatis-Plus第7周开始做项目加刷算法。每个模块当时都感觉学明白了但到了第45天左右问题暴露得很明显让我说HashMap底层结构能说个大概让我写ConcurrentHashMap的并发机制就卡壳昨天刚看的Transactional失效场景今天面试模拟时只记得两种。这不是记忆力差而是典型的“输入但没有沉淀”。所以第50天我没有安排新知识而是把所有笔记重新打开按“能解决什么问题 - 核心原理是什么 - 面试会怎么追问 - 我能不能手写出来”四步过了一遍。这个方法比我预期中更管用尤其适合准备Java求职的人因为面试官很少按教材顺序问问题他可能同时涉及集合、JVM、Spring的交叉知识。你必须能在一个场景里把多个模块串起来。我整理了前50天的主线大概是这样阶段学习内容对应能力目标第1-2周Java语法、面向对象、异常、常用API能写可运行的Java程序第3周集合框架、泛型、Stream理解常见数据结构的选型第4周并发编程、JMM、JVM基础能解释线程安全和GC第5周MySQL、JDBC、数据库设计能写出合理的表和查询第6周Spring IOC/AOP、Spring Boot能搭建一个Web服务第7周MyBatis/MyBatis-Plus、项目实战能独立完成一个CRUD权限模块第8周起算法刷题、面试题整理、项目复盘为求职面试做准备回看你会发现前面学的东西其实都是为最后两步服务的。如果你现在也在学习打卡阶段我真心建议每7天或每50天做一次大回看不然到投简历时会发现自己连“项目用了什么技术栈”都讲不顺。1.2 从热搜词看实际求职者都在搜什么第50天晚上我翻了翻最近Java相关的搜索词发现了几个高频词mybatisplus根据java实体类生成创建表的sql语句、冒泡排序java、sort函数用法java、java环境变量使用多个jdk、idea 编译时进程堆大小调整为8000还是报错OOM、spring boot mybatis 的 java 开源多商户跨境商城源码。这些词很有意思它们不是教科书目录而是大家在真实求职和做项目时被卡住的地方。这些词放到一起看其实暴露了三条学习主线第一需要提升数据库开发效率所以有人想从实体类直接生成建表SQL第二需要补基础算法所以排序、Comparator这类问题被反复搜第三需要应对真实环境问题比如多JDK切换、启动失败、IDEA内存溢出。如果你发现自己也在搜这些词不用焦虑这恰恰说明你已经进入求职准备的实战阶段了。关键是别让这些搜索词变成收藏夹里的碎片要像我前面说的把每个问题归到“基础、框架、环境、项目”四个桶里然后用一篇文章或一个demo把它吃透。2. MyBatis-Plus实体类自动生成建表SQL核心原理与实操2.1 没有现成API我如何用反射撸一个建表工具先讲场景。我在做一个练习项目时数据库表已经有20多张了每次实体类加字段都要手动去执行ALTER TABLE特别容易漏。当时就搜到热搜词里那个问题能不能根据Java实体类直接生成建表SQL老实说MyBatis-Plus本身没有提供“实体类转CREATE TABLE”的开箱API。它的TableInfoHelper是给内部SQL注入用的但我们可以借助它拿到实体类对应的表名、字段名、字段类型、主键策略等元数据。我写了一个很小的工具类核心思路是通过TableInfoHelper.getTableInfo(entityClass)得到TableInfo然后遍历tableInfo.getFieldList()拿到每个TableFieldInfo的column列名和propertyTypeJava属性类型再按类型映射规则拼成MySQL的DDL。下面是我验证过的一段核心代码仅供参考import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import java.lang.reflect.Field; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; public class DdlGenerator { private static final MapClass?, String TYPE_MAP new HashMap(); static { TYPE_MAP.put(Integer.class, INT); TYPE_MAP.put(Long.class, BIGINT); TYPE_MAP.put(String.class, VARCHAR(255)); TYPE_MAP.put(LocalDateTime.class, DATETIME); TYPE_MAP.put(BigDecimal.class, DECIMAL(20,2)); TYPE_MAP.put(Boolean.class, TINYINT(1)); } public static String generateCreateTableSql(Class? entityClass) { TableInfo tableInfo TableInfoHelper.getTableInfo(entityClass); if (tableInfo null) { throw new IllegalArgumentException(实体类未注册到MyBatis-Plus: entityClass.getName()); } StringBuilder sql new StringBuilder(CREATE TABLE ) .append(tableInfo.getTableName()) .append( (\n); // 主键 if (tableInfo.getKeyColumn() ! null) { sql.append( ).append(tableInfo.getKeyColumn()).append( ) .append(TYPE_MAP.getOrDefault(tableInfo.getKeyType(), BIGINT)) .append( NOT NULL AUTO_INCREMENT COMMENT 主键,\n); } for (TableFieldInfo fieldInfo : tableInfo.getFieldList()) { if (fieldInfo.isWithUpdateFill()) { // 自动填充字段一般由数据库或MyBatis-Plus维护这里跳过或单独处理 continue; } String columnType TYPE_MAP.get(fieldInfo.getPropertyType()); if (columnType null) { columnType VARCHAR(255); } sql.append( ).append(fieldInfo.getColumn()).append( ) .append(columnType) .append( DEFAULT NULL COMMENT ) .append(fieldInfo.getProperty()) .append(,\n); } sql.append( PRIMARY KEY ().append(tableInfo.getKeyColumn()).append()\n) .append() ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT) .append(entityClass.getSimpleName()) .append(;); return sql.toString(); } }这里有个关键点TableInfoHelper.getTableInfo执行时需要实体类已经被MyBatis-Plus的BaseMapper扫描注册过。如果你直接在单元测试里调可能得到null所以要么先初始化MybatisConfiguration要么在Spring容器启动后再调用。2.2 字段映射和注解解析的细节写这个工具时最容易出错的是Java类型到MySQL类型的映射。比如LocalDateTime如果不映射默认会被当成VARCHAR(255)生成的表完全没法用。我的建议是先做一个MapClass?, String把常用类型都放进去另外针对BigDecimal做精度参数映射因为DECIMAL(20,2)和DECIMAL(10,2)差别很大。注解的优先级也要处理如果字段上写了TableField(user_name)那组装SQL时必须直接用注解里的列名而不是属性名的驼峰转下划线。同理TableId(type IdType.AUTO)表示自增主键TableLogic标识逻辑删除字段TableField(fill FieldFill.INSERT_UPDATE)标识自动填充字段。生成DDL时自动填充字段最好单独处理因为它通常有数据库默认值直接生成DEFAULT NULL反而容易埋雷。还有一个经常被忽略的问题是列名映射MyBatis-Plus默认开启驼峰转下划线但如果你没有开mapUnderscoreToCamelCaseTableFieldInfo.getColumn()返回的可能就是属性原名。为了保险我在工具里优先取注解值取不到再用StringUtils做camelToUnderline转换。2.3 生成SQL之后要手动补什么说实话自动生成的DDL只能帮你完成80%的建表工作剩下20%必须手动处理不然真要出生产事故。我实操后的补全清单是唯一索引比如用户表的phone字段应该加UNIQUE KEY但实体类上没有直接表达索引的注解除非你自己扩展所以需要手动加。联合索引订单表按merchant_id create_time查询很常见生成的SQL不会自动创建必须手动追加。字段注释代码里生成的注释是英文属性名不够直观建议手动改成中文。字符集和排序规则utf8mb4和utf8mb4_general_ci通常是我建表的默认选择但如果你有特殊排序需求得手动指定。软删除字段如果你用了TableLogic逻辑删除字段建议生成TINYINT(1) DEFAULT 0并在SQL里加注释避免大家误以为是业务字段。说白了这个工具适合开发环境快速铺表不适合直接推到生产。生产环境的表结构变更还是建议走正式的迁移脚本配合审查流程。3. 排序突击冒泡、Comparator和集合框架的联动3.1 手写冒泡看懂它为什么是O(n^2)就能过面试算法题里被搜得最多的就是“冒泡排序java”我猜原因是面试官太爱从这个最简单的问题开始了。它简单到你不太可能写错但想拿高分你得能在一分钟内写出优化版并能解释时间复杂度和稳定性。public static void bubbleSort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } // 如果某一轮没有发生交换说明已经有序提前结束 if (!swapped) { break; } } }这段代码里最重要的不是循环本身而是swapped这个标志。它把最好情况的时间复杂度从O(n^2)降到O(n)而平均和最坏情况还是O(n^2)。空间复杂度是O(1)而且冒泡是稳定排序——因为只有在前一个元素大于后一个时才交换相等元素不会相对移动。面试官问你冒泡的目的往往是为了引出一连串对比。我建议你顺手把选择排序、插入排序、快速排序、归并排序放在一起比较排序算法平均时间复杂度最坏时间复杂度空间复杂度稳定性冒泡排序O(n^2)O(n^2)O(1)稳定选择排序O(n^2)O(n^2)O(1)不稳定插入排序O(n^2)O(n^2)O(1)稳定快速排序O(n log n)O(n^2)O(log n)不稳定归并排序O(n log n)O(n log n)O(n)稳定能自己画出这张表比背十篇面经都管用。3.2 sort、Comparator和稳定性的实战用法“sort函数用法java”也是高频搜索词。Java里排序入口通常有三个Arrays.sort、Collections.sort/List.sort、stream().sorted()。很多人只知道调用但对实现原理和稳定性一知半解。重点说两个容易被问倒的细节Arrays.sort对基本类型数组使用双轴快速排序它不是稳定排序对对象数组使用TimSort它是稳定排序。Collections.sort底层实际上是List.sort再调用Arrays.sort所以对于对象集合来说它是稳定的。如果你要对一个ListEmployee先按年龄升序、再按姓名首字母升序可以这样写ListEmployee list new ArrayList(); list.sort(Comparator.comparingInt(Employee::getAge) .thenComparing(Employee::getName));Comparator.comparing返回的Comparator值得好好研究。它先用keyExtractor提取排序键再通过Map.Entry的compareTo比较。链式调用thenComparing可以组成多级排序面试时如果能把这个讲清楚比单纯背API有用得多。还有一个高频小题目如何按value对HashMap排序正确做法是先把entrySet放进List再用Map.Entry.comparingByValue()排序HashMapString, Integer map new HashMap(); // ... 放入数据 ListMap.EntryString, Integer entryList new ArrayList(map.entrySet()); entryList.sort(Map.Entry.comparingByValue());这里很多人会直接问“HashMap不是无序的吗排序有意义吗”答案是我们排序的是entry集合不是HashMap本身所以没问题。如果你需要保持排序后的顺序可以用LinkedHashMap接收结果。3.3 把排序放回集合框架里一起记第50天回看时我发现排序算法不能孤立地学它和集合框架是绑定的。面试官考TreeMap时其实就在考排序。TreeMap根据key自然排序或自定义Comparator排序LinkedHashMap维护插入顺序PriorityQueue默认小顶堆底层就是堆排序。如果你把这些放到一起理解回答问题时就能主动引出关系。比如PriorityQueue的默认排序是升序但你可以传Comparator.reverseOrder()变成大顶堆。堆排序的时间复杂度是O(n log n)但空间O(1)。面试题“如何用堆实现Top K”就是从这来的。我学习时的经验是把“排序算法”和“有序集合”放在同一个笔记页里左边是算法右边是集合类然后用代码demo验证。这样你回答的不是孤立的“什么是稳定排序”而是能主动关联到JDK源码里哪些地方用了稳定排序。4. JDK多版本与启动失败环境问题才是劝退第一关4.1 多JDK共存JAVA_HOME、PATH和IDE各有各的脾气很多自学Java的人电脑里可能同时装着JDK8、JDK11、JDK17。项目要用JDK8新框架要求JDK17切换时最常见的现象是改了JAVA_HOME但命令行执行java -version还是旧版本。原因很简单PATH环境变量里有一个具体的JDK路径排在%JAVA_HOME%\bin前面。Windows的PATH会按顺序查找先找到先执行。我的建议是把C:\Program Files\Java\jdk1.8.0_xx\bin这种写死路径全部从PATH里删掉只保留%JAVA_HOME%\bin然后每次切换JAVA_HOME后重新打开命令行。如果你用Windows的setx命令永久设置过环境变量记得重启终端或电脑再验证。Linux环境则推荐用update-alternatives管理sudo update-alternatives --config java但这个只切换java命令javac、jar等命令也要看PATH里的优先级所以还要检查/usr/bin/java软链接是否指向对。IDEA里更麻烦它有自己的一套配置Project Structure - Project - SDK、Settings - Build Tools - Gradle - Gradle JVM、Maven - Runner - JRE。这三个地方如果不一致就会出现命令行里java -version对了IDEA编译还是用错版本。第50天我专门列了一张表每次换JDK就照着检查位置作用常见坑JAVA_HOME命令行工具找JDK被PATH中写死路径覆盖Project SDK决定项目语法级别版本和pom里不一致Gradle/Maven JVM决定构建进程JDK默认选了IDEA自带的JBRIDEA启动器VM决定IDE本身运行改错文件导致IDEA无法启动4.2 启动失败排查链路“java启动失败怎么解决”这个热搜词太真实了。我见过不少朋友项目启动报错直接截图发群里但信息只有最后几行。正确的排查链路应该是先看完整日志再定位进程最后用工具取现场。我的固定套路是用jps -l查看Java进程是否存在如果进程不存在说明启动就挂了需要看控制台或日志文件。用jstack pid查看线程状态重点看有没有BLOCKED、WAITING堆积。用jmap -heap pid查看堆内存使用情况确认是不是内存分配不足。如果是通过java -jar启动可以加-Xlog:gc*JDK9或-verbose:gc看GC日志。最常见的启动失败原因其实很简单端口被占用、数据库连接串写错、依赖冲突导致NoSuchMethodError、配置文件编码问题。论顺序我会先查8080端口netstat -ano | findstr 8080 # Windows lsof -i:8080 # macOS/Linux找到占用进程后杀掉或改端口。再看日志里的第一条异常栈而不是只看最后一句“BUILD FAILURE”。如果是Spring Boot项目启动时加--debug能看到自动配置报告能快速定位哪个依赖没生效。4.3 IDEA编译堆调到8000还OOM先搞清楚调的是谁热搜词里“进程堆大小调整为8000还是报错OutOfMemoryError”这个问题我太有共鸣了。很多人的第一反应是去改IDEA安装目录下的idea64.exe.vmoptions把-Xmx改成8000m。但如果你遇到的是编译时报OOM改这个文件不一定有效。IDEA编译Java代码时用的是独立的“构建进程”它有自己的堆设置。你必须打开Settings - Build, Execution, Deployment - Compiler - Build process heap size (Mbytes)默认只有700MB小项目还好大项目动辄需要2GB以上。另外如果你用了Maven还要检查MAVEN_OPTS如果用了Gradle需要在gradle.properties里配org.gradle.jvmargs-Xmx4g -XX:MaxMetaspaceSize1g。把这些地方都配好再把idea64.exe.vmoptions里的-Xmx调节到一个合理值就不会一头雾水地乱调8000了。还有一个容易踩的坑物理内存只有8GB你硬分配给IDEA 8GB操作系统和浏览器还要用内存反而更容易OOM。我自己的经验是IDEA主进程给-Xmx4g编译进程给-Xmx2048m其余交给系统基本够用。如果还是OOM优先检查是不是依赖了过多插件或者项目里有循环依赖导致编译爆炸。5. 求职阶段的面试题取舍哪些必须内化哪些只要会白话5.1 高频八股的正确背法HashMap、JVM、并发三块连成体系到了第50天如果再去零零散散地搜“java面试题”效率会很低。我的做法是把高频题拆成三张卡片集合、JVM、并发。每张卡片都不是背答案而是用“为什么”串起来。以HashMap为例我给自己设置的面试剧本是底层结构数组链表红黑树。put过程计算key的hash - 定位数组下标 - 如果是空直接放入 - 否则遍历链表 - 遇到相同key就替换value - 链表长度超过8且数组长度大于等于64就转红黑树。扩容时机当负载因子超过0.75数组长度扩容到原来的2倍。为什么用0.75而不是0.5或1这是时间和空间的一个折中。太高会提高hash冲突概率太低则浪费空间。为什么并发不安全因为多个线程同时扩容时可能出现数据覆盖和环形链表。这五句话比背一篇长文更有用面试官听到你能解释“为什么”时基本就会放过这个点去问下一个了。JVM和并发也是一样关键是建立模型JVM内存模型 - 哪些区域会发生OOM - 对象回收判断 - 垃圾回收器并发 - 可见性/原子性/有序性 - volatile/synchronized/Lock - CAS - 线程池。5.2 项目亮点多商户商城的行级权限与数据一致性再看热搜词里的“spring boot mybatis 的 java 开源多商户跨境商城源码”这类关键词背后其实是大家希望有一个真实项目写在简历里。但项目不能只停留在“我会用Spring BootMyBatis做CRUD”的程度面试官一定会问细节特别是多商户场景下的数据隔离和权限控制。行级权限是我认为最值得展开的点。多商户系统里每个商户只能看自己的订单、商品、客户如果所有查询都靠手动加WHERE merchant_id ?团队协作时很容易漏。我的做法是写一个MyBatis拦截器或注解类似DataScope在执行SQL前动态解析并拼接数据权限条件。举个例子订单Mapper里的方法加一个自定义注解DataScope(column merchant_id) ListOrder selectOrderList(Param(query) OrderQuery query);拦截器在Executor执行前拦截SQL取出merchant_id条件拼到SQL尾部。这样做的好处是业务代码不感知权限坏处是SQL复杂时拼接容易出错需要认真测试。数据一致性也是个必问项。订单、库存、支付之间怎么保证不超卖常见方案本地消息表 定时任务/消息队列最终一致如果要强一致可以考虑Seata分布式事务。但面试时你要能说清楚不同方案牺牲了什么。比如2PC性能差、TCC实现复杂、本地消息表有延时。如果只是日常练习我会优先把“乐观锁扣库存”做对UPDATE stock SET count count - 1 WHERE id ? AND count 1再配合重试机制。5.3 被热搜点名的几个冷门点Cleaner、动态编译、AES、RestTemplate第50天回看时我还注意到几个不算高频但搜得很准的冷门词java cleaner原理、java 动态编译、java aes解密、java resttemplate。如果你还有富余时间这些可以作为加餐但不要本末倒置。java.lang.ref.Cleaner是JDK9开始用来替代finalize()的清理器。它基于PhantomReference和ReferenceQueue当对象被GC回收时Cleaner会拿到引用并调用清理动作。这里的关键是清理动作运行在独立线程不能假设某个资源一定在“某个时刻”被释放面试时能说出“非确定性”就是加分项。javax.tools.JavaCompiler可以做运行时编译Java源码比如在线判题系统里编译用户代码。但真正生产环境很少有人直接这么干因为它涉及类加载器、安全管理器、进程资源控制复杂度和风险都很高。AES解密经常被问到是“为什么我的解密结果是乱码”。多数情况是模式/填充不对比如加密用了AES/CBC/PKCS5Padding解密却用了AES/ECB/PKCS5Padding或者IV没有传递。我建议把加解密封装成统一工具类密钥和IV统一从配置读取。RestTemplate在Spring 5之后默认推荐用WebClient但老项目里还有大量RestTemplate代码。面试问它大概率是想考超时设置和拦截器。自己封装时一定要设置connectTimeout和readTimeout不然默认行为坑你没商量。最后再分享一个我第50天悟出来的经验学习Java求职最怕什么都要抓。今天热搜词给了你很多线索但你要做的是把它们归类然后用“为什么”去连接。能把MyBatis-Plus建表、排序稳定性、JDK版本切换、IDEA内存这些问题都讲透你的底子已经不差了。下一步就是把它们放进项目场景里多写多测让每个知识点都能在简历上变成真正的竞争力。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。