MyBatis基础教程:一个老广程序员的持久层上岸路
去年十月的一个深夜,我坐在越秀区老东山那间月租3500的小单间里,窗外是熟悉的骑楼和昏黄的路灯。桌上泡着一壶凉了的普洱,键盘旁边堆着几本翻烂了的《Java编程思想》和《Spring Boot实战》。屏幕上的IDEA还在报错,红得刺眼——“Invalid bound statement (not found): com.example.mapper.UserMapper.selectById”。
那一刻,我差点把键盘砸了。
不是因为技术多难,而是因为明天就要交项目初稿,而我这个“资深”程序员居然连MyBatis的基本配置都没搞明白。更扎心的是,再过三个月,我就要参加广州市考了。白天在公司写业务代码,晚上啃行测申论,周末还得抽空学框架——我感觉自己快被撕成两半了。
从“增删改查”到“增删改查+考公”
我是土生土长的老广,大学读的是华工计算机,毕业后进了天河一家中型互联网公司做后端开发,月薪15k,不算高,但在广州老城区生活,勉强够用。去年年初,老婆(对,我已经结婚了)看着我天天加班到十点,黑眼圈比荔枝湾的水还深,突然说:“不如试试考公吧?稳定点。”
我一开始嗤之以鼻:“程序员不就是吃青春饭?考公?我都30了!”
但她一句话戳中我:“你每天写的那些CRUD,十年后还是CRUD。但公务员,十年后可能还在喝茶看报,至少不会被优化。”
那天晚上我没睡着。打开招聘网站,看到某市直单位招信息化岗,要求“熟悉Java、Spring Boot、MyBatis”,薪资虽然只有现在的一半(税后大概8k),但公积金双边4000,还有食堂、宿舍、年假……最关键的是——不用半夜被报警电话吵醒。
我动心了。
于是,一边准备笔试,一边开始恶补岗位要求的技术栈。而MyBatis,就是横在我面前的第一座山。
初识MyBatis:原来ORM没那么可怕
说实话,以前在公司用的都是JPA或者公司自研ORM,MyBatis只是听说过。我以为它很复杂,结果上手才发现:这不就是SQL的“美化器”吗?
上周五晚上,我决定从零搭个Demo。打开IDEA,新建一个Spring Boot项目(2.7.5版本),引入依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
然后在application.yml里配数据源:
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
接着建个User实体类:
public class User {
private Long id;
private String name;
private Integer age;
// getter/setter省略
}
再写个Mapper接口:
@Mapper
public interface UserMapper {
User selectById(Long id);
List<User> selectAll();
int insert(User user);
int update(User user);
int delete(Long id);
}
到这里,一切都很顺利。问题出在XML映射文件。
我把UserMapper.xml放在resources/mapper/下,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
结果一跑,就报那个经典的错误:“Invalid bound statement”。
我当时真的懵了。反复检查namespace、方法名、XML路径……都对啊!直到凌晨两点,我才想起来——Spring Boot默认不会扫描resources下的XML文件!
解决办法?在启动类加个注解:
@MapperScan("com.example.mapper")
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
或者在pom.xml里加resource过滤:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
搞定那一刻,我长舒一口气,赶紧截图发给老婆:“搞定了!MyBatis其实不难!”
她回我:“你上次说‘LeetCode简单题’也不难,结果刷了三天。”
我……无言以对。
案例驱动:用MyBatis写一个“考公报名系统”原型
为了加深理解,我决定用MyBatis做个微型“考公报名系统”。功能很简单:用户注册、查看岗位、提交报名。
第一步:建表
CREATE TABLE candidate (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
id_card VARCHAR(18) UNIQUE,
phone VARCHAR(11),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE post (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100),
department VARCHAR(100),
quota INT,
min_score DECIMAL(5,2)
);
第二步:写Mapper
比如CandidateMapper:
@Mapper
public interface CandidateMapper {
@Select("SELECT * FROM candidate WHERE id_card = #{idCard}")
Candidate findByCard(String idCard);
@Insert("INSERT INTO candidate(name, id_card, phone) VALUES(#{name}, #{idCard}, #{phone})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Candidate candidate);
}
看到了吗?MyBatis支持两种方式:XML映射 or 注解。对于简单SQL,用注解更清爽;复杂查询(比如多表联查、动态条件),还是XML更灵活。
比如岗位查询,可能要根据部门、最低分筛选,这时候就得用<if>标签:
<select id="searchPosts" resultType="Post">
SELECT * FROM post
<where>
<if test="department != null and department != ''">
AND department LIKE CONCAT('%', #{department}, '%')
</if>
<if test="minScore != null">
AND min_score <= #{minScore}
</if>
</where>
</select>
注意那个<——这是XML转义,写的时候容易漏,调试时又找不到原因,气死个人。
第三步:Service层调用
@Service
public class CandidateService {
@Autowired
private CandidateMapper candidateMapper;
public boolean register(String name, String idCard, String phone) {
// 先查是否已存在
if (candidateMapper.findByCard(idCard) != null) {
return false; // 身份证已注册
}
Candidate c = new Candidate();
c.setName(name);
c.setIdCard(idCard);
c.setPhone(phone);
return candidateMapper.insert(c) > 0;
}
}
整个流程跑通后,我在Postman里测试,成功插入了一条“张三,4401041990xxxxxx,138****1234”的数据。那一刻,我居然有点小激动——这不就是未来我要维护的政务系统吗?
那些踩过的坑,都是上岸的垫脚石
学MyBatis的过程中,我踩了不少坑,也总结了些经验:
- 命名空间必须全限定类名:
namespace="com.example.mapper.UserMapper",少一个字母都不行。 - 驼峰转换要开启:数据库字段是
user_name,Java属性是userName,记得在配置里加:mybatis: configuration: map-underscore-to-camel-case: true - 事务别忘了:涉及多个操作(比如报名+扣费),记得在Service方法上加
@Transactional。 - 日志很重要:开发时打开SQL日志,一眼看出执行了什么:
logging: level: com.example.mapper: debug
最让我感慨的是:MyBatis的本质,其实是“让Java程序员安心写SQL”。它不像Hibernate那样试图完全屏蔽SQL,而是承认SQL的价值,只是帮你把参数绑定、结果映射这些脏活自动化了。
这特别适合政务系统——很多老系统迁移,SQL逻辑复杂,直接重写成本太高,用MyBatis就能平滑过渡。
考公or码农?我的选择
昨天,我收到了广州市某局信息化岗的面试通知。笔试成绩第3,岗位招2人。老婆比我激动,说:“终于不用天天debug到凌晨了!”
但我心里清楚,考公不是逃避,而是另一种责任。政务系统关乎民生,一个bug可能导致几百人无法办理业务。这比电商少卖几单严重得多。
而MyBatis,作为国内政务、金融系统最常用的ORM框架,掌握它,不仅是应付考试,更是为未来的工作打基础。
我现在每天依然写代码,但心态变了。以前是为了KPI,现在是为了“能用技术让老百姓办事更方便一点”。哪怕只是优化一个查询速度,减少用户等待时间,也觉得有意义。
写给同样在路上的你
如果你也在准备考公,又担心技术荒废,我的建议是:
- 聚焦岗位要求:别盲目学微服务、分布式,先把Java、Spring Boot、MyBatis这“铁三角”吃透。
- 用项目驱动学习:像我这样,做个“考公报名系统”、“社保查询Demo”,比刷100道题更有效。
- 接受自己的焦虑:我经常怀疑自己是不是太晚了,但转念一想——30岁考公的人多了去了,有人上岸,有人二战,重要的是别停下。
最后分享个小故事:上周我去陈家祠附近喝早茶,邻桌两个阿伯聊天。一个说:“现在年轻人真辛苦,又要写代码,又要考试。”另一个笑:“但人家有技术傍身,考不上还能回去打工,我们当年可没这退路。”
我低头喝了口虾饺皇,心里暖暖的。
技术人的路,从来不止一条。MyBatis可以连接数据库,也可以连接理想与现实。只要手不抖,心不慌,总能找到属于自己的那条SQL语句——精准、高效、不报错。
共勉。
后记:本文所有代码均在我本地环境验证通过(MySQL 8.0 + Spring Boot 2.7.5 + MyBatis 2.3.0)。如果你也在广州备考,欢迎留言交流。对了,我老婆说,要是上岸了,请大家饮茶——就在北京路那家老字号,人均50,管饱。

评论 0