本节介绍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
类是支持分布式事务通常也实现了连接池支持。例如,DataSource
EJB供应商提供的类几乎总是支持连接池和分布式事务。
假设从前面的示例来看,The Coffee Break商店蓬勃发展的连锁店的所有者已决定通过在Internet上出售咖啡来进一步扩大规模。预期会有大量的在线业务,因此所有者肯定需要连接池。打开和关闭连接会涉及大量开销,并且所有者希望此在线订购系统需要大量的查询和更新。使用连接池,可以反复使用连接池,从而避免了为每次数据库访问创建新连接的开销。此外,所有者现在拥有第二个DBMS,其中包含最近收购的咖啡烘焙公司的数据。这意味着所有者将希望能够编写使用旧DBMS服务器和新DBMS服务器的分布式事务。
链所有者已重新配置计算机系统,以服务于新的更大的客户群。所有者购买了最新的JDBC驱动程序和与其一起使用的EJB应用程序服务器,以便能够使用分布式事务并获得连接池带来的更高性能。提供了许多与最近购买的EJB服务器兼容的JDBC驱动程序。所有者现在具有三层体系结构,中间层是新的EJB应用程序服务器和JDBC驱动程序,第三层是两个DBMS服务器。发出请求的客户端计算机是第一层。
系统管理员需要部署DataSource
对象,以便Coffee Break的编程团队可以开始使用它们。部署DataSource
对象包括三个任务:
DataSource
类的实例首先,考虑最基本的情况,即使用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
。
通过设置属性,系统管理员可以向BasicDataSource
JNDI(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
。
变量ds
是com.dbaccess.BasicDataSource
实现DataSource
接口的类的实例。调用该方法将ds.getConnection
产生与CUSTOMER_ACCOUNTS
数据库的连接。
Connection con = ds.getConnection("fernanda","brewed");
该getConnection
方法仅需要用户名和密码,因为该变量在其属性中ds
具有与CUSTOMER_ACCOUNTS
数据库建立连接所需的其余信息,例如数据库名称和位置。
由于其属性,DataSource
与DriverManager
用于连接的类相比,对象是更好的替代方法。程序员不再需要在其应用程序中对驱动程序名称或JDBC URL进行硬编码,从而使它们更易于移植。而且,DataSource
属性使代码维护更加简单。如果进行了更改,则系统管理员可以更新数据源属性,而不必担心更改与该数据源建立连接的每个应用程序。例如,如果将数据源移动到其他服务器,则系统管理员要做的就是将serverName
属性设置为新的服务器名称。
除了可移植性和易于维护之外,使用DataSource
对象进行连接还可以提供其他优点。当实现DataSource
接口以与实现一起使用ConnectionPoolDataSource
时,DataSource
该类实例产生的所有连接将自动为池连接。类似地,当DataSource
实现被实现为与XADataSource
类一起使用时,它产生的所有连接将自动为可在分布式事务中使用的连接。下一节将说明如何部署这些类型的DataSource
实现。
系统管理员或以该身份工作的其他人可以部署DataSource
对象,以便它产生的连接为池连接。为此,他(她)首先部署一个ConnectionPoolDataSource
对象,然后部署一个DataSource
实现为可以使用的对象。ConnectionPoolDataSource
设置对象的属性,使其代表将与其建立连接的数据源。将ConnectionPoolDataSource
对象注册到JNDI命名服务后,将DataSource
部署对象。通常,仅需为该DataSource
对象设置两个属性:description
和dataSourceName
。赋予该dataSourceName
属性的值是标识ConnectionPoolDataSource
先前部署的对象的逻辑名,该逻辑名是包含进行连接所需的属性的对象。
随着ConnectionPoolDataSource
与DataSource
对象部署,您可以调用该方法DataSource.getConnection
上DataSource
的对象,并得到一个连接池。此连接将连接到ConnectionPoolDataSource
对象属性中指定的数据源。
以下示例描述了The Coffee Break的系统管理员如何部署DataSource
实现为提供池化连接的对象。系统管理员通常会使用部署工具,因此本节中显示的代码片段是部署工具将执行的代码。
为了获得更好的性能,Coffee Break公司从DB Access,Inc.购买了JDBC驱动程序com.dbaccess.ConnectionPoolDS
,其中包括实现该ConnectionPoolDataSource
接口的class 。系统管理员创建创建此类的实例,设置其属性,并将其注册到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
变量关联的逻辑名具有在子上下文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.PooledDataSource
。description
设置该属性是因为它始终是必需的。设置的另一个属性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
。
一个连接池是数据库连接对象的缓存。这些对象表示物理数据库连接,应用程序可以使用这些物理数据库连接来连接数据库。在运行时,应用程序请求池中的连接。如果池包含可以满足请求的连接,则它将连接返回给应用程序。如果未找到任何连接,则会创建一个新的连接并返回到应用程序。应用程序使用该连接在数据库上执行某些工作,然后将对象返回到池中。然后,该连接可用于下一个连接请求。
连接池可促进连接对象的重用,并减少创建连接对象的次数。连接池显着提高了数据库密集型应用程序的性能,因为创建连接对象在时间和资源上都非常昂贵。
现在这些DataSource
和ConnectionPoolDataSource
对象的部署,程序员可以使用DataSource
对象来获取连接池。获取池化连接的代码与获取非池化连接的代码一样,如以下两行所示:
ctx = new InitialContext(); ds = (DataSource)ctx.lookup("jdbc/fastCoffeeDB");
该变量ds
表示一个DataSource
对象,该对象产生与数据库的池化连接COFFEEBREAK
。您只需要检索DataSource
一次该对象,因为您可以使用它来产生所需的任意多个池连接。getConnection
在ds
变量上调用方法会自动产生一个池化连接,因为DataSource
该ds
变量代表的对象已配置为产生池化连接。
连接池通常对程序员是透明的。使用池连接时,只需要做两件事:
使用DataSource
对象而不是DriverManager
类来获得连接。在下面的代码行中,ds
是一个DataSource
对象的实现和部署,以便它将创建池连接,username
并且password
是代表有权访问数据库的用户凭据的变量:
Connection con = ds.getConnection(username, password);
使用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.sql
,javax.ejb
和javax.naming
包除java.sql
。
的DataSource
和ConnectionPoolDataSource
接口处于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服务器包含DataSource
类com.applogic.TransactionalDS
,XADataSource
该类与诸如com.dbaccess.XATransactionalDS
。它可以与任何XADataSource
类一起使用的事实使EJB服务器可以跨JDBC驱动程序移植。当DataSource
与XADataSource
物体的部署,产生的连接将能够参与分布式事务。在这种情况下,将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建议类的任何实例的逻辑名称始终以开头。xa
jdbc
com.dbaccess.XATransactionalDS
jdbc/xa
Context ctx = new InitialContext(); ctx.bind("jdbc/xa/distCoffeeDB", xads);
接下来,部署DataSource
实现xads
与其他XADataSource
对象交互的对象。请注意,DataSource
类com.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
已与XADataSource
JNDI命名服务中的对象相关联,因此以下代码生成了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
块来关闭该连接。这样可以保证即使抛出异常也将关闭有效的连接,从而确保将连接返回到连接池以进行回收。
下面的代码示例说明了一个Enterprise 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; }