MyBatis入门没那么难,但坑真不少

变量命名困难户
2025-12-28 14:40
阅读 1555

上周五晚上十点半,我正窝在工位上用Vim敲着一个动画过渡效果的JS逻辑,突然钉钉“叮”了一声——后端小哥甩过来一个链接:“兄弟,帮看看这个SQL查得慢不慢?”点开一看,好家伙,MyBatis的XML里嵌了三层<foreach>,还夹杂着LIKE '%${value}%'。那一刻我真想把键盘砸了:咱前端都快被运营催成陀螺了,你还给我整这种性能炸弹?

说来也巧,最近我在准备跳槽(阿里三年多,双11熬过三轮,是时候换个空气了),刷面试题时发现,MyBatis几乎是Java后端岗的必考项,连一些偏全栈的岗位也会问几句。虽然我主攻前端,但跟后端联调、看日志、查慢SQL这些事,哪次不是亲力亲为?尤其去年双11大促期间,我们一个商品详情页接口因为MyBatis配置不当,缓存穿透直接打崩数据库,CTO半夜拉群@所有人,那场面至今记忆犹新。

所以,与其每次都被后端甩锅说“前端传参不对”,不如自己动手搞明白这玩意儿到底怎么玩。今天这篇,就当我这个Vim党+前端工程师跨界踩坑的实战笔记,给想了解MyBatis的同学铺条路。


为什么是MyBatis?不是JPA,也不是手写JDBC?

先说背景。我们系统早期用的是纯JDBC,每次查个用户信息都要写十几行try-catch,代码又臭又长。后来团队引入MyBatis,主打一个“SQL和代码分离”——SQL写在XML里,Java只管调用接口。好处很明显:

  • SQL可读性高,DBA看了直呼内行
  • 动态查询(比如运营后台那种“按条件筛选订单”)写起来不痛苦
  • 跟Spring Boot集成贼顺,自动装配一配,直接@Autowired注入

反观JPA(比如Hibernate),虽然“对象关系映射”听起来高大上,但在我们这种高并发、复杂查询多的场景下,生成的SQL往往不够精准,调优成本反而更高。而Go语言那边,很多团队直接用gorm或原生SQL,反倒更灵活——这让我一度怀疑,是不是Java生态太爱封装了?


三步走:从零跑通一个MyBatis查询

别被文档吓到,其实核心就三件事:配置、接口、SQL

第一步:Maven依赖 + 配置文件

<!-- pom.xml -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

然后在application.yml里配数据源(省略密码等敏感信息):

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/shop_db?useSSL=false&serverTimezone=UTC
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver

注意:别忘了加mybatis.mapper-locations=classpath:mapper/*.xml,不然XML文件找不到,启动直接报错,我第一次就栽这儿了。

第二步:写Mapper接口

// UserMapper.java
public interface UserMapper {
    User selectById(Long id);
    List<User> selectByStatus(@Param("status") Integer status);
}

这里有个坑:方法参数超过一个时,必须加@Param注解,否则XML里拿不到变量名。别问我怎么知道的,上次联调时我传了个statuspage,结果XML里写成#{status}却报“找不到参数status”,差点以为是前端JSON格式错了。

第三步:写XML映射文件

<!-- mapper/UserMapper.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.mapper.UserMapper">
    
    <select id="selectById" resultType="com.example.model.User">
        SELECT * FROM users WHERE id = #{id}
    </select>

    <select id="selectByStatus" resultType="com.example.model.User">
        SELECT * FROM users 
        WHERE status = #{status}
        ORDER BY created_at DESC
    </select>
</mapper>

重点来了:namespace必须和接口全路径一致!大小写、包名一个都不能错。有一次我把UserMapper写成userMapper,IDE没报错,但运行时直接404,排查半小时才发现是XML命名空间写歪了。


运营需求来了?动态SQL救你命

说到运营,他们最爱提这种需求:“我要能按用户ID、手机号、注册时间范围、状态……任意组合筛选!” 如果用JDBC,你得拼字符串,SQL注入风险拉满。而MyBatis的<where> + <if>组合,简直为此而生:

<select id="searchUsers" resultType="User">
    SELECT * FROM users
    <where>
        <if test="userId != null">
            AND id = #{userId}
        </if>
        <if test="phone != null and phone != ''">
            AND phone LIKE CONCAT('%', #{phone}, '%')
        </if>
        <if test="startTime != null">
            AND created_at >= #{startTime}
        </if>
        <!-- 更多条件... -->
    </where>
</select>

注意两点:

  1. <where>会自动去掉第一个AND,避免语法错误
  2. 字符串判空别只写!= null,还得加!= '',否则前端传个空字符串,SQL变成LIKE '%%',全表扫描警告!

性能优化:别让MyBatis拖垮你的双11

作为经历过双11的人,我对“慢SQL”有PTSD。MyBatis本身不慢,但用不好就是灾难。分享几个生产环境血泪经验:

问题 现象 解决方案
N+1查询 查100个订单,每个订单再查用户,共101次SQL <collection>做关联查询,或改成分页+批量ID查询
大结果集无分页 运营导出全量用户,内存爆了 强制加LIMIT,或用RowBounds分页(但慎用,性能差)
模糊查询无索引 LIKE '%关键词%' 导致全表扫描 改用Elasticsearch,或限制只能前缀匹配LIKE '关键词%'

特别提醒:MyBatis的一级缓存(SqlSession级别)在线程池环境下基本无效,别指望它扛住高并发。真要缓存,老老实实用Redis,或者开启二级缓存(但要注意脏读问题)。


面试题高频考点:MyBatis怎么和Spring整合的?

最近帮朋友模拟面试,发现这个问题几乎必问。简单说:MyBatis-Spring通过MapperFactoryBean把Mapper接口代理成Spring Bean。当你@Autowired UserMapper时,Spring其实注入的是一个动态代理对象,调用方法时会委托给SqlSession执行SQL。

底层原理涉及BeanDefinitionRegistryPostProcessorClassPathMapperScanner,但面试时你只要说出“MyBatis-Spring利用Spring的扩展点,自动扫描Mapper接口并注册为Bean”就够了。再深入点,可以聊聊SqlSessionFactoryBean如何创建SqlSessionTemplate——不过这时候面试官可能已经在点头了。


写在最后:技术没有银弹,只有合适与否

说实话,作为一个前端,写这篇MyBatis教程有点“越界”。但在这个前后端边界越来越模糊的时代,懂一点后端,联调时不背锅;懂一点数据库,排查问题不抓瞎。更何况,面试时你能聊清楚MyBatis的缓存机制、动态SQL原理,HR都会觉得你“技术视野广”。

至于要不要学Go?我觉得不用焦虑。Java在企业级应用里还是稳如老狗,MyBatis这套玩法短期内不会淘汰。倒是那些整天喊“抛弃Java”的,可能还没经历过凌晨三点被DBA电话叫醒改SQL的痛。

好了,Vim保存退出,我去改那个动画bug了。希望这篇能帮你少踩几个坑——毕竟,程序员的时间,应该花在创造价值上,而不是重复造轮子,或者背锅。

评论 0

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