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

HTTPS小卫士
2025-12-17 15:18
阅读 457

上周五晚上十点半,实验室只剩我和隔壁工位的阿哲还在“肝”项目。成都的初夏夜风从窗户缝里钻进来,配上空调外机嗡嗡的噪音,还真有点程序员深夜加班的氛围感。导师布置的课题系统要对接新数据库,而我之前只会用JDBC裸写SQL——那玩意儿在研一课程设计里还能糊弄过去,放到真实项目里简直就是自虐。

被逼无奈之下,我翻开了MyBatis的官方文档。说来惭愧,作为211软件工程研二狗,在实验室搞了快一年后端开发,居然还没正经用过主流ORM框架。好在Mac上IntelliJ IDEA跑得飞起,配好环境后,总算没让Windows测试机提前“退休”。

为啥非得用MyBatis?

先说背景:我们这个项目是给本地一家社区医院做的预约挂号系统,前后端分离架构。前端同学(对,就是那个总在群里@我说接口又404的小王)用Vue3 + TypeScript,后端是我一个人扛着Spring Boot + MySQL。本来以为CRUD能随便应付,结果产品经理上周突然加需求:“医生排班要支持按科室、日期、时间段三级联动筛选”——好家伙,SQL直接复杂度爆炸。

如果继续手写JDBC,光是拼接动态WHERE条件就得让我秃头。而且团队Code Review时,师兄指着我那段if (name != null) sql += " AND name = ?"直摇头:“你这代码,等哪天要加个模糊查询,怕不是得重写整个DAO?”

于是,MyBatis成了救命稻草。

初体验:配置比想象中简单

MyBatis的核心思想其实很朴素:把SQL从Java代码里解放出来,但又不像Hibernate那样完全隐藏SQL。对于我们这种既要控制性能、又不想被SQL字符串折磨的开发者来说,简直是甜点。

我的pom.xml只加了两个依赖:

<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>

然后在application.yml里配好数据源(成都这地方网络偶尔抽风,所以连接池参数我特意调大了点):

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/hospital?useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: your_password
    hikari:
      maximum-pool-size: 20
      connection-timeout: 30000

关键一步来了:Mapper接口 + XML映射文件。比如我要查医生信息:

// DoctorMapper.java
public interface DoctorMapper {
    List<Doctor> selectByConditions(@Param("deptId") Integer deptId, 
                                   @Param("date") LocalDate date);
}

对应的DoctorMapper.xml

<select id="selectByConditions" resultType="com.lab.hospital.entity.Doctor">
    SELECT * FROM doctor 
    WHERE status = 1
    <if test="deptId != null">
        AND department_id = #{deptId}
    </if>
    <if test="date != null">
        AND EXISTS (
            SELECT 1 FROM schedule 
            WHERE doctor_id = doctor.id 
            AND schedule_date = #{date}
        )
    </if>
</select>

看到没?动态SQL直接写在XML里,逻辑清晰到连前端小王都能看懂(他后来真来看了,还吐槽说“你们后端终于不用if拼SQL了”)。

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

当然,路不可能一帆风顺。第一个大坑就是驼峰命名转换。数据库字段叫create_time,Java实体类是createTime,结果查出来全是null。查了半天文档才发现要加配置:

mybatis:
  configuration:
    map-underscore-to-camel-case: true

第二个坑更隐蔽:事务管理。我在Service层写了@Transactional,但方法内部调用另一个Service方法时,事务居然不生效!后来才明白这是Spring AOP的代理机制问题——同一个类内的方法调用不会触发代理。解决办法要么拆成两个Bean,要么用AopContext.currentProxy()(不推荐),最后我老老实实用了第一种。

还有一次线上事故:前端传了个超长的医生姓名(可能是测试时乱填的),结果MySQL报Data too long for column 'name'。虽然根本原因是前端没做长度校验,但作为后端,我也得兜底。于是在MyBatis里加了参数校验拦截器——不过这是进阶内容了,新手先保证功能跑通再说。

性能与可维护性:别只顾着跑通就交差

我们实验室有个不成文的规定:代码提交前必须过SonarQube扫描。有一次我写了个N+1查询(在一个循环里反复查排班表),被直接打回。MyBatis其实提供了<collection>标签做关联查询,避免多次访问数据库:

<resultMap id="DoctorWithSchedules" type="Doctor">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="schedules" ofType="Schedule">
        <id property="id" column="schedule_id"/>
        <result property="date" column="schedule_date"/>
    </collection>
</resultMap>

配合一条JOIN SQL,查询次数从N+1降到1次。在高并发场景下,这种优化能救命——去年双11我们学校电商实训项目就因为N+1查询差点把数据库干崩。

另外,SQL可读性真的很重要。别为了炫技写超复杂的嵌套子查询。有一次我看到实习生写的SQL里有三层EXISTS,当场血压飙升。现在我们团队约定:超过5行的SQL必须写注释,复杂逻辑优先考虑用视图或存储过程(虽然我不太爱用存储过程,但有时候真香)。

给前端同学的“友好提示”

说到前端,其实MyBatis间接影响了他们的开发体验。比如我们统一了分页接口格式:

{
  "code": 200,
  "data": {
    "list": [...],
    "total": 100,
    "pageNum": 1,
    "pageSize": 10
  }
}

后端用MyBatis-Plus(MyBatis的增强工具)的PageHelper插件一行代码搞定分页,前端小王再也不用自己算offset了。他还偷偷跟我说:“你们后端要是早点用这玩意儿,我上周就不会因为分页参数错乱熬到凌晨三点了。”

开发心得:工具只是手段,思维才是核心

折腾了两周,系统终于稳定上线。复盘下来,MyBatis最大的价值不是“自动映射”,而是强制你把SQL当作一等公民来对待。它不像某些全自动ORM那样让你忘记数据库的存在,也不像JDBC那样让你淹没在字符串拼接里。

作为研究生,我越来越觉得:好的代码不仅要跑得对,还要让人看得懂、改得动、测得全。MyBatis在这点上做得很好——SQL写在XML里,天然具备可读性;Mapper接口清晰定义了数据契约;配合良好的日志配置(记得开mybatis.configuration.log-impl=STDOUT_LOGGING),调试起来也方便。

最后吐个槽:运维大哥昨天又抱怨日志太大,说MyBatis打印的SQL占了80%空间。我默默把日志级别从DEBUG改成INFO……成年人的世界,果然都是妥协。


如果你也在成都搞Java开发,欢迎来茶店子附近约杯咖啡聊聊技术(或者一起吐槽产品经理)。毕竟在这个节奏舒服的城市,代码可以慢慢写,但架构一定要稳。

评论 0

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