连接数据源对象
本节介绍DataSource
对象,这是获得与数据源的连接的首选方法。除了它们的其他优点(稍后将进行解释)之外,DataSource
对象还可以提供连接池和分布式事务。此功能对于企业数据库计算至关重要。特别是,它是 Enterprise JavaBeans(EJB)技术不可或缺的。
本节说明如何使用DataSource
interface获得连接以及如何使用分布式事务和连接池。两者都涉及 JDBC 应用程序中很少的代码更改。
部署使这些操作成为可能的类所执行的工作(系统 管理 员通常使用工具(例如 Apache Tomcat 或 Oracle WebLogic Server)执行的工作)因要部署的DataSource
对象的类型而异。因此,本节的大部分内容专门介绍系统 管理 员如何设置环境,以便程序员可以使用DataSource
对象获取连接。
涵盖以下主题:
使用数据源对象获取连接
在构建连接中,您学习了如何使用DriverManager
类构建连接。本节说明如何使用DataSource
对象获得与数据源的连接,这是首选方法。
由实现DataSource
的类实例化的对象表示特定的 DBMS 或某些其他数据源,例如文件。 DataSource
对象代表特定的 DBMS 或某些其他数据源,例如文件。如果公司使用多个数据源,它将为每个数据源部署一个单独的DataSource
对象。 DataSource
interface由驱动程序供应商实现。它可以通过三种不同的方式实现:
-
基本的
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 的命名服务中注册它
首先,考虑最基本的情况,即使用DataSource
interface的基本实现,即不支持连接池或分布式事务的interface。在这种情况下,只需要部署一个DataSource
对象。 DataSource
的基本实现产生与DriverManager
类产生的连接类型相同的连接。
创建数据源类的实例并设置其属性
假设仅希望基本实现DataSource
的公司从 JDBC 供应商 DB Access,Inc.购买了一个驱动程序。该驱动程序包括实现DataSource
interface的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 命名和目录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/billingDB
的DataSource
对象。由于方法lookup
的返回值是 Java Object
,因此在将其分配给变量ds
之前,必须将其强制转换为更具体的DataSource
类型。
变量ds
是实现DataSource
interface的com.dbaccess.BasicDataSource
类的实例。调用方法ds.getConnection
会产生到CUSTOMER_ACCOUNTS
数据库的连接。
Connection con = ds.getConnection("fernanda","brewed");
getConnection
方法仅需要用户名和密码,因为变量ds
在其属性中具有与CUSTOMER_ACCOUNTS
数据库构建连接所需的其余信息,例如数据库名称和位置。
数据源对象的优点
由于其属性,与连接DriverManager
类相比,DataSource
对象是更好的替代方法。程序员不再需要在其应用程序中对驱动程序名称或 JDBC URL 进行硬编码,从而使它们更易于移植。同样,DataSource
属性使维护代码更加简单。如果有更改,系统 管理 员可以更新数据源属性,而不必担心更改与该数据源构建连接的每个应用程序。例如,如果将数据源移至其他服务器,则系统 管理 员要做的就是将serverName
属性设置为新的服务器名称。
除了可移植性和易于维护之外,使用DataSource
对象获取连接还可以提供其他优点。当实现DataSource
interface以与ConnectionPoolDataSource
实现一起使用时,由该DataSource
类的实例产生的所有连接将自动成为池连接。类似地,当实现DataSource
实现以与XADataSource
类一起使用时,它产生的所有连接将自动成为可在分布式事务中使用的连接。下一节说明如何部署DataSource
实现的这些类型。
部署其他数据源实现
系统 管理 员或以该身份工作的其他人可以部署DataSource
对象,以便它产生的连接为池连接。为此,他(她)首先部署一个ConnectionPoolDataSource
对象,然后部署一个实现与之配合使用的DataSource
对象。设置ConnectionPoolDataSource
对象的属性,使其代表将与其构建连接的数据源。将ConnectionPoolDataSource
对象注册到 JNDI 命名服务后,将部署DataSource
对象。通常,必须为DataSource
对象设置两个属性:description
和dataSourceName
。赋予dataSourceName
属性的值是标识先前部署的ConnectionPoolDataSource
对象的逻辑名称,该对象是包含构建连接所需的属性的对象。
部署了ConnectionPoolDataSource
和DataSource
对象之后,您可以在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.购买了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
变量交互的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
的池化连接。
获取和使用池化连接
连接池是数据库连接对象的缓存。这些对象表示物理数据库连接,应用程序可以使用这些物理数据库连接来连接数据库。在运行时,应用程序请求池中的连接。如果池包含可以满足请求的连接,则它将连接返回给应用程序。如果未找到任何连接,则会创建一个新连接并将其返回给应用程序。应用程序使用该连接在数据库上执行一些工作,然后将对象返回到池中。然后,该连接可用于下一个连接请求。
连接池可促进连接对象的重用,并减少创建连接对象的次数。连接池显着提高了数据库密集型应用程序的性能,因为创建连接对象在时间和资源上都非常昂贵。
现在已经部署了这些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
语句关闭池化连接。在适用于使用池连接的代码的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.sql
,javax.ejb
和javax.naming
软件包。
DataSource
和ConnectionPoolDataSource
interface位于javax.sql
包中,而 JNDI 构造函数InitialContext
和方法Context.lookup
是javax.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 服务器包括DataSource
类com.applogic.TransactionalDS
,该类可与XADataSource
类(例如com.dbaccess.XATransactionalDS
)一起使用。它可以与任何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
关联的逻辑名称。这是实现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.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)
,它启用了自动提交 Pattern,因为这也会干扰事务 管理 器对事务边界的控制。这就解释了为什么在分布式事务范围内创建的新连接默认情况下会禁用其自动提交 Pattern。请注意,这些限制仅在连接参与分布式事务时才适用。连接不是分布式事务的一部分时,没有任何限制。
对于以下示例,假设已发送一份咖啡,这将触发对位于不同 DBMS 服务器上的两个表的更新。第一个表是新的INVENTORY
表,第二个表是COFFEES
表。因为这些表位于不同的 DBMS 服务器上,所以涉及到它们的事务将是分布式事务。以下示例中的代码获得分布式连接的第二部分,该代码获取连接,更新COFFEES
表并关闭连接。
请注意,由于分布式事务的范围由中间层服务器的基础系统基础结构控制,因此代码不会明确地提交或回退更新。同样,假设用于分布式事务的连接是池连接,则应用程序使用finally
块来关闭该连接。这样可以保证即使抛出异常也将关闭有效的连接,从而确保将连接返回到连接池以进行回收。
下面的代码示例说明了一个 Enterprise Bean,它是一个实现可以由 Client 端计算机调用的方法的类。本示例的 Object 是演示分布式事务的应用程序代码与其他代码没有什么不同,只是它不调用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;
}