Set - 6

Question 1 :

How do I execute stored procedures?

Answer :

Here is an example on how to execute a stored procedure with JDBC (to use this in a servlet is the same the only thing is that you create the connection and callable statement in the init() of the servlet):
package DBTest;
import java.sql.*;
public class JdbcTest {

private String msDbUrl = "jdbc:odbc:ms";
private String msJdbcClass = "sun.jdbc.odbc.JdbcOdbcDriver";
private Connection mcDbAccess;
private CallableStatement msProcedure;

public JdbcTest() {
try {
Class.forName( msDbUrl ).newInstance();
mcDbAccess = DriverManager.getConnection( msJdbcClass, "milestone", "milestone" );
msProcedure = mcDbAccess.prepareCall(
"{? = call sp_sav_Bom_Header( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) }"
);
msProcedure.registerOutParameter( 1, java.sql.Types.VARCHAR );
msProcedure.setInt( 2, -1 );
msProcedure.setInt( 3, 39 );
msProcedure.setString( 4, "format" );
long ltTest = new java.util.Date().getTime();
System.out.println( "Today: " + ltTest );
msProcedure.setTimestamp( 5, new Timestamp( ltTest ) );
msProcedure.setString( 6, "type" );
msProcedure.setString( 7, "submitter" );
msProcedure.setString( 8, "email" );
msProcedure.setString( 9, "phone" );
msProcedure.setString( 10, "comments" );
msProcedure.setString( 11, "label" );
msProcedure.setInt( 12, 52 );
msProcedure.setBoolean( 13, true );
msProcedure.setBoolean( 14, false );
msProcedure.setInt( 15, 53 );
msProcedure.setString( 16, "runtime" );
msProcedure.setString( 17, "configuration" );
msProcedure.setBoolean( 18, true );
msProcedure.setBoolean( 19, false );
msProcedure.setString( 20, "special instructions" );
msProcedure.setInt( 21, 54 );

ResultSet lrsReturn = null;
System.out.println( "Execute: " + (lrsReturn = msProcedure.executeQuery() ) );
while( lrsReturn.next() ) {
System.out.println( "Got from result set: " + lrsReturn.getInt( 1 ) );
}
System.out.println( "Got from stored procedure: " + msProcedure.getString( 1 ) );
} catch( Throwable e ) {
e.printStackTrace();
}
}

public static void main(String[] args) {
new JdbcTest();
}
}
I also tried it by using a native JDBC driver (i-net) and it also works fine. The only problem we encounter with JDBC-ODBC bridge is that a stored procedure pads spaces to the full length of a VARCHAR but the native JDBC behaves right. Therefore I suggest to use JDBC native drivers.
The above example uses the MS SQL Server.


Question 2 :

How can I get data from multiple ResultSets?

Answer :

With certain database systems, a stored procedure can return multiple result sets, multiple update counts, or some combination of both. Also, if you are providing a user with the ability to enter any SQL statement, you don't know if you are going to get a ResultSet or an update count back from each statement, without analyzing the contents. The Statement.execute() method helps in these cases.

Method Statement.execute() returns a boolean to tell you the type of response:

* true indicates next result is a ResultSet
Use Statement.getResultSet to get the ResultSet
* false indicates next result is an update count
Use Statement.getUpdateCount to get the update count
* false also indicates no more results
Update count is -1 when no more results (usually 0 or positive)

After processing each response, you use Statement.getMoreResults to check for more results, again returning a boolean. The following demonstrates the processing of multiple result sets:

boolean result = stmt.execute(" ... ");
int updateCount = stmt.getUpdateCount();

while (result || (updateCount != -1)) {
if(result) {
ResultSet r = stmt.getResultSet();
// process result set
} else if(updateCount != -1) {
// process update count
}
result = stmt.getMoreResults();
updateCount = stmt.getUpdateCount();
}


Question 3 :

How can resultset records be restricted to certain rows?

Answer :

The easy answer is "Use a JDBC 2.0 compliant driver".
With a 2.0 driver, you can use the setFetchSize() method within a Statement or a ResultSet object.
For example,
Statement stmt = con.createStatement();
stmt.setFetchSize(400);
ResultSet rs = stmt.executeQuery("select * from customers");

will change the default fetch size to 400.
You can also control the direction in which the rows are processed. For instance:
stmt.setFetchDirection(ResultSet.FETCH_REVERSE)
will process the rows from bottom up.
The driver manager usually defaults to the most efficient fetch size...so you may try experimenting with different value for optimal performance.


Question 4 :

How do I insert an image file (or other raw data) into a database?

Answer :

All raw data types (including binary documents or images) should be read and uploaded to the database as an array of bytes, byte[]. Originating from a binary file,
1. Read all data from the file using a FileInputStream.
2. Create a byte array from the read data.
3. Use method setBytes(int index, byte[] data); of java.sql.PreparedStatement to upload the data.


Question 5 :

How can I connect from an applet to a database on the server?

Answer :

There are two ways of connecting to a database on the server side.
1. The hard way. Untrusted applets cannot touch the hard disk of a computer. Thus, your applet cannot use native or other local files (such as JDBC database drivers) on your hard drive. The first alternative solution is to create a digitally signed applet which may use locally installed JDBC drivers, able to connect directly to the database on the server side.
2. The easy way. Untrusted applets may only open a network connection to the server from which they were downloaded. Thus, you must place a database listener (either the database itself, or a middleware server) on the server node from which the applet was downloaded. The applet would open a socket connection to the middleware server, located on the same computer node as the webserver from which the applet was downloaded. The middleware server is used as a mediator, connecting to and extract data from the database.


Question 6 :

Can I use the JDBC-ODBC bridge driver in an applet?

Answer :

Short answer: No.
Longer answer: You may create a digitally signed applet using a Certicate to circumvent the security sandbox of the browser.


Question 7 :

Which is the preferred collection class to use for storing database result sets?

Answer :

When retrieving database results, the best collection implementation to use is the LinkedList. The benefits include:
* Retains the original retrieval order
* Has quick insertion at the head/tail
* Doesn't have an internal size limitation like a Vector where when the size is exceeded a new internal structure is created (or you have to find out size beforehand to size properly)
* Permits user-controlled synchronization unlike the pre-Collections Vector which is always synchronized

Basically:
ResultSet result = stmt.executeQuery("...");
List list = new LinkedList();
while(result.next()) {
list.add(result.getString("col"));
}

If there are multiple columns in the result set, you'll have to combine them into their own data structure for each row. Arrays work well for that as you know the size, though a custom class might be best so you can convert the contents to the proper type when extracting from databse, instead of later.


Question 8 :

The java.sql package contains mostly interfaces. When and how are these interfaces implemented while connecting to database?

Answer :

The implementation of these interfaces is all part of the driver. A JDBC driver is not just one class - it is a complete set of database-specific implementations for the interfaces defined by the JDBC.
These driver classes come into being through a bootstrap process. This is best shown by stepping through the process of using JDBC to connect to a database, using Oracle's type 4 JDBC driver as an example:

* First, the main driver class must be loaded into the VM:
Class.forName("oracle.jdbc.driver.OracleDriver");
The specified driver must implement the Driver interface. A class initializer (static code block) within the OracleDriver class registers the driver with the DriverManager.
* Next, we need to obtain a connection to the database:
String jdbcURL = "jdbc:oracle:thin:@www.jguru.com:1521:ORCL";
Connection connection = DriverManager.getConnection(jdbcURL);
DriverManager determines which registered driver to use by invoking the acceptsURL(String url) method of each driver, passing each the JDBC URL. The first driver to return "true" in response will be used for this connection. In this example, OracleDriver will return "true", so DriverManager then invokes the connect() method of OracleDriver to obtain an instance of OracleConnection. It is this database-specific connection instance implementing the Connection interface that is passed back from the DriverManager.getConnection() call.
* The bootstrap process continues when you create a statement:
Statement statement = connection.createStatement();
The connection reference points to an instance of OracleConnection. This database-specific implementation of Connection returns a database-specific implementation of Statement, namely OracleStatement
* Invoking the execute() method of this statement object will execute the database-specific code necessary to issue an SQL statement and retrieve the results:
ResultSet result = statement.executeQuery("SELECT * FROM TABLE");
Again, what is actually returned is an instance of OracleResultSet, which is an Oracle-specific implementation of the ResultSet interface.
So the purpose of a JDBC driver is to provide these implementations that hide all the database-specific details behind standard Java interfaces.


Question 9 :

How can I manage special characters (for example: " _ ' % ) when I execute an INSERT query? If I don't filter the quoting marks or the apostrophe, for example, the SQL string will cause an error?

Answer :

In JDBC, strings containing SQL commands are just normal strings - the SQL is not parsed or interpreted by the Java compiler. So there is no special mechanism for dealing with special characters; if you need to use a quote (") within a Java string, you must escape it.
The Java programming language supports all the standard C escapes, such as \n for newline, \t for tab, etc. In this case, you would use \" to represent a quote within a string literal:

String stringWithQuote =
"\"No,\" he replied, \"I did not like that salted licorice.\"";

This only takes care of one part of the problem: letting us control the exact string that is passed on to the database. If you want tell the database to interpret characters like a single quote (') literally (and not as string delimiters, for instance), you need to use a different method. JDBC allows you to specify a separate, SQL escape character that causes the character following to be interpreted literally, rather than as a special character.

An example of this is if you want to issue the following SQL command:

SELECT * FROM BIRDS
WHERE SPECIES='Williamson's Sapsucker'

In this case, the apostrophe in "Williamson's" is going to cause a problem for the database because SQL will interpret it as a string delimiter. It is not good enough to use the C-style escape \', because that substitution would be made by the Java compiler before the string is sent to the database.
Different flavors of SQL provide different methods to deal with this situation. JDBC abstracts these methods and provides a solution that works for all databases. With JDBC you could write the SQL as follows:

Statement statement = // obtain reference to a Statement
statement.executeQuery(
"SELECT * FROM BIRDS WHERE SPECIES='Williamson/'s Sapsucker' {escape '/'}");

The clause in curly braces, namely {escape '/'}, is special syntax used to inform JDBC drivers what character the programmer has chosen as an escape character. The forward slash used as the SQL escape has no special meaning to the Java compiler; this escape sequence is interpreted by the JDBC driver and translated into database-specific SQL before the SQL command is issued to the database.
Escape characters are also important when using the SQL LIKE clause. This usage is explicitly addressed in section 11.5 of the JDBC specification:

The characters "%" and "_" have special meaning in SQL LIKE clauses (to match zero or more characters, or exactly one character, respectively). In order to interpret them literally, they can be preceded with a special escape character in strings, e.g. "\". In order to specify the escape character used to quote these characters, include the following syntax on the end of the query:
{escape 'escape-character'}
For example, the query
SELECT NAME FROM IDENTIFIERS WHERE ID LIKE '\_%' {escape '\'}
finds identifier names that begin with an underbar.


Question 10 :

How can I make batch updates using JDBC?

Answer :

One of the more advanced features of JDBC 2.0 is the ability to submit multiple update statements to the database for processing as a single unit. This batch updating can be significantly more efficient compared to JDBC 1.0, where each update statement has to be executed separately.

Consider the following code segment demonstrating a batch update:
try {
dbCon.setAutoCommit(false);
Statement stmt= dbCon.createStatement();
stmt.addBatch("INSERT INTO bugs "+
"VALUES (1007, 'Server stack overflow', 1,2,{d '1999-01-01'})");
stmt.addBatch("INSERT INTO bugs "+
"VALUES (1008,'Cannot load DLL', 3,1,{d '1999-01-01'})");
stmt.addBatch("INSERT INTO bugs "+
"VALUES (1009,'Applet locks up',2,2,{d '1999-01-01'})");

int[] updCnt = stmt.executeBatch();
dbCon.commit();

} catch (BatchUpdateException be) {

//handle batch update exception
int[] counts = be.getUpdateCounts();
for (int i=0; I counts.length; i++) {
System.out.println("Statement["+i+"] :"+counts[i]);
}
dbCon.rollback();
}
catch (SQLException e) {

//handle SQL exception
dbCon.rollback();
}

Before carrying out a batch update, it is important to disable the auto-commit mode by calling setAutoCommit(false). This way, you will be able to rollback the batch transaction in case one of the updates fail for any reason. When the Statement object is created, it is automatically associated a "command list", which is initially empty. We then add our SQL update statements to this command list, by making successive calls to the addBatch() method. On calling executeBatch(), the entire command list is sent over to the database, and are then executed in the order they were added to the list. If all the commands in the list are executed successfully, their corresponding update counts are returned as an array of integers. Please note that you always have to clear the existing batch by calling clearBatch() before creating a new one.
If any of the updates fail to execute within the database, a BatchUpdateException is thrown in response to it. In case there is a problem in returning the update counts of each SQL statement, a SQLException will be thrown to indicate the error.


Question 11 :

How do I extract SQL table column type information?

Answer :

Use the getColumns method of the java.sql.DatabaseMetaData interface to investigate the column type information of a particular table. Note that most arguments to the getColumns method (pinpointing the column in question) may be null, to broaden the search criteria. A code sample can be seen below:

public static void main(String[] args) throws Exception
{
// Load the database driver - in this case, we
// use the Jdbc/Odbc bridge driver.
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

// Open a connection to the database
Connection conn = DriverManager.getConnection("[jdbcURL]",
"[login]", "[passwd]");

// Get DatabaseMetaData
DatabaseMetaData dbmd = conn.getMetaData();

// Get all column types for the table "sysforeignkeys", in schema
// "dbo" and catalog "test".
ResultSet rs = dbmd.getColumns("test", "dbo", "sysforeignkeys", "%");

// Printout table data
while(rs.next())
{
// Get dbObject metadata
String dbObjectCatalog = rs.getString(1);
String dbObjectSchema = rs.getString(2);
String dbObjectName = rs.getString(3);
String dbColumnName = rs.getString(4);
String dbColumnTypeName = rs.getString(6);
int dbColumnSize = rs.getInt(7);
int dbDecimalDigits = rs.getInt(9);
String dbColumnDefault = rs.getString(13);
int dbOrdinalPosition = rs.getInt(17);
String dbColumnIsNullable = rs.getString(18);

// Printout
System.out.println("Col(" + dbOrdinalPosition + "): " + dbColumnName
+ " (" + dbColumnTypeName +")");
System.out.println(" Nullable: " + dbColumnIsNullable +
", Size: " + dbColumnSize);
System.out.println(" Position in table: " + dbOrdinalPosition
+ ", Decimal digits: " + dbDecimalDigits);
}

// Free database resources
rs.close();
conn.close();
}


Question 12 :

How can I investigate the parameters to send into and receive from a database stored procedure?

Answer :

Use the method getProcedureColumns in interface DatabaseMetaData to probe a stored procedure for metadata. The exact usage is described in the code below.

NOTE! This method can only discover parameter values. For databases where a returning ResultSet is created simply by executing a SELECT statement within a stored procedure (thus not sending the return ResultSet to the java application via a declared parameter), the real return value of the stored procedure cannot be detected. This is a weakness for the JDBC metadata mining which is especially present when handling Transact-SQL databases such as those produced by SyBase and Microsoft.

public static void main(String[] args) throws Exception
{
// Load the database driver - in this case, we
// use the Jdbc/Odbc bridge driver.
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver")
;
// Open a connection to the database
Connection conn = DriverManager.getConnection("[jdbcURL]",
"[login]", "[passwd]");

// Get DatabaseMetaData
DatabaseMetaData dbmd = conn.getMetaData();

// Get all column definitions for procedure "getFoodsEaten" in
// schema "testlogin" and catalog "dbo".
System.out.println("Procedures are called '" + dbmd.getProcedureTerm() +"' in the DBMS.");
ResultSet rs = dbmd.getProcedureColumns("test", "dbo", "getFoodsEaten", "%");

// Printout table data
while(rs.next())
{
// Get procedure metadata
String dbProcedureCatalog = rs.getString(1);
String dbProcedureSchema = rs.getString(2);
String dbProcedureName = rs.getString(3);
String dbColumnName = rs.getString(4);
short dbColumnReturn = rs.getShort(5);
String dbColumnReturnTypeName = rs.getString(7);
int dbColumnPrecision = rs.getInt(8);
int dbColumnByteLength = rs.getInt(9);
short dbColumnScale = rs.getShort(10);
short dbColumnRadix = rs.getShort(11);
String dbColumnRemarks = rs.getString(13);


// Interpret the return type (readable for humans)
String procReturn = null;

switch(dbColumnReturn)
{
case DatabaseMetaData.procedureColumnIn:
procReturn = "In";
break;
case DatabaseMetaData.procedureColumnOut:
procReturn = "Out";
break;
case DatabaseMetaData.procedureColumnInOut:
procReturn = "In/Out";
break;
case DatabaseMetaData.procedureColumnReturn:
procReturn = "return value";
break;
case DatabaseMetaData.procedureColumnResult:
procReturn = "return ResultSet";
default:
procReturn = "Unknown";
}

// Printout
System.out.println("Procedure: " + dbProcedureCatalog + "." + dbProcedureSchema
+ "." + dbProcedureName);
System.out.println(" ColumnName [ColumnType(ColumnPrecision)]: " + dbColumnName
+ " [" + dbColumnReturnTypeName + "(" + dbColumnPrecision + ")]");
System.out.println(" ColumnReturns: " + procReturn + "(" + dbColumnReturnTypeName + ")");
System.out.println(" Radix: " + dbColumnRadix + ", Scale: " + dbColumnScale);
System.out.println(" Remarks: " + dbColumnRemarks);
}

// Close database resources
rs.close();
conn.close();
}