Java 与数据库:主流库与 API 概览

更新于 2025-12-29

Marco Behler 2022-08-24

你可以使用本指南全面了解 Java 中所有流行的数据库库和 API,涵盖 JDBC、Hibernate、JPA、jOOQ、Spring Data 等。


Java 与数据库:入门介绍

当你希望在(服务器端或桌面端)Java 应用中访问数据库时,通常会面临以下三个问题:

  1. 你是采用“Java 优先”还是“数据库优先”的开发方式?
    你是先写 Java 类还是先写 SQL 语句?是否需要集成一个已存在的数据库?

  2. 你如何执行 SQL 语句?
    从小型的 CRUD 操作(SELECT FROMINSERT INTOUPDATE WHERE)到更复杂的 SQL 报表查询(分析函数等)?

  3. 对象-关系映射(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 功能远不止基础映射和查询,还包括级联、懒加载、缓存等。它是极其复杂的软件,无法仅靠复制教程代码掌握。

这导致两个常见问题:

  1. “Hibernate 在搞魔法!”
    开发者因缺乏背景知识,无法理解其内部行为。
  2. “用 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?

现实中通常有两种选择:

  1. 以 JPA 为主,在规范不足时补充 Hibernate 特有功能。
  2. 全程使用纯 Hibernate(个人偏好)。

两种方式均可,前提是你懂 SQL

JPA 基本持久化操作

JPA 的入口是 EntityManagerFactoryEntityManager。对比 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 saveEntityManager vs Session),逻辑几乎一致。

事实上,Hibernate 源码揭示了真相:

public interface Session extends SharedSessionContract, EntityManager { /*...*/ }
public interface SessionFactory extends EntityManagerFactory { /*...*/ }

结论

  • Hibernate Session 就是 JPA EntityManager
  • Hibernate SessionFactory 就是 JPA EntityManagerFactory

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 外,主要还有:

其他如 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/数据库)。其核心流程:

  1. 使用 jOOQ 代码生成器连接数据库,生成代表表/列的 Java 类
  2. 用这些类构建类型安全的 SQL 查询(而非字符串)。
  3. 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 项目天然集成。