|
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
General Java Cross-Database Portabliity with JDBC
Cross-Database Portabliity with JDBC
By: Sesh Venugopal
Jan. 1, 2000 12:00 AM
Java programs can use the JDBC API to access relational databases, thereby cleanly separating the database system from the application. This approach holds the promise of cross-database portability, i.e., "write once, run on any database." In practice, several stumbling blocks stand in the way of fulfilling this promise. This article shows examples of these stumbling blocks so you can see the common pattern of development. Drawing on this pattern, I'll develop a roadmap you can use to write applications that can work around these stumbling blocks. Finally, I'll demonstrate how the roadmap can be applied to build a portable application.
Breakdown of Portability
To drive the Access database, I used a Windows-supplied Microsoft Access driver and the JDBC-ODBC bridge from Sun (which came with JDK 1.1) as the JDBC driver of choice. For Oracle I used the JDBC OCI8 driver that supported JDBC 1.22.
Situation 1: SQL Escape Syntax
JDBC specifies a so-called SQL escape syntax that may be used in Statement instances for various tasks such as pattern matching of strings, executing database-specific scalar functions, manipulating date and time values, calling stored procedures and executing outer joins. For each of these tasks JDBC specifies a syntax that's used by the developer, and the JDBC driver is responsible for translating this into database-specific code. Following is an outer join example that doesn't port.
select dname, ename from dept d, emp e Note that the department OPERATIONS (DEPTNO = 40) doesn't appear in the resulting table because none of the entries in the EMP table has a DEPTNO value of 40. But what if you want to see every department represented in the result, even if it doesn't have any entries in EMP? You need to execute an outer join. An outer join preserves unmatched rows in either the left table (left outer join) or the right table (right outer join). JDBC prescribes a SQL escape syntax for outer joins that looks like this: { oj outer-join} where the keyword oj stands for outer join, and outer-join is of the form: table left outer join { table | outer-join} on search-condition If you want to preserve all the unmatched departments in the example, you can issue a left outer join by specifying the tables in order DEPT followed by EMP:
select dname, ename from This works perfectly well with Access, but not with Oracle. The OCI8 driver doesn't support the oj escape syntax. Instead, you need to use the following equivalent SQL statement:
select dname, ename from dept d, emp e in which the (+) at the end of the statement stands for outer join. The result of this outer join is the table in Figure 4.
Situation 2: DataType Translation
The next example shows how the table of a database can be populated. A naïve approach hard-codes datatype-specific information in the program, thereby making it nonportable. Following this I'll show a portable alternative that illustrates another stumbling block.
The variable conn refers to the database connection. The PreparedStatement instance, ps, is parametrized with the values of the columns, indicated by the question marks. Each parameter is matched with a value before executing the prepared statement for some row. The following code segment shows how the empno column value is filled:
String next = st.nextToken(); The first line reads an input token (using a StringTokenizer instance st to parse each input line) as a String object. The second line checks whether this token spells null, which indicates that the input does not have a value for this column. In this case a special setNull method is invoked on ps. The first argument to this method indicates the parameter position in the prepared statement (empno is the first); the second argument indicates the JDBC SQL type of the intended value. As mentioned earlier, the class java.sql.Types defines a set of constants corresponding to various SQL types, and the constant java.sql.Types.SMALLINT stands for the SQL type SMALLINT. For every column in the EMP table, these three lines of code need to be implemented with appropriate modification in data types. There are a couple of serious drawbacks to this approach. One is that the program becomes ungainly and hard to maintain. Another is that all data types are exposed to the developer, so if a column type changes, or this program is ported to a different database, this code must be rewritten. A better alternative postpones the datatype translation to runtime, thereby making the code portable. This alternative makes use of the PreparedStatement method setObject: ps.setObject(1, next); used instead of ps.setShort(1, (short)Integer.parseInt(next)); The second argument, next, is a String instance that's automatically translated by the JDBC driver to the required database type, which in this case is a small integer. There is no coding of any datatype information. This statement can be set in a loop, using the loop index to control the first argument, which is the position of the parameter in the PreparedStatement instance. If one or more of the column values is null, a little more work is required. The statement would then be written as:
ps.setNull(1, To maintain the datatype independence of the code to make it portable, the SQL type of the column value isn't coded directly here. Instead, it's discovered at runtime using the java.sql.DatabaseMetaData interface, and then plugged into the above statement. The DatabaseMetaData interface provides metadata information for a database. Metadata is data that describes data. For instance, the EMP table contains employee information. This is data. Metadata would contain information on things like the number of columns in the table, the data types of these columns, whether a column can have null values, and so on. This is data about data. The DatabaseMetaData interface provides methods that can be called to find out various metadata information about a database. For our example above, we need to find the data types of the columns of EMP so we can plug that information into the PreparedStatement setNull method call. This alternative works with the MS Access database, but not with the Oracle database the OCI8 driver refuses to pass the setObject invocation because it's unable to translate the String external type to the required database type.
Situation 3: Positioned Update
select name, rowid from emp It may be retrieved using the ResultSet getString method: String rowid = rset.getString(2); It may be set as a parameter using the PreparedStatement setString method: ps.setString(2, rowid) The JDBC-compliant template code shown in Listing 1 can be reworked for Oracle, using rowid, at the cost of giving up portability.
Roadmap for Workarounds
A related common issue that is apparent from the examples is that even if the driver is appropriate for the task at hand, some database-specific information may have to be factored into the application. A naïve approach to this, which is to hard-code the required information into the application, makes the application nonportable. However, it's often possible to discover this information at runtime instead by using the DatabaseMetaData interface, thereby maintaining the portability of the application. These observations suggest a two-point roadmap to work around the stumbling blocks:
I'll now use the roadmap to sketch the process of building an application that populates the EMP table with data from a text file. Recall that a row of data may be inserted into the table by using a PreparedStatement instance, which in turn requires that the type of each column of EMP be discovered at runtime using the DatabaseMetaData interface. Following Point 1 of the roadmap, the DriverLoader class is implemented, which loads either the OCI8 driver for Oracle8 or the JDBC-ODBC bridge for MS Access, as required at runtime. Other drivers for the existing databases, as well as drivers for other databases, may be added as needed. Following Point 2 of the roadmap, the application is divided into three classes. One of these, TableColumns, implements the metadata discovery process. In this case it's the discovery of column types for the table EMP. Another class, TableMediator, uses this metadata and interacts with a third class, TableBuilder, that reads data from the input text file and sends it down the chain of classes to the database. This chain of classes is illustrated in Figure 7.
Conclusion
I picked this specific set of three stumbling blocks for illustration simply because they provide a window into very different ways in which JDBC may be used in a database application. These examples and the workarounds demonstrated point to a general way of structuring a database application for cross-database portability. Specific refinements to this general approach can be adopted based on special requirements of the applications and the architecture; there's a lot of room for maneuvering within the proposed roadmap. Reader Feedback: Page 1 of 1
Latest Cloud Developer Stories
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week
Breaking Cloud Computing News
|
|||||||||||||||||||||||||||||||||||||||||||||||||