连接 DataSource 对象
原文: https://docs.oracle.com/javase/tutorial/jdbc/basics/sqldatasources.html
本节介绍DataSource
对象,这是获取数据源连接的首选方法。除了将在后面解释的其他优点之外,DataSource
对象还可以提供连接池和分布式事务。此功能对于企业数据库计算至关重要。特别是,它是 Enterprise JavaBeans(EJB)技术不可或缺的一部分。
本节介绍如何使用DataSource
接口建立连接以及如何使用分布式事务和连接池。这两者都涉及 JDBC 应用程序中很少的代码更改。
部署使这些操作成为可能的类(系统管理员通常使用工具(例如 Apache Tomcat 或 Oracle WebLogic Server)执行)所执行的工作因所部署的DataSource
对象的类型而异。因此,本节的大部分内容专门用于说明系统管理员如何设置环境,以便程序员可以使用DataSource
对象来获取连接。
涵盖以下主题:
在建立连接中,您学习了如何使用DriverManager
类建立连接。本节介绍如何使用DataSource
对象获取与数据源的连接,这是首选方法。
由实现DataSource
的类实例化的对象表示特定的 DBMS 或某些其他数据源,例如文件。 DataSource
对象表示特定的 DBMS 或某些其他数据源,例如文件。如果公司使用多个数据源,它将为每个数据源部署一个单独的DataSource
对象。 DataSource
接口由驱动程序供应商实现。它可以通过三种不同的方式实现:
- 基本的
DataSource
实现生成标准的Connection
对象,这些对象不是在分布式事务中合并或使用的。 - 支持连接池的
DataSource
实现生成参与连接池的Connection
对象,即可以回收的连接。 - 支持分布式事务的
DataSource
实现生成可以在分布式事务中使用的Connection
对象,即访问两个或多个 DBMS 服务器的事务。
JDBC 驱动程序应至少包含一个基本的DataSource
实现。例如,Java DB JDBC 驱动程序包括实现org.apache.derby.jdbc.ClientDataSource
和 MySQL,com.mysql.jdbc.jdbc2.optional.MysqlDataSource
。如果您的客户端在 Java 8 compact profile 2 上运行,则 Java DB JDBC 驱动程序为org.apache.derby.jdbc.BasicClientDataSource40
。本教程的示例需要紧凑的配置文件 3 或更高版本。
支持分布式事务的DataSource
类通常还实现对连接池的支持。例如,EJB 供应商提供的DataSource
类几乎总是支持连接池和分布式事务。
假设从之前的例子中,兴旺的咖啡店连锁店的所有者决定通过互联网销售咖啡进一步扩大。由于预计会有大量的在线业务,所有者肯定需要连接池。打开和关闭连接涉及大量开销,并且所有者预期该在线订购系统将需要大量的查询和更新。通过连接池,可以反复使用连接池,从而避免为每个数据库访问创建新连接的费用。此外,所有者现在拥有第二个 DBMS,其中包含最近收购的咖啡烘焙公司的数据。这意味着所有者希望能够编写使用旧 DBMS 服务器和新 DBMS 服务器的分布式事务。
连锁店主已重新配置计算机系统,以服务于更大的新客户群。所有者购买了最新的 JDBC 驱动程序和与其一起使用的 EJB 应用程序服务器,以便能够使用分布式事务并获得连接池带来的更高性能。许多 JDBC 驱动程序与最近购买的 EJB 服务器兼容。所有者现在具有三层体系结构,中间层有一个新的 EJB 应用程序服务器和 JDBC 驱动程序,第二层有两个 DBMS 服务器。发出请求的客户端计算机是第一层。
系统管理员需要部署DataSource
对象,以便 Coffee Break 的编程团队可以开始使用它们。部署DataSource
对象包含三个任务:
- 创建
DataSource
类的实例 - 设置其属性
- 将其注册为使用 Java 命名和目录接口(JNDI)API 的命名服务
首先,考虑最基本的情况,即使用DataSource
接口的基本实现,即不支持连接池或分布式事务的接口。在这种情况下,只需要部署一个DataSource
对象。 DataSource
的基本实现产生与DriverManager
类产生的相同类型的连接。
创建 DataSource 类的实例并设置其属性
假设只想要DataSource
基本实现的公司从 JDBC 供应商 DB Access,Inc。购买了一个驱动程序。该驱动程序包含实现DataSource
接口的类com.dbaccess.BasicDataSource
。以下代码摘录创建类BasicDataSource
的实例并设置其属性。部署BasicDataSource
实例后,程序员可以调用方法DataSource.getConnection
来连接公司的数据库,CUSTOMER_ACCOUNTS
。首先,系统管理员使用默认构造器创建BasicDataSource
对象_ds_
。然后系统管理员设置三个属性。请注意,以下代码通常由部署工具执行:
com.dbaccess.BasicDataSource ds = new com.dbaccess.BasicDataSource();
ds.setServerName("grinder");
ds.setDatabaseName("CUSTOMER_ACCOUNTS");
ds.setDescription("Customer accounts database for billing");
变量_ds_
现在代表服务器上安装的数据库CUSTOMER_ACCOUNTS
。 BasicDataSource
对象_ds_
产生的任何连接都将连接到数据库CUSTOMER_ACCOUNTS
。
使用使用 JNDI API 的命名服务注册 DataSource 对象
设置属性后,系统管理员可以使用 JNDI(Java 命名和目录接口)命名服务注册BasicDataSource
对象。使用的特定命名服务通常由系统属性确定,此处未显示。以下代码摘录注册BasicDataSource
对象并将其与逻辑名jdbc/billingDB
绑定:
Context ctx = new InitialContext();
ctx.bind("jdbc/billingDB", ds);
此代码使用 JNDI API。第一行创建一个InitialContext
对象,该对象用作名称的起始点,类似于文件系统中的根目录。第二行将BasicDataSource
对象_ds_
与逻辑名jdbc/billingDB
关联或绑定。在下一个代码摘录中,您为命名服务提供此逻辑名称,并返回BasicDataSource
对象。逻辑名称可以是任何字符串。在这种情况下,公司决定使用名称billingDB
作为CUSTOMER_ACCOUNTS
数据库的逻辑名称。
在前面的示例中,jdbc
是初始上下文下的子上下文,就像根目录下的目录是子目录一样。名称jdbc/billingDB
类似于路径名,其中路径中的最后一项类似于文件名。在这种情况下,billingDB
是赋予BasicDataSource
对象_ds_
的逻辑名称。子上下文jdbc
保留用于绑定到DataSource
对象的逻辑名称,因此jdbc
将始终是数据源逻辑名的第一部分。
使用已部署的 DataSource 对象
在系统管理员部署基本DataSource
实现之后,程序员可以使用它。这意味着程序员可以提供绑定到DataSource
类实例的逻辑数据源名称,JNDI 命名服务将返回该DataSource
类的实例。然后可以在DataSource
对象上调用方法getConnection
以获得与其表示的数据源的连接。例如,程序员可能会编写以下两行代码来获取DataSource
对象,该对象生成与数据库CUSTOMER_ACCOUNTS
的连接。
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/billingDB");
第一行代码获取初始上下文作为检索DataSource
对象的起点。当您向方法lookup
提供逻辑名称jdbc/billingDB
时,该方法返回系统管理员在部署时绑定到jdbc/billingDB
的DataSource
对象。因为方法lookup
的返回值是 Java Object
,所以我们必须将其转换为更具体的DataSource
类型,然后再将其赋值给变量_ds_
。
变量_ds_
是实现DataSource
接口的类com.dbaccess.BasicDataSource
的实例。调用方法_ds_ .getConnection
产生与CUSTOMER_ACCOUNTS
数据库的连接。
Connection con = ds.getConnection("fernanda","brewed");
getConnection
方法仅需要用户名和密码,因为变量_ds_
具有与CUSTOMER_ACCOUNTS
数据库建立连接所需的其余信息,例如数据库名称和位置,在其属性中。
DataSource 对象的优点
由于它的属性,DataSource
对象是获得连接的DriverManager
类的更好选择。程序员不再需要在其应用程序中对驱动程序名称或 JDBC URL 进行硬编码,这使得它们更具可移植性。此外,DataSource
属性使维护代码更加简单。如果有更改,系统管理员可以更新数据源属性,而不用担心更改连接到数据源的每个应用程序。例如,如果将数据源移动到其他服务器,则系统管理员所要做的就是将serverName
属性设置为新的服务器名称。
除了便携性和易维护性之外,使用DataSource
对象获取连接还可以提供其他优势。当DataSource
接口实现为使用ConnectionPoolDataSource
实现时,该DataSource
类的实例产生的所有连接将自动成为池连接。类似地,当DataSource
实现被实现为使用XADataSource
类时,它产生的所有连接将自动成为可以在分布式事务中使用的连接。下一节将介绍如何部署这些类型的DataSource
实现。
系统管理员或以该容量工作的其他人可以部署DataSource
对象,以便它生成的连接是池连接。为此,他或她首先部署ConnectionPoolDataSource
对象,然后部署一个DataSource
对象来实现它。设置ConnectionPoolDataSource
对象的属性,使其表示将生成连接的数据源。使用 JNDI 命名服务注册ConnectionPoolDataSource
对象后,将部署DataSource
对象。通常只需为DataSource
对象设置两个属性:description
和dataSourceName
。赋予dataSourceName
属性的值是标识先前部署的ConnectionPoolDataSource
对象的逻辑名称,该对象包含进行连接所需的属性。
部署ConnectionPoolDataSource
和DataSource
对象后,可以调用DataSource
对象上的方法DataSource.getConnection
并获得池化连接。此连接将指向ConnectionPoolDataSource
对象属性中指定的数据源。
以下示例描述了 The Coffee Break 的系统管理员如何部署实现的DataSource
对象以提供池化连接。系统管理员通常使用部署工具,因此本节中显示的代码片段是部署工具将执行的代码。
为了获得更好的性能,The Coffee Break 公司从 DB Access,Inc。购买了一个 JDBC 驱动程序,其中包含实现ConnectionPoolDataSource
接口的类com.dbaccess.ConnectionPoolDS
。系统管理员创建创建此类的实例,设置其属性,并将其注册到 JNDI 命名服务。 Coffee Break 从其 EJB 服务器供应商 Application Logic,Inc。购买了DataSource
类com.applogic.PooledDataSource
。类com.applogic.PooledDataSource
通过使用ConnectionPoolDataSource
类com.dbaccess.ConnectionPoolDS
提供的底层支持来实现连接池。
必须首先部署ConnectionPoolDataSource
对象。以下代码创建com.dbaccess.ConnectionPoolDS
的实例并设置其属性:
com.dbaccess.ConnectionPoolDS cpds = new com.dbaccess.ConnectionPoolDS();
cpds.setServerName("creamer");
cpds.setDatabaseName("COFFEEBREAK");
cpds.setPortNumber(9040);
cpds.setDescription("Connection pooling for " + "COFFEEBREAK DBMS");
部署ConnectionPoolDataSource
对象后,系统管理员将部署DataSource
对象。以下代码使用 JNDI 命名服务注册com.dbaccess.ConnectionPoolDS
对象_cpds_
。请注意,与_cpds_
变量关联的逻辑名称在子上下文jdbc
下添加了子上下文pool
,类似于将子目录添加到分层中的另一个子目录文件系统。类com.dbaccess.ConnectionPoolDS
的任何实例的逻辑名称将始终以jdbc/pool
开头。 Oracle 建议将所有ConnectionPoolDataSource
对象放在子上下文jdbc/pool
下:
Context ctx = new InitialContext();
ctx.bind("jdbc/pool/fastCoffeeDB", cpds);
接下来,部署实现与_cpds_
变量和com.dbaccess.ConnectionPoolDS
类的其他实例交互的DataSource
类。以下代码创建此类的实例并设置其属性。请注意,此com.applogic.PooledDataSource
实例仅设置了两个属性。设置description
属性是因为始终需要它。设置的另一个属性dataSourceName
为_cpds_
提供逻辑 JNDI 名称,它是com.dbaccess.ConnectionPoolDS
类的实例。换句话说, _cpds_
表示将为DataSource
对象实现连接池的ConnectionPoolDataSource
对象。
以下代码(可能由部署工具执行)创建PooledDataSource
对象,设置其属性,并将其绑定到逻辑名jdbc/fastCoffeeDB
:
com.applogic.PooledDataSource ds = new com.applogic.PooledDataSource();
ds.setDescription("produces pooled connections to COFFEEBREAK");
ds.setDataSourceName("jdbc/pool/fastCoffeeDB");
Context ctx = new InitialContext();
ctx.bind("jdbc/fastCoffeeDB", ds);
此时,部署了DataSource
对象,应用程序可以从该对象获得与数据库COFFEEBREAK
的池连接。
连接池是数据库连接对象的缓存。对象表示应用程序可用于连接到数据库的物理数据库连接。在运行时,应用程序从池请求连接。如果池包含可以满足请求的连接,则它将返回与应用程序的连接。如果未找到任何连接,则会创建新连接并将其返回给应用程序。应用程序使用连接对数据库执行某些操作,然后将对象返回池。然后,该连接可用于下一个连接请求。
连接池可以促进连接对象的重用,并减少创建连接对象的次数。连接池显着提高了数据库密集型应用程序的性能,因为创建连接对象在时间和资源方面都很昂贵。
现在已经部署了这些DataSource
和ConnectionPoolDataSource
对象,程序员可以使用DataSource
对象来获得池化连接。获取池连接的代码就像获取非池连接的代码一样,如以下两行所示:
ctx = new InitialContext();
ds = (DataSource)ctx.lookup("jdbc/fastCoffeeDB");
变量_ds_
表示DataSource
对象,该对象产生与数据库COFFEEBREAK
的池连接。您只需要检索一次DataSource
对象,因为您可以根据需要使用它来生成尽可能多的池化连接。在_ds_
变量上调用方法getConnection
会自动产生池连接,因为_ds_
变量代表的DataSource
对象被配置为生成池连接。
连接池通常对程序员是透明的。使用池化连接时,只需要执行两项操作:
使用
DataSource
对象而不是DriverManager
类来获取连接。在下面的代码行中,_ds_
是实现和部署的DataSource
对象,因此它将创建池连接,username
和password
是表示凭证的变量有权访问数据库的用户:Connection con = ds.getConnection(username, password);
使用
finally
语句关闭池连接。以下finally
块将出现在适用于使用池化连接的代码的try/catch
块之后:try {
Connection con = ds.getConnection(username, password);
// ... code to use the pooled
// connection con
} catch (Exception ex {
// ... code to handle exceptions
} finally {
if (con != null) con.close();
}
否则,使用池化连接的应用程序与使用常规连接的应用程序相同。应用程序员在完成连接池时可能会注意到的另一件事是性能更好。
以下示例代码获取DataSource
对象,该对象生成与数据库COFFEEBREAK
的连接,并使用它来更新表COFFEES
中的价格:
import java.sql.*;
import javax.sql.*;
import javax.ejb.*;
import javax.naming.*;
public class ConnectionPoolingBean implements SessionBean {
// ...
public void ejbCreate() throws CreateException {
ctx = new InitialContext();
ds = (DataSource)ctx.lookup("jdbc/fastCoffeeDB");
}
public void updatePrice(float price, String cofName,
String username, String password)
throws SQLException{
Connection con;
PreparedStatement pstmt;
try {
con = ds.getConnection(username, password);
con.setAutoCommit(false);
pstmt = con.prepareStatement("UPDATE COFFEES " +
"SET PRICE = ? " +
"WHERE COF_NAME = ?");
pstmt.setFloat(1, price);
pstmt.setString(2, cofName);
pstmt.executeUpdate();
con.commit();
pstmt.close();
} finally {
if (con != null) con.close();
}
}
private DataSource ds = null;
private Context ctx = null;
}
此代码示例中的连接参与连接池,因为以下情况属实:
- 已部署实现
ConnectionPoolDataSource
的类的实例。 - 已部署实现
DataSource
的类的实例,并且为其dataSourceName
属性设置的值是绑定到先前部署的ConnectionPoolDataSource
对象的逻辑名称。
请注意,尽管此代码与您之前看到的代码非常相似,但它在以下方面有所不同:
除
java.sql
外,它还导入javax.sql
,javax.ejb
和javax.naming
包。DataSource
和ConnectionPoolDataSource
接口位于javax.sql
包中,JNDI 构造器InitialContext
和方法Context.lookup
是javax.naming
包的一部分。此特定示例代码采用 EJB 组件的形式,该组件使用javax.ejb
包中的 API。此示例的目的是显示您使用池化连接的方式与使用非池化连接的方式相同,因此您无需担心理解 EJB API。它使用
DataSource
对象来获取连接,而不是使用DriverManager
工具。它使用
finally
块来确保连接已关闭。
获取和使用池化连接类似于获取和使用常规连接。当充当系统管理员的人已正确部署ConnectionPoolDataSource
对象和DataSource
对象时,应用程序使用该DataSource
对象获取池化连接。但是,应用程序应使用finally
块关闭池化连接。为简单起见,前面的示例使用了finally
块但没有使用catch
块。如果try
块中的方法抛出异常,则默认情况下将抛出该异常,并且在任何情况下都将执行finally
子句。
可以部署DataSource
对象以获取可在分布式事务中使用的连接。与连接池一样,必须部署两个不同的类实例:XADataSource
对象和实现与之一起使用的DataSource
对象。
假设 The Coffee Break 企业家购买的 EJB 服务器包含DataSource
类com.applogic.TransactionalDS
,它与XADataSource
类XADataSource
类一起使用。它适用于任何XADataSource
类的事实使 EJB 服务器可以跨 JDBC 驱动程序移植。部署DataSource
和XADataSource
对象时,生成的连接将能够参与分布式事务。在这种情况下,实现类com.applogic.TransactionalDS
,以便生成的连接也是池连接,这通常是作为 EJB 服务器实现的一部分提供的DataSource
类的情况。
必须首先部署XADataSource
对象。以下代码创建com.dbaccess.XATransactionalDS
的实例并设置其属性:
com.dbaccess.XATransactionalDS xads = new com.dbaccess.XATransactionalDS();
xads.setServerName("creamer");
xads.setDatabaseName("COFFEEBREAK");
xads.setPortNumber(9040);
xads.setDescription("Distributed transactions for COFFEEBREAK DBMS");
以下代码使用 JNDI 命名服务注册com.dbaccess.XATransactionalDS
对象_xads_
。请注意,与_xads_
相关联的逻辑名称在jdbc
下添加了子上下文xa
。 Oracle 建议类com.dbaccess.XATransactionalDS
的任何实例的逻辑名始终以jdbc/xa
开头。
Context ctx = new InitialContext();
ctx.bind("jdbc/xa/distCoffeeDB", xads);
接下来,部署实现为与_xads_
和其他XADataSource
对象交互的DataSource
对象。请注意,DataSource
类com.applogic.TransactionalDS
可以与任何 JDBC 驱动程序供应商的XADataSource
类一起使用。部署DataSource
对象涉及创建com.applogic.TransactionalDS
类的实例并设置其属性。 dataSourceName
属性设置为jdbc/xa/distCoffeeDB
,与com.dbaccess.XATransactionalDS
关联的逻辑名称。这是XADataSource
类,它实现DataSource
类的分布式事务功能。以下代码部署DataSource
类的实例:
com.applogic.TransactionalDS ds = new com.applogic.TransactionalDS();
ds.setDescription("Produces distributed transaction " +
"connections to COFFEEBREAK");
ds.setDataSourceName("jdbc/xa/distCoffeeDB");
Context ctx = new InitialContext();
ctx.bind("jdbc/distCoffeeDB", ds);
现在已经部署了类com.applogic.TransactionalDS
和com.dbaccess.XATransactionalDS
的实例,应用程序可以在TransactionalDS
类的实例上调用方法getConnection
以获得可以在分布式中使用的COFFEEBREAK
数据库的连接交易。
要获得可用于分布式事务的连接,必须使用已正确实现和部署的DataSource
对象,如部署分布式事务部分所示。使用这样的DataSource
对象,在其上调用方法getConnection
。连接后,使用它就像使用任何其他连接一样。因为jdbc/distCoffeesDB
已与 JNDI 命名服务中的XADataSource
对象关联,所以以下代码生成可在分布式事务中使用的Connection
对象:
Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/distCoffeesDB");
Connection con = ds.getConnection();
当它是分布式事务的一部分时,对如何使用此连接存在一些次要但重要的限制。事务管理器控制分布式事务何时开始以及何时提交或回滚;因此,应用程序代码永远不应该调用方法Connection.commit
或Connection.rollback
。应用程序同样应该永远不会调用Connection.setAutoCommit(true)
,这会启用自动提交模式,因为这也会干扰事务管理器对事务边界的控制。这解释了为什么在分布式事务范围内创建的新连接默认情况下禁用其自动提交模式。请注意,这些限制仅适用于连接参与分布式事务的情况;连接不是分布式事务的一部分时没有限制。
对于以下示例,假设已发送咖啡订单,这会触发对位于不同 DBMS 服务器上的两个表的更新。第一个表是新的INVENTORY
表,第二个表是COFFEES
表。由于这些表位于不同的 DBMS 服务器上,因此涉及它们的事务将是分布式事务。以下示例中的代码获取连接,更新COFFEES
表并关闭连接,是分布式事务的第二部分。
请注意,代码未显式提交或回滚更新,因为分布式事务的范围由中间层服务器的底层系统基础结构控制。此外,假设用于分布式事务的连接是池连接,应用程序使用finally
块来关闭连接。这可以保证即使抛出异常也会关闭有效连接,从而确保连接返回到连接池以进行回收。
以下代码示例演示了企业 Bean,它是一个实现客户端计算机可以调用的方法的类。此示例的目的是演示分布式事务的应用程序代码与其他代码没有区别,只是它不调用Connection
方法commit
,rollback
或setAutoCommit(true)
。因此,您无需担心了解所使用的 EJB API。
import java.sql.*;
import javax.sql.*;
import javax.ejb.*;
import javax.naming.*;
public class DistributedTransactionBean implements SessionBean {
// ...
public void ejbCreate() throws CreateException {
ctx = new InitialContext();
ds = (DataSource)ctx.lookup("jdbc/distCoffeesDB");
}
public void updateTotal(int incr, String cofName, String username,
String password)
throws SQLException {
Connection con;
PreparedStatement pstmt;
try {
con = ds.getConnection(username, password);
pstmt = con.prepareStatement("UPDATE COFFEES " +
"SET TOTAL = TOTAL + ? " +
"WHERE COF_NAME = ?");
pstmt.setInt(1, incr);
pstmt.setString(2, cofName);
pstmt.executeUpdate();
stmt.close();
} finally {
if (con != null) con.close();
}
}
private DataSource ds = null;
private Context ctx = null;
}