连接 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对象包含三个任务:

  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_。然后系统管理员设置三个属性。请注意,以下代码通常由部署工具执行:

  1. com.dbaccess.BasicDataSource ds = new com.dbaccess.BasicDataSource();
  2. ds.setServerName("grinder");
  3. ds.setDatabaseName("CUSTOMER_ACCOUNTS");
  4. ds.setDescription("Customer accounts database for billing");

变量_ds_现在代表服务器上安装的数据库CUSTOMER_ACCOUNTSBasicDataSource对象_ds_产生的任何连接都将连接到数据库CUSTOMER_ACCOUNTS

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

设置属性后,系统管理员可以使用 JNDI(Java 命名和目录接口)命名服务注册BasicDataSource对象。使用的特定命名服务通常由系统属性确定,此处未显示。以下代码摘录注册BasicDataSource对象并将其与逻辑名jdbc/billingDB绑定:

  1. Context ctx = new InitialContext();
  2. 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的连接。

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

第一行代码获取初始上下文作为检索DataSource对象的起点。当您向方法lookup提供逻辑名称jdbc/billingDB时,该方法返回系统管理员在部署时绑定到jdbc/billingDBDataSource对象。因为方法lookup的返回值是 Java Object,所以我们必须将其转换为更具体的DataSource类型,然后再将其赋值给变量_ds_

变量_ds_是实现DataSource接口的类com.dbaccess.BasicDataSource的实例。调用方法_ds_ .getConnection产生与CUSTOMER_ACCOUNTS数据库的连接。

  1. 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对象设置两个属性:descriptiondataSourceName。赋予dataSourceName属性的值是标识先前部署的ConnectionPoolDataSource对象的逻辑名称,该对象包含进行连接所需的属性。

部署ConnectionPoolDataSourceDataSource对象后,可以调用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。购买了DataSourcecom.applogic.PooledDataSource。类com.applogic.PooledDataSource通过使用ConnectionPoolDataSourcecom.dbaccess.ConnectionPoolDS提供的底层支持来实现连接池。

必须首先部署ConnectionPoolDataSource对象。以下代码创建com.dbaccess.ConnectionPoolDS的实例并设置其属性:

  1. com.dbaccess.ConnectionPoolDS cpds = new com.dbaccess.ConnectionPoolDS();
  2. cpds.setServerName("creamer");
  3. cpds.setDatabaseName("COFFEEBREAK");
  4. cpds.setPortNumber(9040);
  5. 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下:

  1. Context ctx = new InitialContext();
  2. 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

  1. com.applogic.PooledDataSource ds = new com.applogic.PooledDataSource();
  2. ds.setDescription("produces pooled connections to COFFEEBREAK");
  3. ds.setDataSourceName("jdbc/pool/fastCoffeeDB");
  4. Context ctx = new InitialContext();
  5. ctx.bind("jdbc/fastCoffeeDB", ds);

此时,部署了DataSource对象,应用程序可以从该对象获得与数据库COFFEEBREAK的池连接。

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

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

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

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

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

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

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

    1. Connection con = ds.getConnection(username, password);
  2. 使用finally语句关闭池连接。以下finally块将出现在适用于使用池化连接的代码的try/catch块之后:

    1. try {
    2. Connection con = ds.getConnection(username, password);
    3. // ... code to use the pooled
    4. // connection con
    5. } catch (Exception ex {
    6. // ... code to handle exceptions
    7. } finally {
    8. if (con != null) con.close();
    9. }

否则,使用池化连接的应用程序与使用常规连接的应用程序相同。应用程序员在完成连接池时可能会注意到的另一件事是性能更好。

以下示例代码获取DataSource对象,该对象生成与数据库COFFEEBREAK的连接,并使用它来更新表COFFEES中的价格:

  1. import java.sql.*;
  2. import javax.sql.*;
  3. import javax.ejb.*;
  4. import javax.naming.*;
  5. public class ConnectionPoolingBean implements SessionBean {
  6. // ...
  7. public void ejbCreate() throws CreateException {
  8. ctx = new InitialContext();
  9. ds = (DataSource)ctx.lookup("jdbc/fastCoffeeDB");
  10. }
  11. public void updatePrice(float price, String cofName,
  12. String username, String password)
  13. throws SQLException{
  14. Connection con;
  15. PreparedStatement pstmt;
  16. try {
  17. con = ds.getConnection(username, password);
  18. con.setAutoCommit(false);
  19. pstmt = con.prepareStatement("UPDATE COFFEES " +
  20. "SET PRICE = ? " +
  21. "WHERE COF_NAME = ?");
  22. pstmt.setFloat(1, price);
  23. pstmt.setString(2, cofName);
  24. pstmt.executeUpdate();
  25. con.commit();
  26. pstmt.close();
  27. } finally {
  28. if (con != null) con.close();
  29. }
  30. }
  31. private DataSource ds = null;
  32. private Context ctx = null;
  33. }

此代码示例中的连接参与连接池,因为以下情况属实:

  • 已部署实现ConnectionPoolDataSource的类的实例。
  • 已部署实现DataSource的类的实例,并且为其dataSourceName属性设置的值是绑定到先前部署的ConnectionPoolDataSource对象的逻辑名称。

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

  • java.sql外,它还导入javax.sqljavax.ejbjavax.naming包。

    DataSourceConnectionPoolDataSource接口位于javax.sql包中,JNDI 构造器InitialContext和方法Context.lookupjavax.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 服务器包含DataSourcecom.applogic.TransactionalDS,它与XADataSourceXADataSource类一起使用。它适用于任何XADataSource类的事实使 EJB 服务器可以跨 JDBC 驱动程序移植。部署DataSourceXADataSource对象时,生成的连接将能够参与分布式事务。在这种情况下,实现类com.applogic.TransactionalDS,以便生成的连接也是池连接,这通常是作为 EJB 服务器实现的一部分提供的DataSource类的情况。

必须首先部署XADataSource对象。以下代码创建com.dbaccess.XATransactionalDS的实例并设置其属性:

  1. com.dbaccess.XATransactionalDS xads = new com.dbaccess.XATransactionalDS();
  2. xads.setServerName("creamer");
  3. xads.setDatabaseName("COFFEEBREAK");
  4. xads.setPortNumber(9040);
  5. xads.setDescription("Distributed transactions for COFFEEBREAK DBMS");

以下代码使用 JNDI 命名服务注册com.dbaccess.XATransactionalDS对象_xads_。请注意,与_xads_相关联的逻辑名称在jdbc下添加了子上下文xa。 Oracle 建议类com.dbaccess.XATransactionalDS的任何实例的逻辑名始终以jdbc/xa开头。

  1. Context ctx = new InitialContext();
  2. 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关联的逻辑名称。这是XADataSource类,它实现DataSource类的分布式事务功能。以下代码部署DataSource类的实例:

  1. com.applogic.TransactionalDS ds = new com.applogic.TransactionalDS();
  2. ds.setDescription("Produces distributed transaction " +
  3. "connections to COFFEEBREAK");
  4. ds.setDataSourceName("jdbc/xa/distCoffeeDB");
  5. Context ctx = new InitialContext();
  6. ctx.bind("jdbc/distCoffeeDB", ds);

现在已经部署了类com.applogic.TransactionalDScom.dbaccess.XATransactionalDS的实例,应用程序可以在TransactionalDS类的实例上调用方法getConnection以获得可以在分布式中使用的COFFEEBREAK数据库的连接交易。

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

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

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

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

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

以下代码示例演示了企业 Bean,它是一个实现客户端计算机可以调用的方法的类。此示例的目的是演示分布式事务的应用程序代码与其他代码没有区别,只是它不调用Connection方法commitrollbacksetAutoCommit(true)。因此,您无需担心了解所使用的 EJB API。

  1. import java.sql.*;
  2. import javax.sql.*;
  3. import javax.ejb.*;
  4. import javax.naming.*;
  5. public class DistributedTransactionBean implements SessionBean {
  6. // ...
  7. public void ejbCreate() throws CreateException {
  8. ctx = new InitialContext();
  9. ds = (DataSource)ctx.lookup("jdbc/distCoffeesDB");
  10. }
  11. public void updateTotal(int incr, String cofName, String username,
  12. String password)
  13. throws SQLException {
  14. Connection con;
  15. PreparedStatement pstmt;
  16. try {
  17. con = ds.getConnection(username, password);
  18. pstmt = con.prepareStatement("UPDATE COFFEES " +
  19. "SET TOTAL = TOTAL + ? " +
  20. "WHERE COF_NAME = ?");
  21. pstmt.setInt(1, incr);
  22. pstmt.setString(2, cofName);
  23. pstmt.executeUpdate();
  24. stmt.close();
  25. } finally {
  26. if (con != null) con.close();
  27. }
  28. }
  29. private DataSource ds = null;
  30. private Context ctx = null;
  31. }