MyBatis 入门
国内 Java 企业最主流的持久层框架。相比 JPA 的「ORM 自动映射」,MyBatis 的理念是 SQL 你自己写、映射你说了算 —— 对写惯了复杂 SQL 的 PHP 开发者非常友好。它和 JdbcTemplate 有点像,但多了「自动映射 + 动态 SQL」两大武器。
对比 JPA:选谁
| 维度 | JPA(类 Eloquent) | MyBatis |
|---|---|---|
| SQL 由谁写 | 框架生成(findByXxx) | 你自己写 |
| 映射 | 自动 | 半自动(SQL 结果 → 实体) |
| 复杂 SQL 掌控 | 吃力,要 JPQL/原生 SQL | 完全掌控 |
| 学习曲线 | 概念多(懒加载、EntityGraph) | 简单直接 |
| 国内企业 | 占比小 | 占比大 |
| 适合 | 简单 CRUD、ORM 爱好者 | SQL 重度、复杂查询多 |
TIP
如果你本来就爱写 SQL(很多 PHP 开发者习惯 Query Builder + 复杂查询),MyBatis 会非常顺手。如果你的需求主要是简单 CRUD,JPA 更快。两者都能进 Spring Boot,也可共存(不用二选一)。
三步上手
1. 依赖
xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>2. 配置
yaml
mybatis:
mapper-locations: classpath:mapper/*.xml # XML Mapper 位置
type-aliases-package: com.example.blog.post # 实体别名包
configuration:
map-underscore-to-camel-case: true # user_id → userId 自动映射(强烈推荐)TIP
map-underscore-to-camel-case: true 让数据库的 user_id 自动映射到实体的 userId,省掉大量手写映射。这一行基本必开。
3. 建实体 + Mapper
实体(普通 POJO,和 JPA 实体一样用 Lombok):
java
@Data
public class Post {
private Long id;
private String title;
private String content;
private Long userId;
private Integer status;
private LocalDateTime createdAt;
}Mapper 接口(对应 Repository):
java
package com.example.blog.post;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PostMapper {
Post findById(Long id);
List<Post> findAll();
int insert(Post post);
int update(Post post);
int deleteById(Long id);
}TIP
@Mapper 标记接口,MyBatis 启动时扫描并生成实现。方法名 + SQL(XML 或注解)绑定,不用写实现类。对应 JPA 的 Repository 接口、Eloquent 的 Model 查询。
SQL 写在哪:XML(推荐) vs 注解
XML 方式:src/main/resources/mapper/PostMapper.xml
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.blog.post.PostMapper">
<select id="findById" resultType="Post">
SELECT * FROM posts WHERE id = #{id}
</select>
<select id="findAll" resultType="Post">
SELECT * FROM posts ORDER BY id DESC
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO posts (title, content, user_id, status)
VALUES (#{title}, #{content}, #{userId}, #{status})
</insert>
<update id="update">
UPDATE posts SET title = #{title}, content = #{content} WHERE id = #{id}
</update>
<delete id="deleteById">
DELETE FROM posts WHERE id = #{id}
</delete>
</mapper>id对应 Mapper 接口方法名resultType="Post"用配置的别名(type-aliases-package)自动映射列到实体字段#{title}是参数占位(预编译,防注入,等同 JdbcTemplate 的?)
注解方式:SQL 写在接口上
java
@Mapper
public interface PostMapper {
@Select("SELECT * FROM posts WHERE id = #{id}")
Post findById(Long id);
@Insert("INSERT INTO posts (title, content) VALUES (#{title}, #{content})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(Post post);
}XML vs 注解
- 简单 SQL:注解够用
- 复杂 SQL / 动态 SQL:XML 更清晰,可复用、可维护(国内企业普遍 XML)
- 本教程推荐 XML:和「SQL 与 Java 分离」的 Laravel 心智更接近
使用 Mapper
java
@Service
public class PostService {
private final PostMapper postMapper;
public PostService(PostMapper postMapper) {
this.postMapper = postMapper;
}
public Post get(Long id) {
Post post = postMapper.findById(id);
if (post == null) {
throw BusinessException.notFound("帖子不存在: " + id);
}
return post;
}
public List<Post> list() {
return postMapper.findAll();
}
public Long create(PostDto dto) {
Post post = new Post();
post.setTitle(dto.getTitle());
post.setContent(dto.getContent());
post.setUserId(dto.getUserId());
post.setStatus(1);
postMapper.insert(post);
return post.getId(); // useGeneratedKeys 回填主键
}
}对照速查
| Eloquent / Query Builder | MyBatis |
|---|---|
Post::find($id) | postMapper.findById(id) |
Post::where('status',1)->get() | XML 里 SELECT ... WHERE status = #{status} |
Post::create($data) | insert + useGeneratedKeys |
$post->save() | update |
Post::destroy($id) | deleteById |
模型关联 $post->user | 自定义 JOIN SQL |
参数传递的三种方式
java
// 1. 单参数:直接 #{名字}
Post findById(Long id);
// 2. 多参数:用 @Param 命名
List<Post> findByUser(@Param("userId") Long userId, @Param("status") Integer status);xml
<select id="findByUser" resultType="Post">
SELECT * FROM posts WHERE user_id = #{userId} AND status = #{status}
</select>java
// 3. 对象参数:直接用属性名
int insert(Post post); // #{title} 取 post.title常见问题
| 报错 | 原因 |
|---|---|
Invalid bound statement (not found) | Mapper 接口方法在 XML 里没对应 id,或 XML 没被扫描 |
A query was run and no Result Maps were found | 返回类型没配 resultType |
| 字段全是 null | 没开 map-underscore-to-camel-case,或 resultType 写错 |
| XML 找不到 | mapper-locations 路径配错 |
下一步
- MyBatis XML Mapper:动态 SQL(
<if>、<foreach>)、结果映射、关联查询