连接数据源对象

本节介绍DataSource对象,这是获得与数据源的连接的首选方法。除了它们的其他优点(稍后将进行解释)之外,DataSource对象还可以提供连接池和分布式事务。此功能对于企业数据库计算至关重要。特别是,它是 Enterprise JavaBeans(EJB)技术不可或缺的。

本节说明如何使用DataSourceinterface获得连接以及如何使用分布式事务和连接池。两者都涉及 JDBC 应用程序中很少的代码更改。

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

涵盖以下主题:

使用数据源对象获取连接

构建连接中,您学习了如何使用DriverManager类构建连接。本节说明如何使用DataSource对象获得与数据源的连接,这是首选方法。

由实现DataSource的类实例化的对象表示特定的 DBMS 或某些其他数据源,例如文件。 DataSource对象代表特定的 DBMS 或某些其他数据源,例如文件。如果公司使用多个数据源,它将为每个数据源部署一个单独的DataSource对象。 DataSourceinterface由驱动程序供应商实现。它可以通过三种不同的方式实现:

  • 基本的DataSource实现产生的标准Connection对象没有在分布式事务中合并或使用。

  • 支持连接池的DataSource实现会产生Connection对象,这些对象将参与连接池,即可以回收的连接。

  • 支持分布式事务的DataSource实现产生Connection对象,这些对象可用于分布式事务,即访问两个或更多 DBMS 服务器的事务。

JDBC 驱动程序至少应包括一个基本的DataSource实现。例如,Java DB JDBC 驱动程序包括实现org.apache.derby.jdbc.ClientDataSource和 MySQL 的com.mysql.jdbc.jdbc2.optional.MysqlDataSource。如果您的 Client 端在 Java 8 Compact Profile 2 上运行,则 Java DB JDBC 驱动程序为org.apache.derby.jdbc.BasicClientDataSource40。本教程的 samples 要求压缩配置文件 3 或更高。

支持分布式事务的DataSource类通常还实现对连接池的支持。例如,EJB 供应商提供的DataSource类几乎总是支持连接池和分布式事务。

假设从前面的示例来看,The Coffee BreakStore 蓬勃 Developing 的连锁店的所有者已决定通过在互联网上出售咖啡来进一步扩大规模。预期会有大量在线业务,因此所有者肯定需要连接池。打开和关闭连接会涉及大量开销,并且所有者希望此在线 Order 系统需要大量的查询和更新。使用连接池,可以反复使用连接池,从而避免了为每次数据库访问创建新连接的开销。此外,所有者现在拥有第二个 DBMS,其中包含最近收购的咖啡烘焙公司的数据。这意味着所有者将希望能够编写使用旧 DBMS 服务器和新 DBMS 服务器的分布式事务。

链所有者已重新配置计算机系统,以服务于新的更大的 Clients。所有者购买了最新的 JDBC 驱动程序和与其一起使用的 EJB 应用程序服务器,以便能够使用分布式事务并获得连接池带来的更高性能。提供了许多与最近购买的 EJB 服务器兼容的 JDBC 驱动程序。所有者现在具有三层体系结构,中间层是新的 EJB 应用程序服务器和 JDBC 驱动程序,第三层是两个 DBMS 服务器。发出请求的 Client 端计算机是第一层。

部署基本数据源对象

系统 管理 员需要部署DataSource对象,以便 Coffee Break 的编程团队可以开始使用它们。部署DataSource对象包含三个任务:

  • 创建DataSource类的实例

  • 设置其属性

  • 在使用 Java 命名和目录interface(JNDI)API 的命名服务中注册它

首先,考虑最基本的情况,即使用DataSourceinterface的基本实现,即不支持连接池或分布式事务的interface。在这种情况下,只需要部署一个DataSource对象。 DataSource的基本实现产生与DriverManager类产生的连接类型相同的连接。

创建数据源类的实例并设置其属性

假设仅希望基本实现DataSource的公司从 JDBC 供应商 DB Access,Inc.购买了一个驱动程序。该驱动程序包括实现DataSourceinterface的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_ACCOUNTSBasicDataSource对象ds产生的任何连接都将是到数据库CUSTOMER_ACCOUNTS的连接。

向使用 JNDI API 的命名服务注册 DataSource 对象

通过设置属性,系统 管理 员可以向 JNDI(Java 命名和目录interface)命名服务注册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类实例的逻辑数据源名称,并且 JNDI 命名服务将返回该DataSource类的实例。然后可以在该DataSource对象上调用方法getConnection以获得与其表示的数据源的连接。例如,程序员可能编写以下两行代码来获得DataSource对象,该对象生成与数据库CUSTOMER_ACCOUNTS的连接。

Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/billingDB");

代码的第一行以初始上下文作为检索DataSource对象的起点。当为方法lookup提供逻辑名jdbc/billingDB时,该方法将返回系统 管理 员在部署时绑定到jdbc/billingDBDataSource对象。由于方法lookup的返回值是 Java Object,因此在将其分配给变量ds之前,必须将其强制转换为更具体的DataSource类型。

变量ds是实现DataSourceinterface的com.dbaccess.BasicDataSource类的实例。调用方法ds.getConnection会产生到CUSTOMER_ACCOUNTS数据库的连接。

Connection con = ds.getConnection("fernanda","brewed");

getConnection方法仅需要用户名和密码,因为变量ds在其属性中具有与CUSTOMER_ACCOUNTS数据库构建连接所需的其余信息,例如数据库名称和位置。

数据源对象的优点

由于其属性,与连接DriverManager类相比,DataSource对象是更好的替代方法。程序员不再需要在其应用程序中对驱动程序名称或 JDBC URL 进行硬编码,从而使它们更易于移植。同样,DataSource属性使维护代码更加简单。如果有更改,系统 管理 员可以更新数据源属性,而不必担心更改与该数据源构建连接的每个应用程序。例如,如果将数据源移至其他服务器,则系统 管理 员要做的就是将serverName属性设置为新的服务器名称。

除了可移植性和易于维护之外,使用DataSource对象获取连接还可以提供其他优点。当实现DataSourceinterface以与ConnectionPoolDataSource实现一起使用时,由该DataSource类的实例产生的所有连接将自动成为池连接。类似地,当实现DataSource实现以与XADataSource类一起使用时,它产生的所有连接将自动成为可在分布式事务中使用的连接。下一节说明如何部署DataSource实现的这些类型。

部署其他数据源实现

系统 管理 员或以该身份工作的其他人可以部署DataSource对象,以便它产生的连接为池连接。为此,他(她)首先部署一个ConnectionPoolDataSource对象,然后部署一个实现与之配合使用的DataSource对象。设置ConnectionPoolDataSource对象的属性,使其代表将与其构建连接的数据源。将ConnectionPoolDataSource对象注册到 JNDI 命名服务后,将部署DataSource对象。通常,必须为DataSource对象设置两个属性:descriptiondataSourceName。赋予dataSourceName属性的值是标识先前部署的ConnectionPoolDataSource对象的逻辑名称,该对象是包含构建连接所需的属性的对象。

部署了ConnectionPoolDataSourceDataSource对象之后,您可以在DataSource对象上调用方法DataSource.getConnection并获得池化连接。此连接将连接到ConnectionPoolDataSource对象的属性中指定的数据源。

以下示例描述了 The Coffee Break 的系统 管理 员如何部署实现为提供池化连接的DataSource对象。系统 管理 员通常将使用部署工具,因此本节中显示的代码片段是部署工具将执行的代码。

为了获得更好的性能,Coffee Break 公司从 DB Access,Inc.购买了 JDBC 驱动程序,该驱动程序包含实现com.dbaccess.ConnectionPoolDS的类com.dbaccess.ConnectionPoolDS。系统 管理 员创建创建此类的实例,设置其属性,并将其注册到 JNDI 命名服务。 Coffee Break 从其 EJB 服务器供应商 Application Logic,Inc.购买了DataSourcecom.applogic.PooledDataSourcecom.applogic.PooledDataSource类通过使用ConnectionPoolDataSourcecom.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变量交互的DataSource类以及com.dbaccess.ConnectionPoolDS类的其他实例。以下代码创建此类的实例并设置其属性。请注意,为此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的池化连接。

获取和使用池化连接

连接池是数据库连接对象的缓存。这些对象表示物理数据库连接,应用程序可以使用这些物理数据库连接来连接数据库。在运行时,应用程序请求池中的连接。如果池包含可以满足请求的连接,则它将连接返回给应用程序。如果未找到任何连接,则会创建一个新连接并将其返回给应用程序。应用程序使用该连接在数据库上执行一些工作,然后将对象返回到池中。然后,该连接可用于下一个连接请求。

连接池可促进连接对象的重用,并减少创建连接对象的次数。连接池显着提高了数据库密集型应用程序的性能,因为创建连接对象在时间和资源上都非常昂贵。

现在已经部署了这些DataSourceConnectionPoolDataSource对象,程序员可以使用DataSource对象获得池化连接。获取池化连接的代码与获取非池化连接的代码一样,如以下两行所示:

ctx = new InitialContext();
ds = (DataSource)ctx.lookup("jdbc/fastCoffeeDB");

变量ds表示DataSource对象,该对象产生到数据库COFFEEBREAK的池化连接。您只需检索一次该DataSource对象,因为您可以使用它产生所需的尽可能多的池连接。在ds变量上调用方法getConnection会自动产生池化连接,因为ds变量代表的DataSource对象已配置为产生池化连接。

连接池通常对程序员是透明的。使用池连接时,只需要做两件事:

  • 使用DataSource对象而不是DriverManager类来获得连接。在下面的代码行中,ds是已实现并部署的DataSource对象,因此它将创建池连接,而usernamepassword是代表有权访问数据库的用户凭据的变量:
Connection con = ds.getConnection(username, password);
  • 使用finally语句关闭池化连接。在适用于使用池连接的代码的try/catch块之后,将出现以下finally块:
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.sqljavax.ejbjavax.naming软件包。

DataSourceConnectionPoolDataSourceinterface位于javax.sql包中,而 JNDI 构造函数InitialContext和方法Context.lookupjavax.naming包的一部分。此特定示例代码采用使用javax.ejb包中的 API 的 EJB 组件的形式。本示例的 Object 是说明使用池化连接的方式与使用非池化连接的方式相同,因此您不必担心理解 EJB API。

  • 它使用DataSource对象获取连接,而不是使用DriverManager工具。

  • 它使用finally块来确保关闭连接。

获取和使用池化连接类似于获取和使用常规连接。当某人充当系统 管理 员正确部署了ConnectionPoolDataSource对象和DataSource对象时,应用程序将使用该DataSource对象获取池化连接。但是,应用程序应使用finally块关闭池化连接。为简单起见,前面的示例使用了finally块,但没有使用catch块。如果try块中的方法引发了异常,则默认情况下将引发该异常,无论如何都将执行finally子句。

部署分布式事务

可以部署DataSource个对象来获取可在分布式事务中使用的连接。与连接池一样,必须部署两个不同的类实例:一个XADataSource对象和一个实现为与之一起使用的DataSource对象。

假设 The Coffee Break 企业家购买的 EJB 服务器包括DataSourcecom.applogic.TransactionalDS,该类可与XADataSource类(例如com.dbaccess.XATransactionalDS)一起使用。它可以与任何XADataSource类一起使用的事实使 EJB 服务器可跨 JDBC 驱动程序移植。部署DataSourceXADataSource对象时,生成的连接将能够参与分布式事务。在这种情况下,实现了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对象。注意DataSourcecom.applogic.TransactionalDS可以与任何 JDBC 驱动程序供应商的XADataSource类一起使用。部署DataSource对象涉及创建com.applogic.TransactionalDS类的实例并设置其属性。 dataSourceName属性设置为jdbc/xa/distCoffeeDB,与com.dbaccess.XATransactionalDS关联的逻辑名称。这是实现DataSource类的分布式事务功能的XADataSource类。以下代码部署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.TransactionalDScom.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.commitConnection.rollback。同样,应用程序永远不要调用Connection.setAutoCommit(true),它启用了自动提交 Pattern,因为这也会干扰事务 管理 器对事务边界的控制。这就解释了为什么在分布式事务范围内创建的新连接默认情况下会禁用其自动提交 Pattern。请注意,这些限制仅在连接参与分布式事务时才适用。连接不是分布式事务的一部分时,没有任何限制。

对于以下示例,假设已发送一份咖啡,这将触发对位于不同 DBMS 服务器上的两个表的更新。第一个表是新的INVENTORY表,第二个表是COFFEES表。因为这些表位于不同的 DBMS 服务器上,所以涉及到它们的事务将是分布式事务。以下示例中的代码获得分布式连接的第二部分,该代码获取连接,更新COFFEES表并关闭连接。

请注意,由于分布式事务的范围由中间层服务器的基础系统基础结构控制,因此代码不会明确地提交或回退更新。同样,假设用于分布式事务的连接是池连接,则应用程序使用finally块来关闭该连接。这样可以保证即使抛出异常也将关闭有效的连接,从而确保将连接返回到连接池以进行回收。

下面的代码示例说明了一个 Enterprise Bean,它是一个实现可以由 Client 端计算机调用的方法的类。本示例的 Object 是演示分布式事务的应用程序代码与其他代码没有什么不同,只是它不调用Connection方法commitrollbacksetAutoCommit(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;
}