Marco Behler 2022-08-24
你可以使用本指南全面了解 Java 中所有流行的数据库库和 API,涵盖 JDBC、Hibernate、JPA、jOOQ、Spring Data 等。
Java 与数据库:入门介绍
当你希望在(服务器端或桌面端)Java 应用中访问数据库时,通常会面临以下三个问题:
你是采用“Java 优先”还是“数据库优先”的开发方式?
你是先写 Java 类还是先写 SQL 语句?是否需要集成一个已存在的数据库?你如何执行 SQL 语句?
从小型的 CRUD 操作(SELECT FROM、INSERT INTO、UPDATE WHERE)到更复杂的 SQL 报表查询(分析函数等)?对象-关系映射(ORM)有多容易实现?
即如何在 Java 对象与数据库表/行之间进行映射?
为说明 ORM 的概念,假设你有如下 Java 类:
public class User {
private Integer id;
private String firstName;
private String lastName;
// 构造函数 / Getter / Setter...
}
同时,你也有一张名为 USERS 的数据库表。假设表中有三条用户记录,大致如下:
| id | first_name | last_name |
|---|---|---|
| 1 | hansi | huber |
| 2 | max | mutzke |
| 3 | donald | trump |
那么,如何在 Java 类与这张表之间建立映射关系呢?
在 Java 中,有多种方式可以实现这种映射:
- JDBC:底层选择。
- 轻量级 SQL 框架:如 jOOQ 或 Spring 的 JDBC 抽象层。
- 完整的 ORM 框架:如 Hibernate 或任意 JPA 实现。
本指南将涵盖上述所有选项,但理解 JDBC 基础知识至关重要。
为什么?因为所有其他库(无论是 Spring 还是 Hibernate)都构建在 JDBC 之上——它们底层都使用 JDBC。
JDBC:底层数据库访问
什么是 JDBC?
在 Java 中访问数据库最底层的方式是通过 JDBC API(Java Database Connectivity)。
本文后续提到的所有框架实际上都基于 JDBC。当然,你也可以直接使用它来执行 SQL 查询。
JDBC 的优点在于:无需任何第三方库,因为它随每个 JDK/JRE 一起提供。你只需为特定数据库准备合适的 JDBC 驱动程序。
建议:如果你想快速掌握 JDBC 入门、驱动获取、连接池配置及 SQL 执行等内容,推荐阅读《JDBC入门》一文,然后再继续阅读本指南。
JDBC 到 Java:示例
假设你的数据库包含前面提到的 Users 表,你想查询所有用户并将其转换为 List<User>。
先剧透一下:JDBC 完全不会帮你自动完成 SQL 与 Java 对象之间的转换。来看代码:
package com.marcobehler;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class JdbcQueries {
public static void main(String[] args) throws SQLException {
try (Connection conn = DriverManager
.getConnection("jdbc:mysql://localhost/test?serverTimezone=UTC",
"myUsername", "myPassword")) {
PreparedStatement selectStatement = conn.prepareStatement("select * from users");
ResultSet rs = selectStatement.executeQuery();
List<User> users = new ArrayList<>();
while (rs.next()) { // 遍历所有行
Integer id = rs.getInt("id");
String firstName = rs.getString("first_name");
String lastName = rs.getString("last_name");
User user = new User(id, firstName, lastName);
users.add(user);
}
}
}
}
代码解析
try (Connection conn = DriverManager
.getConnection("jdbc:mysql://localhost/test?serverTimezone=UTC",
"myUsername", "myPassword")) {
这里我们打开了一个 MySQL 数据库连接。务必使用 try-with-resources 语句,以确保连接在使用完毕后自动关闭。
PreparedStatement selectStatement = conn.prepareStatement("select * from users");
ResultSet rs = selectStatement.executeQuery();
你需要创建并执行 SQL 语句,通过 PreparedStatement 实现(支持 ? 占位符,但此处暂不展开)。
List<User> users = new ArrayList<>();
while (rs.next()) {
Integer id = rs.getInt("id");
String firstName = rs.getString("first_name");
String lastName = rs.getString("last_name");
User user = new User(id, firstName, lastName);
users.add(user);
}
你必须手动遍历 ResultSet(即 SQL 查询返回的所有行),并通过调用正确的 getter 方法(如 getString()、getInt())按列名和类型逐一手动构造 Java 对象。
此外,上面的代码还刻意省略了两点重要内容:
- SQL 查询中的占位符(例如:
SELECT * FROM USERS WHERE name = ? AND registration_date = ?),用于防止 SQL 注入。 - 事务处理:包括开启、提交事务,以及在出错时回滚。
这个例子很好地说明了为什么 JDBC 被认为是“底层”的:你需要大量手动工作来完成 SQL 与 Java 对象之间的双向转换,并且要自己管理数据库连接的开闭。
JDBC 小结
使用 JDBC 就像在“裸金属”上操作。你拥有 SQL 和 JDBC 的全部性能与控制力,但必须自行处理 Java 对象与 SQL 之间的转换,并负责正确地打开/关闭数据库连接。
这正是更便捷、轻量级框架出现的原因,我们将在下一节介绍它们。
Java ORM 框架:Hibernate、JPA 等
Java 开发者通常更习惯编写 Java 类,而非 SQL 语句。因此,许多(全新)项目采用 Java 优先 的开发方式:先定义 Java 类,再生成对应的数据库表。
这就自然引出了 ORM 问题:如何将新写的 Java 类映射到(尚未创建的)数据库表?甚至能否直接从 Java 类生成数据库结构?
这就是 Hibernate 或其他 JPA 实现这类完整 ORM 框架的用武之地。
什么是 Hibernate?
Hibernate 是一个成熟的 ORM(对象-关系映射)库,最早发布于 2001 年!当前稳定版本为 5.4.x,6.x 正在开发中。
尽管已有无数书籍专门讲解 Hibernate,这里尝试简要总结其核心能力:
- 可(相对)轻松地在数据库表与 Java 类之间转换,你只需做初始映射。
- 对于基本 CRUD 操作(创建、删除、更新用户),无需手写 SQL。
- 提供多种查询机制(HQL、Criteria API),作为 SQL 的“面向对象”替代方案(虽然后文才有具体示例)。
示例代码
假设你有如下数据库表(与 JDBC 部分相同):
CREATE TABLE users (
id INTEGER NOT NULL,
first_name VARCHAR(255),
last_name VARCHAR(255),
PRIMARY KEY (id)
);
以及对应的 Java 类:
public class User {
private Integer id;
private String firstName;
private String lastName;
// Getter/Setter 省略
}
如果你已将 hibernate-core.jar 添加到项目中,如何告诉 Hibernate 将 User.java 映射到 users 表?
答案是:Hibernate 映射注解。
如何使用 Hibernate 映射注解?
默认情况下,Hibernate 无法知道哪个类应映射到哪张表(User.java 应该对应 invoices 表还是 users 表?)。
历史上,你需要编写 .xml 映射文件。但近年来,基于注解的映射方式已成为主流,我们将不再讨论 XML 方式。
你可能已经见过 @Entity、@Column、@Table 等注解。以下是带注解的 User.java:
import javax.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
// Getter/Setter 省略
}
简要说明:
@Entity:标记该类需映射到数据库表。@Table:指定映射的表名。@Column:指定字段对应的列名。@Id+@GeneratedValue:声明主键及其自动生成策略。
当然还有更多注解,但你已能理解其思路:先写 Java 类,再用注解描述其与数据库的映射关系。
快速启动 Hibernate(5.x)
完成注解后,还需引导 Hibernate。其核心入口是 SessionFactory,它理解你的映射注解,并允许你打开 Session(本质上是增强版的 JDBC 连接)。
⚠️ 注意:Hibernate 5.x+ 的引导代码较繁琐,Spring 等框架通常会替你处理。但若想直接使用 Hibernate,需如下操作:
public static void main(String[] args) {
StandardServiceRegistryBuilder registryBuilder =
new StandardServiceRegistryBuilder().configure().build();
MetadataSources sources = new MetadataSources(registryBuilder);
sources.addAnnotatedClass(User.class);
Metadata metadata = sources.buildMetadata();
SessionFactory sessionFactory = metadata.buildSessionFactory();
}
Hibernate 基本持久化操作示例
有了 SessionFactory,即可获取 Session 并执行操作。在 Hibernate/JPA 术语中,这称为“持久化”(persistence)——即将 Java 对象保存到数据库。
Session session = sessionFactory.openSession();
User user = new User();
user.setFirstName("Hans");
user.setLastName("Dampf");
session.save(user); // 自动生成并执行 "INSERT INTO users" SQL!
相比 JDBC,你不再需要处理 PreparedStatement 和参数绑定,Hibernate 会自动生成正确的 SQL(前提是注解正确)。
其他基本操作:
// 自动生成: "SELECT * FROM users WHERE id = 1"
User user = session.get(User.class, 1);
// 自动生成: "UPDATE users SET ... WHERE id = 1"
session.update(user);
// 自动生成: "DELETE FROM users WHERE id = 1"
session.delete(user);
Hibernate 查询语言(HQL)
对于复杂查询,Hibernate 提供 HQL(Hibernate Query Language)。
HQL 类似 SQL,但面向 Java 对象,且与底层数据库无关(理论上同一 HQL 可用于 MySQL、Oracle、PostgreSQL 等),代价是无法使用数据库特有功能。
“面向 Java 对象”是什么意思?看例子:
// 查询 firstName 为 'hans' 的用户
List<User> users = session.createQuery(
"SELECT u FROM User u WHERE u.firstName = 'hans'", User.class
).list();
// 更新 lastName
session.createQuery(
"UPDATE User u SET u.lastName = :newName WHERE u.lastName = :oldName"
).executeUpdate();
注意:这里访问的是 Java 属性(u.firstName),而非 SQL 列名(first_name)。Hibernate 会自动将其转换为数据库特定的 SQL,并将结果映射为 User 对象。
详情请参考 Hibernate 官方 HQL 文档。
Hibernate Criteria API
HQL 本质仍是字符串拼接(尽管 IDE 有支持)。若需动态构建查询(如根据用户输入改变 WHERE 条件),Hibernate 提供 Criteria API。
目前有两个版本:
- Criteria 1.0:已废弃,将在 Hibernate 6.x 移除,但更易用。
- Criteria 2.0:类型安全,但学习曲线陡峭,需配置注解处理器生成“静态元模型”(如
User_类)。
将前述 HQL 转为 Criteria 2.0:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> root = criteria.from(User.class);
criteria.select(root);
criteria.where(builder.equal(root.get(User_.firstName), "hans"));
List<User> users = entityManager.createQuery(criteria).getResultList();
你用可读性换来了类型安全和灵活性(可轻松加入 if-else 动态构建条件)。但注意:一个简单的 "SELECT * FROM users WHERE firstName = ?" 现在需要六行代码!
Hibernate 的缺点
Hibernate 功能远不止基础映射和查询,还包括级联、懒加载、缓存等。它是极其复杂的软件,无法仅靠复制教程代码掌握。
这导致两个常见问题:
- “Hibernate 在搞魔法!”
开发者因缺乏背景知识,无法理解其内部行为。 - “用 Hibernate 就不用学 SQL 了!”
实际上,项目越复杂,越需要 SQL 技能来验证和优化 Hibernate 生成的语句。
解决方案只有一个:高效使用 Hibernate,必须同时精通 Hibernate 和 SQL。
推荐学习资源
- 书籍:《Java Persistence with Hibernate》(608 页,足见其复杂度)
- 博客:Vlad Mihalcea、Thorben Janssen(Hibernate 专家)
- 视频课程:本站 Hibernate 教程(虽非最新,但快速入门极佳)
Java 持久化 API(JPA)是什么?
前文讨论的是纯 Hibernate,那 JPA 是什么?与 Hibernate 有何区别?
JPA 仅是一个规范(Specification),不是实现。它定义了持久化库必须支持的功能标准。Hibernate、EclipseLink、TopLink 等都是 JPA 的实现。
简单说:如果你的库支持以特定方式保存对象、映射、查询(如 Criteria API)等,就可称为“JPA 兼容”。
因此,你可以编写 JPA 标准代码,再通过配置(如添加 Hibernate 依赖)接入具体实现。JPA 本质上是 Hibernate 之上的又一层抽象。
JPA 版本演进
- JPA 1.0:2006 年批准
- JPA 2.0:2009 年
- JPA 2.1:2013 年
- JPA 2.2:2017 年
Hibernate 与 JPA 的实际区别
理论上,JPA 让你无视底层实现(Hibernate/EclipseLink)。
实践中,由于 Hibernate 是最流行的 JPA 实现,JPA 规范常是 Hibernate 功能的“子集”。例如:
- JPQL 本质是功能受限的 HQL。
- 所有 JPQL 查询都是合法 HQL,但反之不成立。
因为 JPA 规范制定耗时,且只是各实现的“最大公约数”,所以其功能必然少于 Hibernate 等具体实现。
应该用 JPA 还是 Hibernate?
现实中通常有两种选择:
- 以 JPA 为主,在规范不足时补充 Hibernate 特有功能。
- 全程使用纯 Hibernate(个人偏好)。
两种方式均可,前提是你懂 SQL。
JPA 基本持久化操作
JPA 的入口是 EntityManagerFactory 和 EntityManager。对比 Hibernate 示例:
EntityManagerFactory factory =
Persistence.createEntityManagerFactory("org.hibernate.tutorial.jpa");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
em.persist(new User("John Wayne"));
em.persist(new User("John Snow"));
em.getTransaction().commit();
em.close();
除了命名不同(persist vs save,EntityManager vs Session),逻辑几乎一致。
事实上,Hibernate 源码揭示了真相:
public interface Session extends SharedSessionContract, EntityManager { /*...*/ }
public interface SessionFactory extends EntityManagerFactory { /*...*/ }
结论:
- Hibernate
Session就是 JPAEntityManager - Hibernate
SessionFactory就是 JPAEntityManagerFactory
JPA 查询语言:JPQL
JPQL 是 HQL 的子集。两者查询写法几乎相同:
// HQL
session.createQuery("UPDATE Person SET name = :newName WHERE name = :oldName")
.setParameter("oldName", oldName)
.setParameter("newName", newName)
.executeUpdate();
// JPQL
entityManager.createQuery("UPDATE Person p SET p.name = :newName WHERE p.name = :oldName")
.setParameter("oldName", oldName)
.setParameter("newName", newName)
.executeUpdate();
JPA Criteria API
与 HQL/JPQL 不同,JPA Criteria API 与 Hibernate Criteria API 完全不同(前文已介绍 Hibernate 版本)。
其他 JPA 实现
除 Hibernate 外,主要还有:
- EclipseLink(参见 Hibernate vs EclipseLink)
- TopLink(较老)
其他如 BatooJPA 等项目多已废弃,因维护完整 JPA 实现成本极高。截至 2020 年,Hibernate 拥有最活跃的社区。
QueryDSL
QueryDSL 如何融入 JPA 生态?此前我们或手写 HQL/JPQL(字符串拼接),或使用复杂的 Criteria API。
QueryDSL 试图结合两者优点:比 Criteria 更易用,比字符串更类型安全。
注:QueryDSL 曾一度停滞,但自 2020 年起重新活跃,并支持 NoSQL(如 MongoDB、Lucene)。
示例:执行 "SELECT * FROM users WHERE first_name = :name"
QUser user = QUser.user;
JPAQuery<?> query = new JPAQuery<>(entityManager);
List<User> users = query.select(user)
.from(user)
.where(user.firstName.eq("Hans"))
.fetch();
QUser 类由 QueryDSL 在编译时自动生成(需配置注解处理器),基于你的 JPA/Hibernate 注解类。生成的查询类型安全且可读性高,远胜 JPA Criteria 2.0。
ORM 框架总结
ORM 框架成熟而复杂。最大误区是认为“用了 ORM 就不用懂 SQL”。
确实,ORM 能快速实现基础类到表的映射。但若缺乏对其工作原理的理解,项目后期必将面临性能与维护难题。
核心建议:
务必扎实掌握 Hibernate(或所选 ORM)的工作机制,同时精通 SQL 和数据库原理。这才是正确之道。
Java SQL 库:轻量级方案
以下库更偏向 数据库优先(database-first)的轻量级方案,适合:
- 已有遗留数据库
- 新项目中先设计数据库 schema,再写 Java 类
jOOQ
jOOQ 是 Lukas Eder 维护的流行库(作者博客深度解析 SQL/Java/数据库)。其核心流程:
- 使用 jOOQ 代码生成器连接数据库,生成代表表/列的 Java 类。
- 用这些类构建类型安全的 SQL 查询(而非字符串)。
- jOOQ 自动将查询转为真实 SQL,执行并映射结果。
假设已为 Users 表生成代码,可执行如下查询:
Result<Record3<String, String, String>> result =
create.select(USERS.FIRST_NAME, USERS.LAST_NAME, SUBSCRIPTIONS.ID)
.from(USERS)
.join(SUBSCRIPTIONS).on(USERS.SUBSCRIPTION_ID.eq(SUBSCRIPTIONS.ID))
.where(USERS.FIRST_NAME.eq("Hans"))
.fetch();
jOOQ 不仅简化 SQL 构建和 CRUD 操作,还支持所有数据库特有功能(窗口函数、透视表、闪回查询、OLAP、存储过程等)。
MyBatis
MyBatis 是另一个活跃的数据库优先选择(源自 Apache iBATIS 3.0)。
其核心是 SqlSessionFactory。SQL 语句可放在 XML 文件中,或通过接口注解定义:
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUser(int id);
}
使用方式:
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUser(1);
MyBatis 内置简单映射(如列名匹配时自动转为 User 对象),复杂场景需在 XML 中手动配置。其强项是 Dynamic SQL(基于 XML 的动态 SQL 构建,支持 if/else/when 等逻辑)。
Jdbi
Jdbi 是 JDBC 之上的小型封装,提供更友好的 API。有两种风格:
Fluent API:
Jdbi jdbi = Jdbi.create("jdbc:h2:mem:test");
List<User> users = jdbi.withHandle(handle -> {
handle.execute("CREATE TABLE user (id INTEGER PRIMARY KEY, name VARCHAR)");
handle.createUpdate("INSERT INTO user(id, name) VALUES (:id, :name)")
.bindBean(new User(3, "David"))
.execute();
return handle.createQuery("SELECT * FROM user ORDER BY name")
.mapToBean(User.class)
.list();
});
Declarative API:通过接口定义查询(类似 MyBatis)。
fluent-jdbc
类似 Jdbi,是 JDBC 的便捷封装:
FluentJdbc fluentJdbc = new FluentJdbcBuilder()
.connectionProvider(dataSource)
.build();
Query query = fluentJdbc.query();
query.update("UPDATE CUSTOMER SET NAME = ?, ADDRESS = ?")
.params("John Doe", "Dallas")
.run();
List<Customer> customers = query.select("SELECT * FROM CUSTOMER WHERE NAME = ?")
.params("John Doe")
.listResult(customerMapper);
SimpleFlatMapper
SimpleFlatMapper 文档较少,但非常实用:它专注 ResultSet/Records 到 POJO 的映射,可与 JDBC、jOOQ、QueryDSL、Jdbi、Spring JDBC 等集成。
JDBC 集成示例:
JdbcMapper<User> userMapper = JdbcMapperFactory.newInstance().newMapper(User.class);
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM USERS")) {
ResultSet rs = ps.executeQuery();
userMapper.forEach(rs, System.out::println); // 打印所有 User 对象
}
Spring JDBC 与 Spring Data
Spring 生态庞大,建议先理解其核心,再接触 Spring Data。
Spring JDBC Template
JdbcTemplate(来自 spring-jdbc)是 Spring 最早的辅助类之一(2001 年起),不同于 Spring Data JDBC。
它是 JDBC 的便捷封装,提供:
- 更好的 ResultSet 处理
- 连接管理
- 异常转换(SQLException → RuntimeException)
- 与 Spring
@Transactional集成
两种风格:
JdbcTemplate(? 参数):
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.execute("CREATE TABLE users(id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))");
jdbcTemplate.batchUpdate("INSERT INTO users(first_name, last_name) VALUES (?,?)", Arrays.asList("john", "wayne"));
jdbcTemplate.query(
"SELECT id, first_name, last_name FROM users WHERE first_name = ?",
new Object[] { "Josh" },
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name"))
).forEach(user -> log.info(user.toString()));
NamedParameterJdbcTemplate(:name 参数):
NamedParameterJdbcTemplate namedTemplate = new NamedParameterJdbcTemplate(dataSource);
SqlParameterSource params = new MapSqlParameterSource().addValue("id", 1);
namedTemplate.queryForObject("SELECT * FROM USERS WHERE ID = :id", params, String.class);
Spring 事务管理:@Transactional
Spring 的核心优势之一是声明式事务:
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void doSomeBusinessLogic() {
entityManager.persist(new Event("First event!", new Date()));
entityManager.persist(new Event("Follow up!", new Date()));
}
相比手动管理事务(begin()/commit()/close()),@Transactional 更简洁,且能与 Hibernate、jOOQ、JPA 等无缝集成。
Spring Data JPA
Spring Data 是 Spring 的数据访问编程模型,旗下包含多个子项目:
- Spring Data JPA / Spring Data JDBC(本文重点)
- Spring Data REST、Redis、LDAP 等
Spring Data JPA / JDBC 是什么?
核心目标:简化 Repository/DAO 和 SQL 查询的编写。
典型模式:每个领域对象(如 User.java)对应一个 Repository(如 UserRepository),提供 findByEmail()、findById() 等方法。
Spring Data 的神奇之处:
它能理解你的 JPA 注解(@Entity、@Column 等),自动生成 Repository 实现!你无需编写任何实现代码即可获得全套 CRUD 操作。
自定义 Spring Data JPA Repository
只需继承 JpaRepository:
public interface MyUserRepository extends JpaRepository<User, Long> {
// 以下方法已由 JpaRepository 提供,无需实现!
List<T> findAll();
List<T> findAll(Sort sort);
<S extends T> List<S> saveAll(Iterable<S> entities);
// ...更多方法
}
自定义查询
Spring Data JPA 支持方法名约定查询(类似 Ruby on Rails):
public interface MyUserRepository extends JpaRepository<User, Long> {
List<User> findByEmailAddressAndLastname(String emailAddress, String lastname);
// 自动生成: SELECT * FROM Users WHERE email_address = ? AND last_name = ?
}
也可用 @Query 手写 JPQL:
@Query("SELECT u FROM User u WHERE u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
Spring Data JDBC 则不支持方法名约定,必须手写 SQL:
public interface MyUserRepository extends CrudRepository<User, Long> {
@Query("SELECT * FROM Users WHERE email = :emailAddress AND lastName = :lastName")
List<User> findByEmailAddressAndLastname(@Param("emailAddress") String emailAddress,
@Param("lastName") String lastName);
}
Spring Data 总结
- 核心:简化数据访问,基于 JPA 注解自动生成 DAO。
- Spring Data JPA:JPA/Hibernate 的便捷层,保留 ORM 全部功能。
- Spring Data JDBC:JDBC 的便捷层,无 ORM“魔法”,更可控。
- 最佳搭档:与 Spring Boot 项目天然集成。