当前位置: 首页 > 工具软件 > Java Grinder > 使用案例 >

Java DataSource对象

袁单鹗
2023-12-01

 

连接数据源对象

本节介绍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.ClientDataSourceMySQL的实现和com.mysql.jdbc.jdbc2.optional.MysqlDataSource。如果您的客户端在Java 8 Compact Profile 2上运行,则Java DB JDBC驱动程序为org.apache.derby.jdbc.BasicClientDataSource40。本教程的示例要求压缩配置文件3或更高。

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

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

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

部署基本数据源对象

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

  1. 创建DataSource类的实例
  2. 设置其属性
  3. 在使用Java命名和目录接口(JNDI)API的命名服务中注册它

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

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

假设一家只希望基本实现的DataSource公司从JDBC供应商DB Access,Inc.购买了一个驱动程序。该驱动程序包括com.dbaccess.BasicDataSource实现该DataSource接口的类。以下代码摘录创建该类的实例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的命名服务注册数据源对象

通过设置属性,系统管理员可以向BasicDataSourceJNDI(Java命名和目录接口)命名服务注册对象。通常使用的特定命名服务由系统属性确定,此处未显示。以下代码摘录注册该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该类的实例。getConnection然后可以在该DataSource对象上调用该方法以获取与其表示的数据源的连接。例如,程序员可能编写以下两行代码来获取一个DataSource对象,该对象产生与数据库的连接CUSTOMER_ACCOUNTS

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

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

变量dscom.dbaccess.BasicDataSource实现DataSource接口的类的实例。调用该方法将ds.getConnection产生与CUSTOMER_ACCOUNTS数据库的连接。

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

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

数据源对象的优点

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

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

部署其他数据源实现

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

随着ConnectionPoolDataSourceDataSource对象部署,您可以调用该方法DataSource.getConnectionDataSource的对象,并得到一个连接池。此连接将连接到ConnectionPoolDataSource对象属性中指定的数据源。

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

为了获得更好的性能,Coffee Break公司从DB Access,Inc.购买了JDBC驱动程序com.dbaccess.ConnectionPoolDS,其中包括实现该ConnectionPoolDataSource接口的class 。系统管理员创建创建此类的实例,设置其属性,并将其注册到JNDI命名服务。Coffee Break从其EJB服务器供应商Application Logic,Inc.购买了其DataSourcecom.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变量关联的逻辑名具有在子上下文pool下添加的子上下文jdbc,这类似于将子目录添加到分层文件系统中的另一个子目录。该类的任何实例的逻辑名称com.dbaccess.ConnectionPoolDS始终以开头jdbc/pool。Oracle建议将所有ConnectionPoolDataSource对象放在子上下文下jdbc/pool

Context ctx = new InitialContext();
ctx.bind("jdbc/pool/fastCoffeeDB", cpds);

接下来,部署DataSource实现为与cpds变量和com.dbaccess.ConnectionPoolDS该类的其他实例交互的类。以下代码创建此类的实例并设置其属性。请注意,仅为此实例设置了两个属性com.applogic.PooledDataSourcedescription设置该属性是因为它始终是必需的。设置的另一个属性dataSourceName给出了的逻辑JNDI名称cpds,它是com.dbaccess.ConnectionPoolDS该类的实例。换句话说,cpds表示ConnectionPoolDataSource将为对象实现连接池的DataSource对象。

以下代码(可能由部署工具执行)创建一个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一次该对象,因为您可以使用它来产生所需的任意多个池连接。getConnectionds变量上调用方法会自动产生一个池化连接,因为DataSourceds变量代表的对象已配置为产生池化连接。

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

  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对象的逻辑名称。

请注意,尽管此代码与您之前看到的代码非常相似,但在以下方面有所不同:

  • 它进口javax.sqljavax.ejbjavax.naming包除java.sql

    DataSourceConnectionPoolDataSource接口处于javax.sql封装,JNDI构造InitialContext和方法Context.lookup是的一部分javax.naming封装。此特定示例代码采用使用javax.ejb包中API的EJB组件的形式。该示例的目的是说明使用池化连接的方式与使用非池化连接的方式相同,因此您不必担心理解EJB API。

  • 它使用DataSource对象来获得连接,而不是使用DriverManager设施。

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

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

部署分布式事务

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

假设The Coffee Break企业家购买的EJB服务器包含DataSourcecom.applogic.TransactionalDSXADataSource该类与诸如com.dbaccess.XATransactionalDS。它可以与任何XADataSource类一起使用的事实使EJB服务器可以跨JDBC驱动程序移植。当DataSourceXADataSource物体的部署,产生的连接将能够参与分布式事务。在这种情况下,将com.applogic.TransactionalDS实现该类,以使生成的连接也成为池连接,对于DataSource作为EJB服务器实现的一部分提供的类,通常是这种情况。

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了子上下文。Oracle建议类的任何实例的逻辑名称始终以开头。xajdbccom.dbaccess.XATransactionalDSjdbc/xa

Context ctx = new InitialContext();
ctx.bind("jdbc/xa/distCoffeeDB", xads);

接下来,部署DataSource实现xads与其他XADataSource对象交互的对象。请注意,DataSourcecom.applogic.TransactionalDS可以与XADataSource任何JDBC驱动程序供应商的类一起使用。部署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已经部署,应用程序可以调用该方法getConnection的实例TransactionalDS类来获取到的连接COFFEEBREAK可在分布式事务中使用的数据库。

使用连接进行分布式事务

要获得可用于分布式事务的连接,必须使用DataSource已正确实现和部署的对象,如“部署分布式事务”部分中所示。使用这样的DataSource对象,对其调用方法getConnection。建立连接后,请像使用其他任何连接一样使用它。由于jdbc/distCoffeesDB已与XADataSourceJNDI命名服务中的对象相关联,因此以下代码生成了Connection可在分布式事务中使用的对象:

Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("jdbc/distCoffeesDB");
Connection con = ds.getConnection();

对于此连接作为分布式事务的一部分时的使用方式,存在一些较小但重要的限制。事务管理器控制分布式事务何时开始以及何时提交或回滚。因此,应用程序代码绝不应调用方法Connection.commitConnection.rollback。应用程序同样不应调用Connection.setAutoCommit(true),它启用了自动提交模式,因为这也会干扰事务管理器对事务边界的控制。这说明了为什么在分布式事务范围内创建的新连接默认情况下会禁用其自动提交模式。请注意,这些限制仅在连接参与分布式事务时才适用。连接不是分布式事务的一部分时,没有任何限制。

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

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

下面的代码示例说明了一个Enterprise Bean,它是一个实现可以由客户端计算机调用的方法的类。这个例子的目的是说明用于分布式事务应用程序代码是没有从其他代码不同,除了它不调用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;
}
 类似资料: