《MyBatis基础教程:Java持久层框架入门》?别被标题骗了,这是一篇踩坑血泪史

神奇之守护者
2025-12-17 18:19
阅读 567

大家好,我是阿强,一个在广州老城区住了8年的“新广漂”——说新是因为户口还没落下来,说老是因为我已经在这片骑楼林立、肠粉飘香的地方,从25岁熬到了33岁。

每天早上7点15分,我准时从荔湾老宅出发,穿过窄巷,跨过早茶摊,挤上那趟永远人贴人的地铁五号线。月薪22k,房贷6800,房租3500(没错,我和老婆还租着房子,因为首付掏空六个钱包后实在不敢再背二套),通勤两小时,代码写到眼瞎——这就是我的日常。

上周五晚上11点47分,我瘫在出租屋的旧沙发上,盯着屏幕上一行报错信息:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.example.mapper.UserMapper.selectUserById

那一刻,我真的想把电脑砸了。不是因为这个错误多难,而是——我都工作快十年了,怎么还在为这种“配置文件路径不对”的低级问题抓狂?

事情得从去年十月说起。


被逼上梁山:从Go转回Java?

去年公司搞微服务重构,领导拍脑袋决定:“新服务全用Go!轻量、并发高、云原生!” 我当时还挺兴奋,毕竟Go语法简洁,goroutine写起来确实爽。我在GitHub上狂刷gin、gorm、ent的源码,连做梦都在写go mod tidy

结果呢?三个月后,运维大哥私下跟我说:“强哥,你那个Go服务,日志格式不统一,监控埋点没做,链路追踪全是null……线上出问题根本没法查。”

更惨的是,老系统全是Java + MyBatis,新Go服务要和它对接,数据一致性靠手动对账。有一次双十一大促,用户支付成功但订单没生成,客服电话被打爆,老板在群里@我:“阿强,你今晚不睡也得把数据对平!”

那天凌晨三点,我一边跑SQL脚本,一边啃冷掉的叉烧包,突然意识到:技术栈不是越新越好,而是要看团队能不能兜得住底。

于是今年年初,我主动申请调回Java组。理由很现实:我需要稳定,需要能快速交付,需要下班后还能陪老婆吃个糖水。

而回归的第一关,就是重新拾起——MyBatis。


你以为MyBatis很简单?那是你没踩过这些坑

很多人说:“MyBatis不就是写个XML,配个mapper,完事。” 呵,说得轻巧。我第一次搭环境就翻车了。

坑一:Mapper接口找不到?可能是你的namespace写错了

我新建了个UserMapper.java,XML里写:

<mapper namespace="com.example.mapper.UserMapper">

结果一运行就报BindingException。查了半小时,发现包路径少了个s——项目结构是mappers,我手误写成mapper。这种错误IDE不报红,编译能过,运行时才炸。

实战经验:命名空间必须和接口全限定名一字不差,包括大小写。建议直接复制接口类的完整路径粘贴进去。

坑二:XML文件没被编译进target目录

我在resources下建了mapper/UserMapper.xml,但Maven打包后死活找不到。后来才发现,Maven默认只编译.java和部分资源文件,XML需要显式配置。

pom.xml里加:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
</build>

不然你就只能眼睁睁看着控制台输出:“Could not find resource mapper/UserMapper.xml”。

坑三:#{id} 和 ${id} 的区别,真的会要命

早期为了图快,我用${id}拼接SQL:

<select id="getUser" resultType="User">
    SELECT * FROM user WHERE id = ${id}
</select>

结果测试同事输入了个1; DROP TABLE user--,数据库差点被清空。还好是在测试环境,不然我这个月绩效直接归零。

记住:#{}是预编译,防SQL注入;${}是字符串替换,危险!除非你100%信任输入(比如枚举值),否则别碰。


GitHub上的轮子,真能随便抄吗?

为了省事,我在GitHub上搜“MyBatis demo”,clone了一个star 2k+的项目。本地跑起来没问题,一部署到测试环境就崩——作者用的是H2内存数据库,而我们用的是MySQL 8.0

连接串、驱动类、时区设置全都不兼容。更坑的是,他用了mybatis-spring-boot-starter 2.1.0,而我们项目锁死在1.3.2(因为老Spring Boot版本)。

那一周我光是调版本兼容性就掉了5斤肉。最后痛定思痛:GitHub上的demo可以看思路,但绝不能直接搬生产。尤其是涉及数据库、安全、事务的部分,必须自己一行行敲,理解每一行的作用。


为什么我坚持用XML,而不是注解?

很多人吹MyBatis-Plus或注解式开发,觉得XML太重。但在我这种老项目里,复杂查询、动态条件、多表关联才是常态。

比如这个需求:“用户可按姓名、手机号、注册时间范围、状态多条件筛选,且某些字段为空时不参与查询。”

用注解写?你会写出这种地狱代码:

@Select("<script>SELECT * FROM user " +
        "<where>" +
        "<if test='name != null'>AND name LIKE CONCAT('%', #{name}, '%')</if>" +
        "<if test='phone != null'>AND phone = #{}</if>" +
        // ... 写到吐
        "</where>" +
        "</script>")
List<User> queryUsers(...);

而XML里,清晰明了:

<select id="queryUsers" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="phone != null">
            AND phone = #{phone}
        </if>
        <!-- 其他条件 -->
    </where>
</select>

XML把SQL逻辑和Java代码分离,后期维护、DBA review、性能优化都方便得多。 别被“注解更现代”的营销话术忽悠了。


回到现实:技术只是工具,生活才是主线

写这篇文章的时候,已经是周日凌晨1点。老婆在隔壁房间睡着了,桌上还放着她给我留的杨枝甘露。明天还要早起挤地铁,继续debug那个该死的分布式事务。

但你知道吗?这次我不焦虑了。

因为我终于明白:技术没有高低贵贱,能解决问题的就是好技术。 Go也好,Java也罢,MyBatis还是JPA,都不过是我们养家糊口的工具。真正重要的是——你能不能在有限时间内,交付稳定可靠的系统,然后准时下班回家。

MyBatis或许不够“潮”,但它稳、成熟、社区庞大,出了问题Stack Overflow上一搜就有答案。对于背着房贷、上有老下有小的中年程序员来说,稳定比炫技重要一万倍。


最后一点真心话

如果你刚入行,看到这篇文,别笑我保守。等你经历过线上事故、凌晨报警、客户投诉,你就会懂:所谓“高级工程师”,不是会多少框架,而是知道什么时候该用最笨但最稳的办法。

MyBatis的基础,无非就是:

  • 接口 + XML(或注解)绑定
  • SQL写对,参数传对
  • 事务管理别漏
  • 日志打开,方便排查

剩下的,都是经验堆出来的肌肉记忆。

我把整理好的MyBatis避坑清单放到了GitHub(搜“guangzhou-coder/mybatis-pitfalls”),里面有我踩过的所有雷、配好的starter模板、以及一份给新人的速查手册。Star不重要,能帮你少熬一个夜,值了。


写完这篇,我去洗把脸,明天还得改那个Invalid bound statement的bug。不过这次,我知道问题出在哪了——mapper扫描路径没配对。

生活就像MyBatis的XML,看似繁琐,但只要路径对了,一切都能映射成功。

共勉。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝