MyBatis基础教程:Java持久层框架入门

神奇_月亮
2025-12-16 17:42
阅读 556

上个月,我终于从裸辞的“gap天堂”跌回了现实。半年前在某大厂后端组卷到怀疑人生——每天睁眼就是双11压测、凌晨三点改SQL、产品经理半夜钉钉发“这个需求很简单”的时候,我一咬牙,把电脑合上,跟HR说“老子不干了”。结果躺平了三个月就开始焦虑,五个月开始刷LeetCode,第六个月……简历投出去石沉大海,面试官问:“你这半年学了啥?”我支支吾吾说“研究Rust”,对方眼神瞬间飘忽:“哦,那挺好的……不过我们主要用Java。”

得,赶紧捡回老本行。最近接了个外包小项目练手,用Spring Boot + MyBatis搭个简单的用户管理系统。没想到,这个曾经觉得“过时”的ORM框架,居然让我重新审视了Java生态里那些“老但稳”的工具。

今天就来写一篇MyBatis的入门教程,顺便聊聊为啥在Go都快成主流后端语言的今天,我们这群Java老炮儿还在死磕MyBatis。


为啥又是MyBatis?不是JPA、不是GORM、也不是直接拼SQL?

说实话,刚接到项目需求时,我第一反应是:能不能不用MyBatis?

现在新项目动不动就上Go,用GORM一把梭,连数据库建模都省了;前端那边Vite+React+TypeScript跑得飞快,连部署都自动化了。反观Java后端,还在XML里写SQL,看着都像上个世纪的遗物。

但现实是骨感的。客户是个传统企业,数据库是Oracle,历史数据表结构乱得像我gap期间的作息表——字段命名全大写、主键叫ID_001、还有三个表靠“备注”字段关联……这种情况下,强约束的JPA(比如Hibernate)根本玩不转。你让@Entity自动映射?它能给你报出20种MappingException,还全是英文,看得人血压飙升。

而MyBatis最大的优势就是:SQL你写,它只负责传参和结果映射。自由度高,适合脏数据、老系统、或者像我这样不想被ORM绑架的倔脾气。

顺便说一句,我书架上那本《MyBatis从入门到精通》还是2018年买的,封面都卷边了。这次翻出来一看,居然还能用!不得不说,有些技术,真的“耐放”。


工具链:Mac上的Java开发,舒服得像在家躺平

作为坚定的Mac党(Windows只用来测兼容性),我的开发环境早就调教得顺滑如德芙:

  • IDE:IntelliJ IDEA(Ultimate版,公司报销的,别问)
  • 构建工具:Maven(虽然Gradle更潮,但客户项目指定Maven,忍了)
  • 数据库客户端:TablePlus(轻量、支持多数据库、界面清爽)
  • 调试神器:Arthas(阿里开源的Java诊断工具,线上查问题救命用)

项目骨架用Spring Initializr一键生成,依赖只加了这几个核心项:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

注意:MyBatis Spring Boot Starter已经升级到3.x,底层用的是MyBatis 3.5+,支持Java 17,对Record类型也有初步支持(虽然还不完善)。


配置:别再手写XML了,注解+Mapper也能很优雅

很多人一提MyBatis就想到满屏的<select id="xxx">,其实现代MyBatis完全可以用注解替代XML,尤其对于简单CRUD。

比如一个用户查询:

@Mapper
public interface UserMapper {
    
    @Select("SELECT id, name, email FROM users WHERE id = #{userId}")
    User findById(@Param("userId") Long userId);
    
    @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(User user);
}

是不是清爽多了?当然,复杂查询(比如多表JOIN、动态WHERE)还是建议用XML,毕竟SQL可读性和维护性更重要。

我在application.yml里加了这些配置:

mybatis:
  configuration:
    map-underscore-to-camel-case: true  # 数据库下划线自动转Java驼峰
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL日志,调试必备
  mapper-locations: classpath:mapper/*.xml  # 如果用了XML,指定路径

特别提醒:生产环境千万别开log-impl!有一次我忘了关,上线后日志文件一天涨了50GB,运维大哥差点把我从工位上拎起来。


踩坑实录:那些让我想砸Mac的瞬间

坑1:@Param不写,参数变null

刚开始写的时候,我这么写:

@Select("SELECT * FROM users WHERE name = #{name}")
User findByName(String name);

结果运行时报错:BindingException: Parameter 'name' not found. Available parameters are [arg0, param1]

查了半天才发现,MyBatis在单参数且未加@Param时,会用arg0param1代替。虽然能跑,但代码可读性极差。加上@Param("name")就万事大吉。

坑2:事务不生效

本地测试插入成功,但集成测试时发现数据没提交。后来发现:MyBatis的Mapper接口方法默认不开启事务

解决方法是在Service层加@Transactional

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Transactional
    public void createUser(User user) {
        userMapper.insert(user);
        // 其他操作...
    }
}

这事儿让我想起在大厂时,有个新人同事没加事务,导致订单状态不一致,线上事故复盘会上被产品指着鼻子骂。我当时坐在角落默默喝了三杯冰美式,心想:还好不是我。


性能与架构:MyBatis真的慢吗?

经常听到有人说“MyBatis性能不如JDBC”、“Hibernate缓存更强”。但真相是:框架本身不背锅,关键看你怎么用

1. 一级缓存(SqlSession级别)

默认开启,同一个SqlSession中重复查询会命中缓存。但Web应用中每个请求通常新建SqlSession,所以基本无效。

2. 二级缓存(Mapper级别)

需要手动开启,且要求实体类实现Serializable。但在分布式环境下,多个服务实例的缓存无法同步,容易导致脏读。我建议:除非是只读数据(比如字典表),否则别轻易开。

3. 批量操作

MyBatis原生支持批量插入/更新,比循环单条执行快10倍以上:

@Mapper
public interface UserMapper {
    @Insert("<script>" +
            "INSERT INTO users(name, email) VALUES " +
            "<foreach collection='list' item='user' separator=','>" +
            "(#{user.name}, #{user.email})" +
            "</foreach>" +
            "</script>")
    void batchInsert(@Param("list") List<User> users);
}

上周我用这个方法导入10万条用户数据,从原来的8分钟降到45秒,测试小姐姐都惊了。


和Go、前端的对比:工具链的哲学差异

说到这儿,不得不扯一扯生态差异。

维度 Java (MyBatis) Go (GORM) 前端 (Prisma)
抽象程度 低(SQL可见) 中(链式API) 高(自动生成Client)
类型安全 弱(依赖XML/注解) 强(结构体绑定) 极强(TS全栈类型推导)
学习曲线 中(需懂SQL) 低(贴近语言) 低(声明即用)
调试体验 一般(日志为主) 好(pprof+日志) 极好(DevTools集成)

你会发现,Go和前端工具越来越“智能”:自动生成代码、类型推导、一键迁移。而Java生态,尤其是MyBatis,更像“工匠活”——你需要亲手雕琢SQL,但也因此对数据库有完全掌控。

我在gap期间试过用Go写个小服务,GORM确实香,几行代码搞定CRUD。但一旦遇到复杂查询(比如窗口函数、递归CTE),还是得写原生SQL,这时候和MyBatis也没差多少。

所以别迷信“新就是好”。MyBatis的“显式SQL”反而在排查慢查询时更有优势——你一眼就能看到执行的语句,而不是去翻ORM的日志或源码。


生产经验:上线前必须检查的三件事

  1. SQL注入防护
    永远用#{},别用${}!后者会直接拼接字符串,黑客传个'; DROP TABLE users; --你就完了。
    (别笑,真有实习生这么干过,测试环境直接崩了)

  2. 连接池配置
    默认HikariCP,但生产要调优:

    spring:
      datasource:
        hikari:
          maximum-pool-size: 20
          connection-timeout: 3000
          idle-timeout: 600000
          max-lifetime: 1800000
    
  3. 慢查询监控
    结合Arthas或SkyWalking,设置SQL执行超时告警。我司规定超过500ms就算慢查询,必须优化。


最后:为什么我还在用Java + MyBatis?

写完这篇教程,我突然明白了:技术选型不是追新,而是匹配场景

Go适合高并发、微服务、云原生;前端框架迭代快,用户体验优先;而Java + MyBatis,在企业级应用、复杂业务逻辑、强事务一致性的场景下,依然是稳如老狗的选择。

而且,你知道最爽的是什么吗?
当我用MyBatis写完DAO层,配上Swagger文档,前端同事看了一眼接口定义就说:“字段都对得上,明天联调!”——那一刻,我觉得半年gap没白躺,至少手还没生。

如果你也正在从其他语言转回Java,或者被领导“建议”用MyBatis,别慌。这玩意儿上手快,坑也不多,关键是——它让你离数据库更近,而不是被框架蒙住眼睛

对了,最近我在研究怎么用Rust写MyBatis的替代品,名字都想好了:MyRustis 😎。等哪天开源了,记得来star!


作者碎碎念:本文代码已上传GitHub(链接略)。坐标北京,通勤一小时,正在找工作。擅长Java/Go/Rust,讨厌无意义的会议和模糊的需求。如有内推,感激不尽!

评论 0

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