MyBatis初体验:一个前端仔被迫写Java后端的血泪史
大家好,我是小林,深圳某腾讯系公司刚入职三个月的大专应届生。白天写Vue3组件,晚上啃Spring Boot,周末还得被领导拉着学AI——别问,问就是“全栈是趋势”。上周五晚上十点半,我正美滋滋准备下班,产品突然甩过来一个需求:“这个数据导出接口要支持动态SQL筛选,明天上线。”
我内心OS:???你当我是哆啦A梦啊?
更要命的是,后端同事全在搞大模型微调(没错,我们组最近全员卷AI),没人搭理我。没办法,硬着头皮自己上。翻了翻项目代码,发现用的是MyBatis——这玩意儿我只在B站教程里见过名字。于是那个周末,我泡在深圳湾人才公园旁的咖啡馆,从早八点肝到晚十点,总算把MyBatis基础摸了个七七八八。
今天这篇教程,就是给和我一样半路出家、被迫写Java后端的“前端难民”准备的。不讲虚的,直接上实战案例,带你从零跑通一个MyBatis CRUD接口。
为什么选MyBatis?因为我不想写JDBC
说实话,一开始我以为Java操作数据库就是写PreparedStatement、ResultSet那一套。直到看到我们老项目里那段300行的DAO层代码,里面全是手动拼SQL、手动映射字段、手动关连接……当时真的想砸电脑。
MyBatis最大的好处是什么?它让你专注SQL本身,而不是那些重复又容易出错的模板代码。比如我要查用户信息,在MyBatis里只需要写:
<!-- UserMapper.xml -->
<select id="selectUserById" resultType="com.example.model.User">
SELECT id, name, email, created_at
FROM users
WHERE id = #{id}
</select>
框架自动帮你:
- 创建PreparedStatement
- 设置参数
- 执行查询
- 把ResultSet映射成Java对象
- 关闭资源
这不比手写JDBC香多了?尤其对我们这种非科班出身的人来说,省下的脑细胞能多活五年。
搭建第一个MyBatis项目(基于Spring Boot)
我们的项目结构是标准的Spring Boot + MyBatis组合。先看依赖:
<!-- pom.xml -->
<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>
然后配数据库连接(application.yml):
spring:
datasource:
url: jdbc:mysql://localhost:3306/myapp?useSSL=false&serverTimezone=UTC
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml # XML映射文件位置
type-aliases-package: com.example.model # 实体类包路径,简化resultType写法
接着定义实体类:
// User.java
public class User {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
// getter/setter 省略(Lombok真香)
}
再写Mapper接口(注意:这是接口,不是实现类!):
// UserMapper.java
@Mapper
public interface UserMapper {
User selectUserById(Long id);
List<User> selectAllUsers();
int insertUser(User user);
int updateUser(User user);
int deleteUser(Long id);
}
最后是XML映射文件(resources/mapper/UserMapper.xml):
<?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="selectUserById" resultType="User">
SELECT id, name, email, created_at
FROM users
WHERE id = #{id}
</select>
<select id="selectAllUsers" resultType="User">
SELECT id, name, email, created_at FROM users
</select>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users(name, email, created_at)
VALUES(#{name}, #{email}, #{createdAt})
</insert>
<update id="updateUser">
UPDATE users
SET name = #{name}, email = #{email}
WHERE id = #{id}
</update>
<delete id="deleteUser">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>
💡 关键点解释:
namespace必须和Mapper接口全限定名一致resultType="User"能简写是因为配置了type-aliases-package<insert>中的useGeneratedKeys="true"是为了获取自增主键
动态SQL:产品经理的“灵活需求”克星
回到开头那个血泪需求——动态筛选。产品经理说:“用户可能按姓名搜,也可能按邮箱搜,也可能都不填,也可能都填……”
这时候就得用MyBatis的动态SQL了。比如:
<select id="searchUsers" resultType="User">
SELECT id, name, email, created_at FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</where>
</select>
<where> 标签会智能处理AND/OR前缀,避免出现 WHERE AND ... 这种语法错误。实测有效,再也不用写一堆 StringUtils.isNotBlank() 判断了。
但更骚的操作是 <foreach> ——比如批量删除:
<delete id="deleteUsersByIds">
DELETE FROM users
WHERE id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
传入一个 List<Long> ids,就能生成 DELETE ... WHERE id IN (1,2,3)。线上跑过千万级数据,稳如老狗(当然记得加索引)。
避坑指南:这些雷我替你踩过了
1. 字段名和属性名不一致?
数据库用下划线(created_at),Java用驼峰(createdAt)。解决方法有两种:
- 全局开启驼峰转换(推荐):
mybatis: configuration: map-underscore-to-camel-case: true - 或者在SQL里手动起别名:
SELECT created_at AS createdAt
2. 参数传多个怎么办?
别傻傻地写 selectUser(String name, String email)。要么封装成DTO,要么用@Param注解:
User selectUser(@Param("name") String name, @Param("email") String email);
XML里就能用 #{name} 和 #{email}。
3. 事务失效?
MyBatis本身不管理事务,靠Spring。记得在Service方法上加 @Transactional,而且必须是public方法!我曾经在一个private方法上加,结果事务完全没生效,测试环境删了5000条数据回滚不了……那天晚上加班到凌晨两点。
和AI结合?试试Prompt工程优化SQL
最近组里在搞AI提效,我也尝试用大模型辅助写SQL。比如给Claude这样的Prompt:
你是一个资深MySQL DBA。请根据以下需求生成MyBatis动态SQL:
- 表名:orders
- 字段:id, user_id, status, amount, created_at
- 支持按user_id精确查询、status精确查询、amount范围查询(minAmount, maxAmount)、created_at范围查询
- 注意防SQL注入,使用#{}而非${}
- 返回完整的
结果它真的生成了合规的XML!虽然还是要人工review,但至少省去了80%的模板编写时间。Prompt工程 + MyBatis,可能是我们这类“杂牌军”程序员的提效新姿势。
写在最后
从周五晚上的绝望,到周日晚上成功跑通接口,我深刻体会到:技术没有高低贵贱,能解决问题的就是好技术。MyBatis或许不如Hibernate“高级”,但它简单、直观、可控,特别适合我们这种既要写前端又要救火后端的打工人。
现在我已经能独立开发简单的CRUD模块了,甚至开始帮后端同事review SQL。上周还因为优化了一条慢查询(加了联合索引),被leader请喝了喜茶——在深圳,这可是最高荣誉!
如果你也是非科班出身,别怕Java后端。从MyBatis开始,一步步来。毕竟,连我这个大专生都能搞定,你肯定行。
附:常用配置速查表
| 配置项 | 作用 | 示例值 |
|---|---|---|
map-underscore-to-camel-case |
开启驼峰转换 | true |
log-impl |
指定日志实现 | org.apache.ibatis.logging.stdout.StdOutImpl |
lazy-loading-enabled |
延迟加载 | false(默认) |
aggressive-lazy-loading |
按需加载关联对象 | false |
下次分享:《MyBatis-Plus如何让我少写80%的XML》。敬请期待!

评论 0