6
数据访问:JPA、MyBatis 与连接池
Data Access
Spring Boot 默认用 HikariCP 连接池(速度最快的 Java 连接池)。数据访问层有两条主流路线:Spring Data JPA(面向对象,少写 SQL)和 MyBatis(手写 SQL,灵活可控)。下面分别看。
Spring Data JPA:接口都不用写实现
你只要写一个接口继承 JpaRepository,Spring Data 自动根据方法名生成 SQL。比如写个 findByTitleContaining,它就知道你要按标题模糊查。
JPA 方法名查询:Spring 帮你写 SQL
Spring Data JPA 能根据方法名自动生成 SQL。你只要按规则命名,不用写一行 SQL。
| 方法名 | 生成的 SQL |
findByTitle(String title) | WHERE title = ? |
findByTitleContaining(String kw) | WHERE title LIKE %?% |
findByDoneTrue() | WHERE done = true |
findByDoneFalseOrderByIdDesc() | WHERE done = false ORDER BY id DESC |
countByDone(boolean done) | SELECT COUNT(*) ... WHERE done = ? |
deleteByTitle(String title) | DELETE ... WHERE title = ? |
JPA 实体 + Repository
import jakarta.persistence.*;
@Entity
public class Task {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String title;
private boolean done;
// 省略 getter / setter
}
// 继承 JpaRepository,自动拥有 CRUD + 分页 + 排序能力
// 方法名查询:findByTitleContaining 自动变成 WHERE title LIKE ?
public interface TaskRepository extends JpaRepository<Task, Long> {
List<Task> findByDone(boolean done);
@Query("SELECT t FROM Task t WHERE t.title LIKE %:kw%")
List<Task> search(@Param("kw") String keyword);
}
Service 里注入 Repository 直接用
@Service
public class TaskService {
private final TaskRepository repo;
public TaskService(TaskRepository repo) { this.repo = repo; }
// 分页查询:Pageable 自动生成分页 SQL
public Page<Task> list(int page, int size) {
return repo.findAll(PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "id")));
}
}
HikariCP 连接池:为什么快
Spring Boot 默认连接池是 HikariCP——目前 Java 世界最快的连接池。它的秘诀是字节码层面优化、精简代理对象、ConcurrentBag 数据结构。你不用换,只要调几个参数。
application.yml 调连接池参数
spring:
datasource:
hikari:
maximum-pool-size: 10 # 最大连接数,按数据库能力调
minimum-idle: 5 # 最小空闲连接
connection-timeout: 30000 # 获取连接超时 30 秒
idle-timeout: 600000 # 空闲 10 分钟回收
max-lifetime: 1800000 # 连接最长存活 30 分钟
连接数不是越大越好
很多人上来就 maximum-pool-size=1000,以为越大越快。错。数据库本身处理能力有限,连接太多反而上下文切换开销大。经验值:CPU 核数 × 2 + 有效磁盘数,一般生产环境 10-30 就够。
MyBatis:手写 SQL 的灵活派
JPA 适合简单 CRUD,但复杂多表查询、动态 SQL,JPA 写起来别扭。国内项目用 MyBatis 的更多——SQL 自己写,参数自己绑,结果自己映射。
MyBatis Mapper 接口 + XML
// Mapper 接口
@Mapper
public interface TaskMapper {
List<Task> selectByKeyword(@Param("kw") String kw);
void updateDone(@Param("id") long id, @Param("done") boolean done);
}
对应 XML 映射文件(resources/mapper/TaskMapper.xml)
<mapper namespace="com.example.demo.mapper.TaskMapper">
<select id="selectByKeyword" resultType="Task">
SELECT id, title, done
FROM task
WHERE title LIKE CONCAT('%', #{kw}, '%')
</select>
<update id="updateDone">
UPDATE task SET done = #{done} WHERE id = #{id}
</update>
</mapper>
application.yml 里配数据源
# 用 H2 内存数据库开发,启动即建表,关库即清空
spring:
datasource:
url: jdbc:h2:mem:demo
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true # 打开 /h2-console 可视化界面
jpa:
hibernate:
ddl-auto: update # 自动建表,开发用;生产用 none
show-sql: true # 控制台打印 SQL,调试用
完整 CRUD 串联:Repository → Service → 分页与条件查询
把上面零件拼成一个完整的增删改查:Service 调 Repository,分页用 Pageable,条件查询用 Example/Specification。重点看分页查询返回的 JSON 长什么样。
Service 完整 CRUD + 分页 + 条件查询
@Service
public class TaskService {
private final TaskRepository repo;
public TaskService(TaskRepository repo) { this.repo = repo; }
// 增
@Transactional
public Task create(String title) {
Task t = new Task();
t.setTitle(title);
t.setDone(false);
return repo.save(t); // save 即 insert 或 update
}
// 改
@Transactional
public Task markDone(long id) {
Task t = repo.findById(id)
.orElseThrow(() -> new IllegalArgumentException("任务不存在"));
t.setDone(true);
return repo.save(t);
}
// 删
@Transactional
public void delete(long id) { repo.deleteById(id); }
// 分页 + 按完成状态筛选
public Page<Task> page(Boolean done, int page, int size) {
Pageable p = PageRequest.of(page, size,
Sort.by(Sort.Direction.DESC, "id"));
if (done == null) return repo.findAll(p);
return repo.findByDone(done, p); // Repository 里加 Pageable 参数即分页
}
}
GET /api/tasks?done=false&page=0&size=2 返回:
{
"content": [ {"id":5,"title":"买牛奶","done":false}, {"id":4,"title":"写周报","done":false} ],
"totalElements": 17, # 总条数
"totalPages": 9, # 总页数
"number": 0, # 当前页
"size": 2 # 每页大小
}
# 前端用 totalPages 画分页器,content 渲染列表,不用自己算 count。
本章面试题 · 第 6 章(数据访问)
1.(对比题)JPA 和 MyBatis 各适合什么场景?
查看答案
答案:JPA 适合简单 CRUD、领域模型清晰,少写 SQL,开发快;MyBatis 适合复杂多表查询、动态 SQL、需要精细优化 SQL 的场景。解析:国内互联网项目偏 MyBatis(SQL 可控),企业管理系统偏 JPA(业务规整)。
2.(理解题)JpaRepository 的 save() 怎么知道是 insert 还是 update?
查看答案
答案:看主键 id:id 为 null(或数据库标记为新对象)走 insert,id 已存在走 update。解析:也可以用 persist() 强制插入、merge() 强制合并,区分更明确。
3.(实践题)ddl-auto=update 为什么生产环境不能用?
查看答案
答案:它只增不删不改类型,改字段类型、删字段不会自动同步,容易和实际表结构不一致。生产环境表结构变更应该用 Flyway/Liquibase 这样的版本化迁移工具。解析:开发期用 update 图方便,生产用 none + 迁移脚本。