从嵌入式转Go后,我为什么又回头学MyBatis?
上周五晚上十一点半,我盯着屏幕上一行红色的 SQLSyntaxErrorException,心里一万只草泥马奔腾而过。不是说好转Go之后就告别Java了吗?结果新项目临时要对接一个老系统,接口文档是2017年的,数据库字段命名还是下划线+拼音混合体(别问,问就是“历史原因”)。产品经理还在群里@我:“这个需求明天上线,没问题吧?”——呵,没问题个锤子。
我是去年从嵌入式开发转Go的硬件出身程序员,现在在深圳一家腾讯系公司做云原生相关的后端服务。平时用VSCode写Go、调K8s YAML、看Prometheus指标,日子过得还算滋润。但这次,甲方爸爸坚持要用Java + MyBatis那一套,理由很充分:“我们运维团队只会部署Java应用。”得,那我只能硬着头皮捡起大学时用过的MyBatis了。
爬虫?不,是“爬”老系统
其实这次的需求本质是个数据同步工具:我们要从对方的老系统里定时拉取用户行为日志,然后喂给我们的实时分析引擎。乍一听像爬虫,但人家给了数据库直连权限(安全团队已经翻白眼了),所以我们直接走JDBC,省去了HTML解析那一套。
但问题来了:对方表结构混乱,字段命名毫无规范,有些字段还是TEXT类型存JSON字符串。如果手写JDBC,光是ResultSet.getString("user_info_json")就得写到吐。这时候,MyBatis的优势就体现出来了——它能自动把结果集映射成对象,还能通过自定义TypeHandler处理特殊字段。
MyBatis到底是个啥?
简单说,MyBatis是一个持久层框架,它把SQL和Java代码解耦了。你不用再在代码里拼字符串写SQL(想想那些"SELECT * FROM user WHERE id = " + userId的恐怖场景),而是把SQL写在XML文件或注解里,由MyBatis帮你执行并映射结果。
对咱们这种从C/C++/Go转过来的人,可能一开始觉得:“这不就是ORM吗?” 但MyBatis和Hibernate这类全自动ORM不一样,它更“半自动”——你依然要写SQL,但它帮你省去了90%的样板代码(boilerplate code)。
快速上手:三步搞定
1. 引入依赖(Maven)
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
2. 配置SqlSessionFactory
这是MyBatis的核心入口,相当于数据库连接池+SQL执行器的组合体。
DataSource dataSource = new PooledDataSource(
"com.mysql.cj.jdbc.Driver",
"jdbc:mysql://old-system-db:3306/logs?useSSL=false",
"user", "password"
);
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("prod", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(LogMapper.class); // 注册Mapper接口
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
💡 小技巧:如果你用Spring Boot,这部分可以全交给
@MapperScan和application.yml,但为了理解原理,建议先手写一遍。
3. 写Mapper接口和SQL
public interface LogMapper {
@Select("SELECT id, event_type, user_info_json FROM user_logs WHERE created_at > #{since}")
List<UserLog> findLogsAfter(@Param("since") LocalDateTime since);
}
对应的POJO:
public class UserLog {
private Long id;
private String eventType;
private String userInfoJson; // 后面我们会用TypeHandler转成Map
// getter/setter...
}
踩坑实录:那些让我想砸键盘的瞬间
坑1:时区不对,数据全偏了8小时
MySQL默认时区是服务器本地时区,而我们用的是UTC时间戳。结果拉出来的数据全是昨天的!解决方案是在JDBC URL加上:
serverTimezone=UTC&useTimezone=true
坑2:TEXT字段里的JSON怎么解析?
对方数据库里有个user_info_json字段,存的是{"uid": "abc", "device": "iPhone"}这样的字符串。我不想在业务层手动new ObjectMapper().readValue(),于是写了自定义TypeHandler:
public class JsonTypeHandler extends BaseTypeHandler<Map<String, Object>> {
private static final ObjectMapper mapper = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Map<String, Object> parameter, JdbcType jdbcType) {
try {
ps.setString(i, mapper.writeValueAsString(parameter));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Map<String, Object> getNullableResult(ResultSet rs, String columnName) {
try {
String json = rs.getString(columnName);
return json == null ? null : mapper.readValue(json, Map.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
然后在实体类字段上加注解:
@Results({
@Result(property = "userInfo", column = "user_info_json", typeHandler = JsonTypeHandler.class)
})
List<UserLog> findLogsAfter(...);
搞定!现在UserLog.getUserInfo()直接返回Map,爽歪歪。
性能与生产考量
虽然这只是个“工具型”服务,但毕竟是线上跑的,不能随便糊弄。我们做了几件事:
| 优化项 | 措施 | 效果 |
|---|---|---|
| 分页查询 | 每次只拉1000条,用LIMIT+created_at游标 |
避免OOM |
| 连接池 | 使用PooledDataSource,最大连接数设为10 |
防止DB被压垮 |
| 重试机制 | 对SQLException做指数退避重试 |
应对网络抖动 |
| 日志脱敏 | 敏感字段如user_info_json在DEBUG日志中截断 |
符合安全规范 |
另外,我们把这个工具打包成Docker镜像,扔到K8s里跑CronJob,每天凌晨2点自动同步。运维同事看到YAML文件里写着image: mybatis-log-sync:v1.2,居然没吐槽——看来Java也不是那么不受待见嘛。
后端视角:MyBatis适合什么场景?
作为天天和Go、K8s打交道的人,我得说:MyBatis不是银弹,但在特定场景下非常香。
- ✅ 遗留系统对接:当你要和一堆老Java系统交互时,MyBatis比手写JDBC高效太多。
- ✅ 复杂SQL场景:比如多表JOIN、动态WHERE条件,MyBatis的
<if>、<foreach>标签比Hibernate的Criteria API直观多了。 - ❌ 全新微服务:如果是从零开始的项目,我会优先选Go + GORM 或 Java + Spring Data JPA,更现代化。
有趣的是,现在很多数据管道工具(比如Flink CDC、Debezium)底层也在用MyBatis风格的SQL模板,说明这种“SQL可控+结果自动映射”的模式依然有生命力。
最后一点碎碎念
写完这个工具,我突然理解了为什么很多大厂后端还坚守Java生态——不是技术多先进,而是历史包袱太重。MyBatis就像一把老但顺手的瑞士军刀,在需要精细控制SQL的场合,它比全自动ORM更可靠。
当然,如果让我重新选择,我还是会坚定地站在Go这边。但作为一个混迹深圳腾讯系公司的前硬件工程师,我学会了灵活切换:工具不重要,解决问题才重要。
对了,那个需求最后按时上线了。测试同学跑完回归,发了个“👍”。我关掉VSCode(顺手卸载了几个不用的Java插件),打开终端敲了句 kubectl get pods -n prod —— 啊,世界清净了。

评论 0