MyBatis基础教程:Java持久层框架入门——一个成都后端仔的实战复盘

500制造机
2025-12-13 09:43
阅读 648

上周五晚上十点,办公室只剩我和运维小哥还在对峙。他盯着我改了三遍的SQL语句直摇头:“你这分页查全表,明天大促直接把DB干趴。”我一边在VSCode里疯狂按Ctrl+Z,一边默默打开MyBatis文档——是的,又一次被现实毒打。

我是成都某上市电商公司技术中台团队的一员,日常负责核心交易链路的抽象和复用。坐标天府软件园,生活节奏确实舒服,但一到双11、618,那叫一个“福报拉满”。我们团队有个不成文的规定:能不用原生JDBC就别用,否则半夜报警电话第一个打给你。

今天这篇不是教科书式教程,而是我踩坑、填坑、再踩坑后总结出的MyBatis入门最佳实践。如果你正准备面试,或者刚接手一个老项目发现DAO层全是手写PreparedStatement(别问,问就是祖传代码),那这篇可能救你命。


为什么是MyBatis?因为ORM没那么“香”

很多新人一上来就喊“我要上Hibernate!”,结果上线三天就哭着改回来。为啥?过度封装 = 失控

在我们中台,数据库查询必须精确到毫秒级响应,还要支持动态拼接条件(比如运营后台的万能筛选器)。Hibernate的HQL和缓存机制在这种场景下简直就是黑盒炸弹。去年双11前压测,一个@OneToMany懒加载没关,直接导致线程池爆满,当时真的想砸电脑。

MyBatis的好处在于:SQL你写,映射你控,性能你说了算。它不替你做决定,只帮你省掉90%的样板代码。对于我们这种既要灵活性又要可控性的中台团队,简直是天作之合。


实战配置:从零搭建一个靠谱的MyBatis环境

别再用XML配置文件堆成山了!现在主流都是 Java Config + 注解 + XML混合。我们团队的标准做法如下:

// DataSourceConfig.java - 数据源交给Spring Boot自动配置
@Configuration
@MapperScan("com.yourcompany.platform.mapper") // 扫描Mapper接口
public class MyBatisConfig {

    @Bean
    @ConfigurationProperties("mybatis.configuration")
    public org.apache.ibatis.session.Configuration mybatisConfiguration() {
        org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
        config.setMapUnderscoreToCamelCase(true); // 自动驼峰转换,告别as xxx_yyy
        config.setLogImpl(StdOutImpl.class); // 开发时打印SQL,上线记得关!
        return config;
    }
}

application.yml 里配好这些关键项:

mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false  # 默认关闭二级缓存!除非你真懂

血泪教训:二级缓存在分布式环境下极易失效,我们吃过亏。现在所有缓存逻辑都走Redis,MyBatis只干它该干的事——SQL映射。


Mapper怎么写?接口 + XML 是王道

虽然MyBatis支持纯注解(@Select等),但复杂查询必须用XML。原因很简单:可读性、可维护性、支持动态SQL。

来看一个典型的订单查询Mapper:

// OrderMapper.java
public interface OrderMapper {
    List<Order> selectOrdersByConditions(@Param("userId") Long userId,
                                         @Param("statusList") List<String> statusList,
                                         @Param("startTime") LocalDateTime startTime);
}

对应的 OrderMapper.xml

<select id="selectOrdersByConditions" resultType="com.yourcompany.domain.Order">
    SELECT order_id, user_id, status, create_time
    FROM t_order
    WHERE 1=1
    <if test="userId != null">
        AND user_id = #{userId}
    </if>
    <if test="statusList != null and statusList.size() > 0">
        AND status IN
        <foreach item="item" collection="statusList" open="(" separator="," close=")">
            #{item}
        </foreach>
    </if>
    <if test="startTime != null">
        AND create_time >= #{startTime}
    </if>
    ORDER BY create_time DESC
</select>

注意几个最佳实践

  • <if> 而不是 AND (xxx OR 1=1),前者更安全;
  • @Param 注解强制命名参数,避免MyBatis猜错;
  • resultType 写全路径,避免同名类冲突。

防坑指南:那些让我通宵的“小问题”

1. 字段映射翻车现场

数据库字段 order_no,Java实体叫 orderNo —— 如果忘了开 mapUnderscoreToCamelCase,查出来全是null。测试同学拿着空数据来找你时,那种绝望……

2. 事务失效的玄学

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    // 注意:必须是public方法!
    @Transactional
    public void createOrder(Order order) {
        orderMapper.insert(order);
        // ...其他操作
    }
}

曾经有个同事把方法写成private,事务当然不生效。运维监控看到DB连接数飙升,还以为是DDoS攻击。

3. 分页查询的性能陷阱

别再用 LIMIT #{offset}, #{size} 做深度分页了!超过1万条记录时,MySQL会扫描前面所有行。我们现在用游标分页(基于create_time + id):

SELECT * FROM t_order 
WHERE (create_time, id) > (#{lastTime}, #{lastId})
ORDER BY create_time ASC, id ASC
LIMIT #{size}

产品经理说“用户不会翻100页”,但爬虫会。


面试题挑战:MyBatis高频考点拆解

最近帮团队面试,发现很多人对MyBatis的理解还停留在“会用就行”。分享几道我们常问的题:

问题 考察点 正确姿势
#{}和${}有什么区别? SQL注入防护 #{}预编译,${}直接拼接(慎用!)
resultMap和resultType怎么选? 映射复杂度 简单对象用resultType,关联查询用resultMap
一级缓存什么时候失效? 事务边界理解 SqlSession关闭或执行update/delete时清空

特别提醒:不要说“MyBatis缓存很牛”。在生产环境,我们基本只依赖一级缓存(SqlSession级别),且生命周期极短。二级缓存?除非你能回答清楚它的LRU策略、脏读风险、集群同步问题,否则别碰。


性能与运维:上线不是终点

在我们中台,每个MyBatis应用上线前必须过三关:

  1. 慢SQL审查:通过Arthas监控实际执行计划;
  2. 连接池配置:HikariCP的maximumPoolSize根据DB最大连接数动态调整;
  3. SQL日志脱敏:用户手机号、身份证号不能打到日志里!

曾经有个实习生把logImpl设成Slf4jImpl,结果全量SQL打到ELK,存储费用暴涨——财务部直接找CTO谈话。从此我们加了CI检查:禁止在prod profile开启SQL日志


写在最后:框架是工具,人才是核心

MyBatis再强大,也挡不住乱写SQL的人。我们团队的文化是:SQL即代码,要CR、要测试、要敬畏

如果你刚入门,别急着学插件、扩展、自定义TypeHandler。先把<foreach><choose>association这些基础标签玩熟,把驼峰映射、事务边界、参数传递搞明白。这些才是日常开发中最常打交道的东西。

上周那个分页问题,最后我用游标方案重写,QPS从1200提升到8500,运维小哥终于对我笑了。走出公司已是凌晨,成都的夜风微凉,但心里踏实——搞定一个性能问题,比喝十杯奶茶都爽

共勉。下次双11,希望不再通宵。

评论 0

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