Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 16 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
16
Dung lượng
45,3 KB
Nội dung
An object-oriented programming language derives its strength from two areas First, you have the constructs of the programming language itself that allow you to write well-structured objects to extend that language Second, you have the extensive libraries of APIs that have been written to provide standard functionality Think for a moment about how APIs are created A software engineer does not just wake up one morning and have an entire API worked out in every detail Instead, an API's design is based on the experiences of professionals like you, who, over time, have gained insight through problem solving as to what is needed in an API to make it a useful part of developing an application Accordingly, over time, an API evolves through this community process to better fit the needs of the programming community When it comes to the JDBC API, specifically the DriverManager facility, there is an evolution taking place In Chapter 4, we needed to put a significant amount of code around DriverManager to implement a sharable connection facility It took even more work to make our sharable connections cacheable With the Java Enterprise Edition (J2EE), a framework has been defined for sharing and caching connections This framework is the JDBC 2.0 Extension API In this chapter, we'll cover the JDBC 2.0 Extension API, which is a another set of JDBC interfaces, along with Oracle's implementation of these interfaces We'll also look at a functional caching object using Oracle's connection caching implementation Let's begin our journey through the new API with a look at the generic source for database connections, the DataSource class 7.1 DataSources A DataSource object is a factory for database connections Oracle's implementations of data sources are database connection objects that encapsulate the registration of the appropriate database driver and the creation of a connection using predetermined parameters DataSource objects are typically bound with the Java Naming and Directory Interface (JNDI), so they can be allocated using a logical name at a centrally managed facility such as an LDAP directory 7.1.1 OracleDataSources Oracle implements the DataSource interface with class OracleDataSource Table 7-1 lists the standard properties implemented by a DataSource object Table 7-1 Standard DataSource properties Property Data type Description databaseName String Oracle SID dataSourceName String Name of the underlying DataSource class description String Description of the DataSource networkProtocol String For OCI driver, determines the protocol used password String Oracle password portNumber int Oracle listener port number serverName String DNS alias or TCP/IP address of the host user String Oracle username DataSource properties follow the JavaBeans design pattern, and therefore, the following getter/setter methods are in a DataSource object: public public public public public public public public public public public public public public public synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized void String void String void String void String void void int void String void String setDatabaseName(String databseName ) getDatabaseName( ) setDataSourceName(String dataSourceName) getDataSourceName( ) setDescription(String description) getDescription( ) setNetworkProtocol(String networkProtocol) getNetworkProtocol( ) setPassword(String password) setPortNumber(int portNumber) getPortNumber( ) setServerName(String ServerName) getServerName( ) setUser(String user) getUser( ) The OracleDataSource class has an additional set of proprietary attributes These are listed in Table 7-2 Table 7-2 OracleDataSource properties Property Data type Description kprb for server-side internal connections driverType String oci8 for client-side OCI driver thin for client- or server-side Thin driver url String tnsEntryName String A convenience property incorporating properties, such as PortNumber, user, and password, that make up a database URL TNS names address for use with the OCI driver And these are the OracleDataSource property getter/setter methods: public public public public public public synchronized synchronized synchronized synchronized synchronized synchronized void String void String void String setDriverType(String dt) getDriverType( ) setURL(String url) getURL( ) setTNSEntryName(String tns) getTNSEntryName( ) Common sense prevails with these settings For example, there is no getPassword( ) method, because that would create a security problem In addition, the properties have a specific precedence If you specify a url property, then any properties specified in the url override those that you specify by any of the other setter methods If you not set the url property but instead specify the tnsEntryName property, then any related setter methods are overridden by the values in the TNS entry name's definition Likewise, if you are using the OCI driver and specify a network protocol of IPC, then any communication properties are ignored because the IPC protocol establishes a direct connection to the database Finally, a username and password passed in the getConnection( ) method override those specified in any other way Note that you must always specify a username and password with whatever means you choose 7.1.2 Getting a Connection from a DataSource To get a connection from a DataSource use one of the two available getConnection( ) methods: public Connection getConnection( ) throws SQLException public Connection getConnection(String username, String password) throws SQLException The first method creates a new Connection object with the username and password settings from the DataSource The second method overrides the username and password in the DataSource Now that you have an understanding of data sources, let's look at Example 7-1, which is an application to test the Thin driver using a DataSource Example 7-1 An application using a DataSource to connect import java.sql.*; import oracle.jdbc.pool.*; class TestThinDSApp { public static void main (String args[]) throws ClassNotFoundException, SQLException { // These settings are typically configured in JNDI, // so they are implementation-specific OracleDataSource ds = new OracleDataSource ( ); ds.setDriverType("thin"); ds.setServerName("dssw2k01"); ds.setPortNumber(1521); ds.setDatabaseName("orcl"); // sid ds.setUser("scott"); ds.setPassword("tiger"); Connection conn = ds.getConnection( ); Statement stmt = conn.createStatement( ); ResultSet rset = stmt.executeQuery( "select 'Hello Thin driver data source tester '||" + "initcap(USER)||'!' result from dual"); if (rset.next( )) System.out.println(rset.getString(1)); rset.close( ); stmt.close( ); conn.close( ); } } First, our test application, TestThinDSApp, creates a new OracleDataSource object and then initializes its properties that are relevant to the Thin driver The OracleDataSource object implements the DataSource interface, so OracleDataSource is also considered to be a DataSource object Next, the program gets a connection from the DataSource using the getConnection( ) method Finally, just to prove everything is working OK, the application queries the database, and closes the connection So what have we accomplished using an OracleDataSource object? Recall that in Chapter we established a connection using the following code: Class.forName("oracle.jdbc.driver.OracleDriver"); Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@dssw2k01:1521:orcl","scott","tiger"); Now, using an OracleDataSource object, our code to establish a connection looks like: OracleDataSource ds = new OracleDataSource( ds.setDriverType("thin"); ds.setServerName("dssw2k01"); ds.setPortNumber(1521); ds.setDatabaseName("orcl"); // sid ds.setUser("scott"); ds.setPassword("tiger"); Connection conn = ds.getConnection( ); ); What's going on here? Our code is actually longer, which doesn't seem to improve things much, does it? But from another perspective, using a DataSource does represent an improvement, because a DataSource implements the Serializable interface, which means it can be bound using JNDI to a directory service What does that mean? It means we can define our connection parameters once, in one place, and use a logical name to get our connection from JNDI How does this help us? Let's say, for example, that we have 1,000 programs that use the specific connection parameters shown in Example 7-1 Let's further assume that we now have to move our database to another host If you wrote your programs using the DriverManager facility, you'll need to modify and compile all 1,000 programs However, if you used a DataSource bound to a directory using JNDI, then you need to change only one entry in the directory, and all the programs will use the new information 7.1.3 Using a JNDI DataSource Let's take a look at a couple of sample applications that illustrate the power and utility of using data sources that are accessed via JNDI The examples use Sun's file-based JNDI implementation You can download the class files for Sun's JNDI filesystem implementation at http://java.sun.com/products/jndi/index.html First, the program in Example 7-2, TestDSBind, creates a logical entry in a JNDI directory to store our DataSource It uses Sun's JNDI filesystem implementation as the directory After that, we'll look at another program that uses the DataSource created by the first My DataSource bind program, TestDSBind, starts by creating a Context variable named ctx Next, it creates a Properties object to use in initializing an initial context In layman's terms, that means it creates a reference to the point in the local filesystem where our program should store its bindings The program proceeds by creating an initial Context and storing its reference in ctx Next, it creates an OracleDataSource and initializes its properties Why an OracleDataSource and not a DataSource? You can't really use a DataSource for binding; you have to use an OracleDataSource, because the setter/getter methods for the properties are implementation- or vendor-specific and are not part of the DataSource interface Last, the program binds our OracleDataSource with the name joe by calling the Context.bind( ) method Example 7-2 An application that binds a JNDI DataSource import java.sql.*; import java.util.*; import javax.naming.*; import oracle.jdbc.pool.*; public class TestDSBind { public static void main (String args []) throws SQLException, NamingException { // For this to work you need to create the // directory /JNDI/JDBC on your filesystem first Context ctx = null; try { Properties prop = new Properties( ); prop.setProperty( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); prop.setProperty( Context.PROVIDER_URL, "file:/JNDI/JDBC"); ctx = new InitialContext(prop); } catch (NamingException ne) { System.err.println(ne.getMessage( )); } OracleDataSource ds = new OracleDataSource( ds.setDriverType("thin"); ds.setServerName("dssw2k01"); ds.setPortNumber(1521); ds.setDatabaseName("orcl"); ds.setUser("scott"); ds.setPassword("tiger"); ); ctx.bind("joe", ds); } } Create a directory, JNDI, on your hard drive, and then create a subdirectory, JDBC, in your JNDI directory Compile Example 7-2 and execute it Assuming you get no error messages, you should find a bindings file in your new JDBC subdirectory This file holds the values for a serialized form of your DataSource logically named joe This means that we can later retrieve a new connection by referencing a resource named joe Now that we have a directory entry, let's test it with our next program, TestDSLookup, in Example 7-3 First, TestDSLookUp creates an initial context just like TestDSBind did Next, it uses the Context.lookup( ) method to look up and instantiate a new DataSource from our serialized version of joe Finally, the program queries the database and closes the connection Pretty cool huh? When using DriverManager, you typically must specify the JDBC driver and database URL in your source code By using a DataSource together with JNDI, you can write code that is independent of a JDBC driver and of a database URL Example 7-3 An application that uses a JNDI DataSource import java.sql.*; import javax.sql.*; import javax.naming.*; import java.util.*; public class TestDSLookUp { public static void main (String[] args) throws SQLException, NamingException { Context ctx = null; try { Properties prop = new Properties( ); prop.setProperty( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); prop.setProperty( Context.PROVIDER_URL, "file:/JNDI/JDBC"); ctx = new InitialContext(prop); } catch (NamingException ne) { System.err.println(ne.getMessage( )); } DataSource ds = (DataSource)ctx.lookup("joe"); Connection conn = ds.getConnection( ); Statement stmt = conn.createStatement( ); ResultSet rset = stmt.executeQuery( "select 'Hello Thin driver data source tester '||" + "initcap(USER)||'!' result from dual"); if (rset.next( )) System.out.println(rset.getString(1)); rset.close( ); stmt.close( ); conn.close( ); } } 7.1.4 Caveats I hope you can appreciate the long-term gain of using DataSources with JNDI rather than embedding connections in your code: • It makes your code independent of a JDBC driver • It makes your code independent of a database URL • It allows you to look up the driver and URL in one operation from anywhere on the network DataSource objects do, however, have a few drawbacks One is that you can't use Oracle Advanced Security with the Thin driver, because there is no way to set the oracle.net properties for data encryption and integrity This is because oracle.net properties are not a part of the standard, nor are they part of Oracle's implementation-specific set of DataSource properties Another drawback to using DataSources is that you have to make the investment in an LDAP directory to truly leverage the use of JNDI, and that can be quite costly In addition to the drawbacks I've mentioned, there are a few DataSource behaviors you should be aware of One concerns the logging feature There are two methods you can use to set and get the log writer for a DataSource A log writer is a PrintWriter object used by the driver to write its activities to a log file They are: public synchronized void setLogWriter(PrintWriter pw) throws SQLException public synchronized PrintWriter getLogWriter( ) throws SQLException As with the DriverManager facility, logging is disabled by default You will always need to call the setLogWriter( ) method after a DataSource has been instantiated, even if you set the log writer before you bind it to a directory Why? Because the PrintWriter you specify in the setLogWriter( ) method is transient and therefore cannot be serialized A second behavior you should be aware of is that when DataSource logging is enabled, it bypasses DriverManager's logging facility There are also two methods you can use to set and get the login timeout, which is the amount of time that an idle connection should be kept open The methods are: public synchronized void setLoginTimeout(int seconds) throws SQLException public synchronized int getLoginTimeout( ) throws SQLException Now that you have a firm grasp of how and when to use a DataSource object, let's continue our investigation of the JDBC 2.0 Extension API with a look at the connection pooling interface ConnectionPoolDataSource 7.2 Oracle's Connection Cache Recall that in Chapter we talked about a cached pool of connections used by servlets When a servlet needed a connection, it drew one from the pool The servlet did its work, and when it was done, it returned the connection back to the pool The benefit of using cached connections is that a servlet does not need to go through the resource-intensive task of opening a new database connection each time the servlet is invoked Also in Chapter 4, I showed a rudimentary connection caching tool Rudimentary as it was, it still required a fair bit of rather complex code to implement As part of the JDBC 2.0 Extension API, Oracle provides a ready-made connection cache interface along with a sample implementation Instead of wasting your precious time doing something that has already been done for you, you can use Oracle's connection cache immediately and in turn concentrate on the business problem at hand At the heart of Oracle's connection caching framework is the connection pool data source It's important you understand what that is and how it works before getting into the details of the connection cache framework itself 7.2.1 ConnectionPoolDataSources A ConnectionPoolDataSource is a DataSource that can be pooled Instead of returning a Connection object as a DataSource object does, a ConnectionPoolDataSource returns a PooledConnection object A PooledConnection object itself holds a physical database connection that is pooled In turn, a PooledConnection returns a Connection object This single layer of indirection allows a ConnectionPoolDataSource to manage PooledConnection objects You can use a PooledConnection to add or remove ConnectionEventListeners A ConnectionEventListener is any Java program thread that wishes to be notified whenever a connection is opened or closed When a Connection is received from or returned to a PooledConnection, the appropriate ConnectEvent event is triggered to close or return the Connection object to its associated pool In this case, the Connection object is not the same implementation of the Connection interface utilized by DriverManager Instead, it's a logical implementation managed by the PooledConnection object The ConnectionPoolDataSource interface is implemented by the class OracleConnectionPoolDataSource, which extends OracleDataSource This means that all the methods from the OracleDataSource class and ConnectionPoolDataSource interface are available in an OracleConnectionPoolDataSource The OraclePooledConnection class implements the PooledConnection interface and also provides the following five constructors: public OraclePooledConnection( ) throws SQLException public OraclePooledConnection(String url) throws SQLException public OraclePooledConnection(String url, String user, String passw ord) throws SQLException public OraclePooledConnection(Connection pc) public OraclePooledConnection(Connection pc, boolean autoCommit) The OracleConnectionEventListener class implements the ConnectionEventListener interface It also provides the following two constructors and one additional method: public OracleConnectionEventListener( ) public OracleConnectionEventListener(DataSource ds) public void setDataSource(DataSource ds) Collectively, these JDBC classes and interfaces, along with Oracle's implementation of them, provide a framework for connection caching However, the topic of how they can be used to build a connection cache is well beyond the scope of this book Besides, Oracle already provides a connection cache interface and sample implementation Let's look at how you can leverage those in your programs 7.2.2 Connection Cache Implementation Let's start our discussion of Oracle's connection cache implementation by defining a few important terms: Connection pool A pool of one or more Connections that use the same properties to establish a physical connection to a database By "properties," I mean things such as databaseName, serverName, portNumber, etc Connection cache A cache of one or more physical connections to one or more databases Pooled connection cache A cache of one or more connections to the same database for the same username Oracle's connection cache interface is named OracleConnectionCache Together, the interface and its implementation provide a cache of physical connections to a particular database for a specified username 7.2.2.1 The OracleConnectionCache interface Oracle's OracleConnectionCache interface defines the following three methods to aid you in managing a connection pool cache: public void close( ) throws SQLException public void closePooledConnection(PooledConnection pc) throws SQLException public void reusePooledConnection(PooledConnection pc) throws SQLException These methods perform the following functions: close( ) Used to close a logical connection to the database obtained from a PooledConnection A logical connection is a connection that has been allocated from a pool When a logical connection is closed, the connection is simply returned to the pool It may physically remain open but is logically no longer in use closePooledConnection( ) Used to remove the associated PooledConnection from a connection pool reusePooledConnection( ) Used to return a PooledConnection to a connection pool 7.2.2.2 The OracleConnectionCacheImpl class The OracleConnectionCacheImpl class extends OracleDataSource and implements the OracleConnectionCache interface Beyond what OracleConnectionCacheImpl inherits from OracleDataSource and the methods it implements from the OracleConnectionCache interface, the OracleConnectionCacheImpl class provides the following constants, constructors, and methods: public final int DYNAMIC_SCHEME public final int FIXED_RETURN_NULL_SCHEME public final int FIXED_WAIT_SCHEME public OracleConnectionCacheImpl( ) throws SQLException public OracleConnectionCacheImpl(ConnectionPoolDataSource cpds) throws SQLException public int getActiveSize( ) public int getCacheSize( ) public void setCacheScheme(int cacheScheme); public int getCacheScheme( ) public void setConnectionPoolDataSource(ConnectionPoolDataSource cpds) throws SQLException public void setMinLimit(int minCacheSize) public int getMinLimit( ) public void setMaxLimit(int maxCacheSize) public int getMaxLimit( ) The first three constants are used with the setCacheScheme( ) method to specify the caching scheme to be used by a given connection cache implementation Caches usually employ a minimum and maximum number of connections as part of a resource strategy The minimum value keeps a minimum number of connections on hand to speed up the connection process A cache uses the maximum value to limit the amount of operating-system resources utilized This prevents the cache from growing beyond its host's ability to provide resources The setCacheScheme( ) method's constants control the behavior of the cache when the specified maximum connection limit has been exceeded The values are defined as follows: DYNAMIC_SCHEME The cache will create connections above the specified maximum limit when necessary but will in turn close connections as they are returned to the cache until the number of connections is within the maximum limit Connections will never be cached above the maximum limit This is the default setting FIXED_RETURN_NULL_SCHEME The cache will return a null connection once the maximum connection limit has been exceeded FIXED_WAIT_SCHEME The cache will wait until there is a connection available and will then return it to the calling application The OracleConnectionCacheImpl class implements two constructor methods, and there are three ways that you can use them to initialize a cache: You can use the default constructor and set the connection properties individually after you've instantiated an object of the class You can use the default constructor to instantiate an object of the class Then you can create a ConnectionPoolDataSource object, initialize it, and pass it as a parameter to the setConnectionPoolDataSource( ) method You can create and initialize a ConnectionPoolDataSource object and then pass it as a parameter to the second form of the OracleConnectionCacheImpl constructor The other methods implemented by the OracleConnectionCacheImpl class are straightforward getter and setter methods They exactly what their names indicate 7.2.3 A Connection Caching Example Now that you have an idea of what an OracleConnectionCacheImpl object can do, let's rewrite our caching object from Chapter using Oracle's caching implementation We'll build a new caching object named OCCIConnection that will use the OracleConnectionCacheImpl class to create a modular caching module The overall development process that we'll follow for this example is: We'll create a program that allows us to create a connection pool data source and bind it to our JNDI directory We'll create a class to implement and manage connection caches We'll test the connection cache using one servlet that retrieves and uses connections and another that displays the current status of the cache 7.2.3.1 Creating and binding a ConnectionPoolDataSource In my opinion, there's no advantage to using a DataSource unless you also utilize JNDI, so our examples here will once again use Sun's filesystem implementation of JNDI First, we'll create a program named OCCIBind, shown in Example 7-4, to bind a ConnectionPoolDataSource to our JNDI directory OCCIBind is similar to the TestDSBind program shown in Example 7-2, but this time, we bind an OraclePoolConnectionSource Example 7-4 An application that binds a ConnectionPoolDataSource import java.sql.*; import java.util.*; import javax.naming.*; import oracle.jdbc.pool.*; public class OCCIBind { public static void main (String args []) throws SQLException, NamingException { Context context = null; try { Properties properties = new Properties( ); properties.setProperty( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); properties.setProperty( Context.PROVIDER_URL, "file:/JNDI/JDBC"); context = new InitialContext(properties); } catch (NamingException ne) { System.err.println(ne.getMessage( )); } OracleConnectionPoolDataSource ocpds = new OracleConnectionPoolDataSource( ); ocpds.setDescription("Database"); ocpds.setDriverType("thin"); ocpds.setServerName("dssw2k01"); ocpds.setPortNumber(1521); ocpds.setDatabaseName("orcl"); ocpds.setUser("scott"); ocpds.setPassword("tiger"); context.bind(ocpds.getDescription( ), ocpds); } } Make sure that the JDBC subdirectory exists under the JNDI directory on your hard drive Then compile and execute the program Once again, you can find the serialized values of our newly bound OracleConnectionPoolDataSource in a file named bindings in the JDBC subdirectory 7.2.3.2 Creating the connection manager Next, we'll create the OCCIConnection class in Example 7-5 This class uses static methods so it can perform its functionality without being instantiated It instantiates a OracleConnectionCacheImpl object to manage the connection pools When a connection is requested, any existing pools are searched first If a matching connection pool cannot be found, then a new OracleConnectionCacheImpl is created to hold connections for the new pool Example 7-5 An OracleConnectionCacheImpl caching implementation import import import import import import java.io.*; java.sql.*; java.util.*; javax.naming.*; javax.sql.*; oracle.jdbc.pool.*; public class OCCIConnection { private static boolean verbose = false; private static int numberImplementations = 0; private static Vector cachedImplementations = new Vector( public synchronized static Connection checkOut( return checkOut("Database"); } ); ) { public synchronized static Connection checkOut(String baseName) { boolean found = false; OracleConnectionCacheImpl cached = null; Connection connection = null; if (verbose) { System.out.println("There are " + Integer.toString(numberImplementations) + " connections in the cache"); System.out.println("Searching for a matching implementation "); } for (int i=0;!found && i 0) { for (int i=0;i < lines.length;i++) { out.println(lines[i]); } } else out.println("No caches implemented!"); out.println(""); out.println(""); out.println(""); } public void doPost( HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doGet(request, response); } } ... closePooledConnection( ) Used to remove the associated PooledConnection from a connection pool reusePooledConnection( ) Used to return a PooledConnection to a connection pool 7.2.2.2 The OracleConnectionCacheImpl... connection pool data source and bind it to our JNDI directory We''ll create a class to implement and manage connection caches We''ll test the connection cache using one servlet that retrieves and. .. OracleDataSource class and ConnectionPoolDataSource interface are available in an OracleConnectionPoolDataSource The OraclePooledConnection class implements the PooledConnection interface and also provides