从嵌入式转Go后,我为什么又回头学MyBatis?

一键启动人生
2025-12-22 04:11
阅读 1802

上周五晚上十一点半,我盯着屏幕上一行红色的 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,这部分可以全交给@MapperScanapplication.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

最热最新
暂无评论
一键启动人生Lv.1
0
影响力
0
文章
0
粉丝