Java数据库访问指南

原始地址:https://dev.to/marcobehler/a-guide-to-accessing-databases-in-java-2fdm

Java数据库访问指南

您可以使用本指南来发现、理解和选择访问数据库的正确Java库,比如MySQL、Postgres、Oracle或者其他数据库。
阅读本指南以了解哪些数据库库适合您的项目和开发团队。

介绍

每当您想要使用Java(服务器/桌面)应用程序连接到数据库时,就会出现三个问题:
您是从Java优先还是数据库优先的角度来开发应用程序?您想先编写哪个?或者说哪个已经存在?
您如何执行SQL语句,从小型CRUD操作(如插入)到复杂的SQL报表查询(分析函数)?
您有多容易从Java对象映射到数据库表?从数据库表映射到Java对象?

所以设想一下这样一个Java类:

public class User {
private Integer id;
private String firstName;
private String lastName;
// 构造函数/Getter和Setter....
}

现在假设你有三个这样的用户,并希望将它们保存到一个名为"users"的数据库表中:

idfirst_namelast_name
1hansihuber
2maxmutzke
3donaldtrump

结果有多种方法可以在Java中实现这个目标,甚至可以说有太多了。因此,让我们拿起刀砍出一条通向你的选择的途径。

然而,无论如何,了解基础知识都是非常重要的,因为其他选项都是基于这些基础之上构建起来的。所以,如果你对一些提到的概念感到不确定,请务必不要跳过下面的内容。

基本的Java数据库访问

JDBC驱动程序

为了连接到数据库,不管是MySQL还是H2,你都需要一个驱动程序,一个JDBC驱动程序。
JDBC驱动程序可以完成相当多的工作,从基本的打开数据库连接到更高级的功能,如从数据库接收事件(Oracle)。
所以无论您将在项目中使用哪个Java数据库库,您都需要去数据库的网站或者Maven库获取最新的JDBC驱动程序,并将其添加到您的项目中。
这就是您开始所需要的。如果您想连接到本机安装的MySQL数据库"test",则可以编写以下代码:

try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/test")) {
assertTrue(conn.isValid(1000));
// 进行数据库连接操作
}

连接池

打开和关闭数据库连接需要一定时间。特别是在Web应用程序中,您不希望每个用户都打开一个新的数据库连接,而是希望有一个小型连接池始终打开并共享。
这就是连接池的用途所在。
一些JDBC驱动程序,如Oracle,带有自己的连接池(UCP)。对于其他数据库,您需要下载一个连接池,而且在Java领域,选择众多。
在许多(旧)应用程序中,您会发现Apache Commons DBCP和C3P0这两个选项。
不幸的是,您可能会经常发现它们配置错误,因为它们缺乏合适的默认值,而且还经常在性能和处理错误情况(例如数据库宕机)方面遇到问题。
因此,我建议新项目使用以下两个比较新的连接池之一:
HikariCP或Vibur-dbcp。它们都非常稳定、高性能,提供合理的默认值和其他方便的特性。此外,为了获得最佳性能,请确保咨询每个连接池的数据库特定文档。
不管您选择哪种选项,您在代码中都不会直接通过DriverManager打开连接,而是向连接池(数据源)请求其中的一个连接,例如:

// 在使用Hikari连接池的情况下
DataSource ds = new HikariDataSource(config);
try (Connection connection = ds.getConnection()) {
// 使用连接
}

低级的数据库访问(JDBC)

Java中访问数据库最底层的方式是通过JDBC API(Java数据库连接)。JDBC随附于每个JDK,您不需要任何额外的库来使用它。而且还应该注意,每个Java数据库库都是在JDBC的基础之上构建的。

入门

一旦您打开了数据库连接(参见上文),您可以通过该连接执行SQL语句,就像在MySQL控制台或Oracle SQL Developer中一样。
但是,纯JDBC的使用有点麻烦,对于将Java对象映射到SQL代码来说没有太多帮助。事实上,您需要自己完成这一切。让我们看看使用纯JDBC编写的基本SQL插入语句的样子:

public void insertWithJdbc() {
User user = ... ;
DataSource ds = ...;
try (Connection conn = ds.getConnection()) {
conn.setAutoCommit(false); // 开启一个新的数据库事务
PreparedStatement statement = conn.prepareStatement("insert into users (first_name, last_name) values (?,?)");
statement.setString(1, user.getFirstName());
statement.setString(2, user.getLastName());
statement.executeUpdate();
conn.commit(); // 提交数据库事务
} catch (SQLException e) {
// 处理错误
// 回滚事务
}
}

如您所见,您需要完成以下工作:

  1. 打开数据库连接
  2. 开启数据库事务
  3. 创建一个PreparedStatement,表示SQL插入语句,并使用占位符
  4. 填充这些占位符(重复)
  5. 最后,提交事务
错误处理和资源清理

但是,这只是故事的一半。每当发生SQLException时,您都知道出了问题,但是您不知道是什么被提交或者哪些没有被提交。由于你不知道,你需要确保手动回滚事务。
此外,作为一名合格的开发人员,您还应该确保显式关闭PreparedStatement和/或连接,而不是希望它们在以后的代码中(取决于JDBC驱动程序的实现)被自动关闭,并释放任何与此相关的数据库资源。
幸运的是,随着Java 7引入的可关闭资源(try-with-resources)机制,这变得更加容易。
使用上面示例代码,尝试自行编写回滚连接的代码,并进行连接和语句的关闭。如果您有点迷茫,请仔细阅读相关的Oracle文档。

下面是一个 使用体面的Java代码编写的以上示例的样子:

public void insertWithJDBCSafelyFromJava7Onwards() {
User user = new User();
try (Connection cxn = DriverManager.getConnection("jdbc:mysql://localhost/test");
PreparedStatement stmt = cxn.prepareStatement("insert into users (first_name, last_name) values (?,?)")) {
try {
cxn.setAutoCommit(false); // 开启一个新的数据库事务
stmt.setString(1, user.getFirstName());
stmt.setString(2, user.getLastName());
stmt.executeUpdate();
cxn.commit(); // 提交数据库事务
} catch (SQLException e) {
// 处理应用程序错误
try {
cxn.rollback();
} catch (SQLException ex) {
// 除了记录错误,你别无它法
}
}
}
}

正如您所看到的,从Java 7开始,自动关闭连接和语句使清理操作变得更加容易。如果您的不幸是Android或者Java 6或更低版本,您可以使用Guava的Closer来实现资源管理方面几乎与此相同的代码。

我们还没有讨论从表中选择数据。我们来看一下:

public void selectWithJdbc() {
// 查看上面其他示例以了解有关statement变量的信息
ResultSet resultSet = statement.executeQuery("select * from users");
while (resultSet.next()) {
String firstName = rs.getString("first_name");
String lastName = rs.getString("last_name");
System.out.println("first name : " + firstName);
System.out.println("last name : " + lastName);
}
}

那么,主要的收获是,当使用JDBC时,您基本上是在使用裸金属。
您可以完全使用SQL语句,但是您需要确保自行在Java对象和SQL代码之间来回转换。此外,您还需要确保正确维护和关闭数据库连接/语句。
这就是诸如Spring(它的JDBC模板)等便利库派上用场的地方,在后面的部分我们将会了解到。

不过,使用JDBC直接是完全有效的,特别是对于初学者来说,应该在深入学习Hibernate之前先熟悉JDBC的概念。

如果您想学习Java数据库基础知识

我写了一本简短的书,包含了许多练习和代码示例,告诉您如何使JDBC访问变得简单易用。了解《Java Database Connections & Transactions》。

Java优先库

Java开发者通常更习惯编写Java类,而不是编写SQL语句。因此,许多新项目都是使用Java优先的方法编写的,这意味着您会在创建相应的数据库表之前创建Java类。
那么,您还需要解决一个问题:如何将Java类映射到这些表格?一种解决方案是使用所谓的ORM,即对象关系映射的库。

Hibernate

一个非常受欢迎的库,帮助您完成此操作的是Hibernate,它已经存在了很长时间。让我们深入了解它(如果您喜欢观看视频,请查看这些短小实用的视频教程,它们在现实场景中演示了所有后续概念):

假设您有以下表格,它基本上是在纯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;
//Getters and setters are omitted for brevity
}

现在,要开始,您显然需要将Hibernate库添加到您的项目中。然后创建一个Hibernate特定的配置文件hibernate.cfg.xml,以创建一个称为SessionFactory(也可以直接在Java代码中创建一个)。

什么是SessionFactory?为什么我需要它?

一个SessionFactory基本上表示您的Java类与数据库表之间的映射(稍后会详细解释)。此外,一个SessionFactory会生成Session(谁能想到),它实质上是一个数据库连接(或者更准确地说是JDBC连接的包装器),同时还具有其他更强大的功能。

但首先,让我们看看映射。当然,Hibernate无法知道如何将您的类映射成适当的数据库表,所以您需要进行映射。传统上,您会在.xml文件中进行映射,但最近很多项目已经转向注释形式的映射。您将在本指南中了解有关注释的更多信息,但如果您想查看遗留的XML映射,请查看此链接。

假设您通过纯Java配置进行Hibernate配置,而没有.cfg.xml文件,那么您的代码应该大致如下:

// Hibernate特定的配置类
MetadataSources sources = new MetadataSources( standardRegistry );
// 添加一个使用JPA/Hibernate注释进行映射的类
sources.addAnnotatedClass( User.class );

这样Hibernate就知道您有一个User类,该类具有注释(最重要的是@Entity注释),告诉Hibernate如何自动将该类映射到适当的数据库表。让我们来看看注释的User类。

@Entity
@Table(name="users")
public static class User {
@Id
private Integer id;
@Column(name="first_name")
private String firstName;
@Column(name="last_name")
private String lastName;
//Getter和Setter被省略以保持简洁
}

关于各个注解可以说很多,超出本指南的范围。相反,我们将着重介绍大局。

通过Hibernate进行基本的CRUD操作

现在您已经设置了映射,唯一剩下要做的就是从sessionFactory获取一个session(读:数据库连接)并将用户保存到数据库中。没错,您不需要为此编写任何SQL语句,Hibernate将为您执行此操作。

Session session = sessionFactory.openSession();
User user = new User();
user.setFirstName("Hans");
user.setLastName("Dampf");
// 这一行代码将为您生成并执行"insert into users" SQL语句!
session.save( user );

类似地,还有其他方法允许您在不编写任何SQL代码的情况下执行其他SQL语句(例如select、update、delete),Hibernate将为您完成这些操作。

// "select from users where id = 1"
User user = session.get( User.class, 1 );
// "update users set...where id=1"
session.update(user);
// "delete from useres where id=1"
session.delete(user);
HQL

当然,有时您需要更多的控制,希望编写自己的SQL语句,特别是更复杂的查询而不仅仅是"select where id = 1"。
Hibernate提供了一种查询语言,称为HQL。HQL类似于SQL,但其重点是于对象,实际上与SQL方言无关。因此,同一条HQL语句可适用于所有数据库查询(缺点是您失去了特定于数据库的查询功能)。
让我们看看它是什么样的:

List<User> users = session.createQuery("select from User u where u.firstName = 'hans'", User.class).list();
session.createQuery("update User u set u.lastName = :newName where u.lastName = :oldName")
.executeUpdate();

请注意,在上面两个示例中,您在查询字符串中访问Java属性(u.lastName),而不是SQL列。Hibernate将这些HQL语句转换为适当的、特定于数据库的SQL语句。

Criteria

除了HQL,Hibernate还提供了通过Criteria API进行查询的功能,这是一种类型安全、编程式的API。然而,要设置它有点棘手,因为您的项目需要一个注解处理插件来为您的注释类生成“静态元模型”。而且,与查询API本身相比,存在两个版本的Criteria API(1和2),而且相当多的开发人员发现版本1比版本2更容易使用,尽管版本1正在被弃用,并且在未来可能完全不可用。无论如何,我们来看一下上面的HQL查询在Criteria形式下的写法,Hibernate会将其转换为适当的、特定于数据库的SQL语句。

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();

正如您所看到的,与生成简单的"select * from users where firstName = ?"所需的六行代码相比,这个示例上面更加简洁。

黑暗面

Hibernate不仅提供了简单的映射和查询功能。当然,真实场景中的映射和查询要比以上例子复杂得多。此外,它还提供了一系列其他的便利功能,例如级联、延迟加载、缓存等等。
它确实是一个复杂的软件,你不能只通过复制粘贴一些在线代码教程来完全理解它。这早晚会让你声称“Hibernate正在执行无法理解的随机魔术”或类似的说法。因为你缺乏关于Hibernate实际执行的背景知识。

此外,使用Hibernate并不意味着您不需要再关心SQL语句了。软件变得越复杂,检查Hibernate生成的SQL语句的重要性就越大,或者编写您自己的优化SQL/HQL/Criteria语句。
换句话说,要高效使用Hibernate,您需要对Hibernate和SQL都有良好的了解。

推荐阅读和观看

您可以阅读《Java Persistence with Hibernate》一书,并通过其中的示例进行实践。这本书有608页,这已经显示了其复杂性,但阅读这本书将极大地提高您的技能。
如果您想要有关Hibernate的更高级信息,请确保查看Vlad Mihalcea的网站。Vlad对Hibernate问题非常了解,不仅在Hibernate上运行博客,而且还提供书籍、视频等。

您还可以在这个网站上找到Hibernate特定的视频教程,为您提供沉浸式且节省时间的入门体验。

JPA

历史和概述

如果您听说过Hibernate,那么您可能也听说过JPA(Java持久化API)。但是JPA是什么?它与Hibernate这样的库相比如何?

首先,JPA只是一个规范,而不是实现或库(在2006年,发布了JPA 1.0规范,最新版本是2017年的JPA 2.2)。因此,JPA定义了一个标准,数据库库需要实现这个标准。

也许您已经猜到了,Hibernate并不是Java数据库库中唯一的一个。还有其他非常相似的库,比如EclipseLink和TopLink,我们将在下一部分中了解更多信息。
接下来有趣的部分是Hibernate、EclipseLink和TopLink
统一运行JPA规范,这意味着 - 在理论上 - 您可以编写与Hibernate特定代码或EclipseLink特定代码无关的JPA特定代码,只需向您的项目添加一些配置文件和所需的数据库库(也称为持久性提供者)即可访问数据库。

我们建议您先了解一下Hibernate,因为它已经在Java开发中变得成熟,并且有一个庞大的用户群。当然,EclipseLink和TopLink也是不错的选择,但是相对于Hibernate在市场份额上明显逊色。

总结

JPA实现是成熟而复杂的软件。
最大的陷阱是认为在使用JPA实现时不需要了解SQL。它们为您提供了一个快速的入门,可以将基本类映射到数据库表。
但是,结合缺乏有关ORM工作原理的基础知识,这往往会导致项目后期遇到问题。
总结:
确保您对SQL和数据库有很好的基础知识(Java开发者通常没有)。
选择一个拥有活跃社区、良好文档和定期发布版本的库。
透彻理解您的数据库库,即花时间阅读这608页的JPA规范。
您的项目可以选择任何JPA实现(Hibernate是很好的起点)。
您的项目也可以选择JooQ或其他提到的数据库优先库。
您还可以组合这些库,例如将JPA实现与JooQ或纯JDBC组合使用,或者通过QueryDSL等工具提供更多便利。

以上是今天的内容。如果您有任何问题或者发现拼写错误,请在评论区留言或发送邮件给我。
感谢阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值