楼层: 首页/ 软件技术/ Spring Boot 3.5/ 数据访问:JPA、MyBatis 与连接池
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 + 迁移脚本。