# 连接 DataSource 对象

> 原文： [https://docs.oracle.com/javase/tutorial/jdbc/basics/sqldatasources.html](https://docs.oracle.com/javase/tutorial/jdbc/basics/sqldatasources.html)

本节介绍`DataSource`对象，这是获取数据源连接的首选方法。除了将在后面解释的其他优点之外，`DataSource`对象还可以提供连接池和分布式事务。此功能对于企业数据库计算至关重要。特别是，它是 Enterprise JavaBeans（EJB）技术不可或缺的一部分。

本节介绍如何使用`DataSource`接口建立连接以及如何使用分布式事务和连接池。这两者都涉及 JDBC 应用程序中很少的代码更改。

部署使这些操作成为可能的类（系统管理员通常使用工具（例如 Apache Tomcat 或 Oracle WebLogic Server）执行）所执行的工作因所部署的`DataSource`对象的类型而异。因此，本节的大部分内容专门用于说明系统管理员如何设置环境，以便程序员可以使用`DataSource`对象来获取连接。

涵盖以下主题：

*   [使用 DataSource 对象获取连接](#datasource_connection)
*   [部署基本数据源对象](#deploy_datasource)
*   [部署其他数据源实现](#datasource_implementation)
*   [获取和使用池连接](#pooled_connection)
*   [部署分布式事务](#deployment_distributed_transactions)
*   [使用分布式事务的连接](#using_connections_distributed_transactions)

在[建立连接](connecting.html)中，您学习了如何使用`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`对象包含三个任务：

1.  创建`DataSource`类的实例
2.  设置其属性
3.  将其注册为使用 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`对象被配置为生成池连接。

连接池通常对程序员是透明的。使用池化连接时，只需要执行两项操作：

1.  使用`DataSource`对象而不是`DriverManager`类来获取连接。在下面的代码行中， `_ds_`是实现和部署的`DataSource`对象，因此它将创建池连接，`username`和`password`是表示凭证的变量有权访问数据库的用户：

    ```
    Connection con = ds.getConnection(username, password);

    ```

2.  使用`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`对象，如[部署分布式事务](#deployment_distributed_transactions)部分所示。使用这样的`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;
}

```