Regarding oracle jdbc bulk array inserts, Greg Rahn wrote this week about the performance gains to be had by batching up calls to the database using the array interface.
As an addendum to his excellent points, please find attached a comparison with using an Oracle collection of an Oracle object type – forgive my Java. Using StructDescriptor, STRUCT, ArrayDescriptor and ARRAY structures is unsightly and unintuitive but they can deliver some further performance gains. If only we could wrap this approach up in some user-friendly layer then I reckon we could kick some of these iterative row-by-row ORM tools into touch.
First up, for the baseline, based On Greg’s example, this is what my batch size performance was like inserting 10000 rows into emp on my system:

And, using an Oracle collection of Oracle object types, uploading the 10000 rows in a single INSERT… TABLE … CAST statement it took 0.219 seconds – Java class here.
Which compared very favourably.
Inline scripts:
create type to_emp as object
(EMPNO NUMBER(4)
,ENAME VARCHAR2(10)
,JOB VARCHAR2(9)
,MGR NUMBER(4)
,HIREDATE DATE
,SAL NUMBER(7,2)
,COMM NUMBER(7,2)
,DEPTNO NUMBER(2));
/
create type tt_emp as table of to_emp;
/
import java.sql.*;
import java.util.*;
import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;
import oracle.sql.ArrayDescriptor;
import oracle.sql.ARRAY;
public class bulkInsert {
public static void main(String[] args) {
try {
OracleDataSource ods = new OracleDataSource();
ods.setURL("jdbc:oracle:oci8:@ora.noldb507");
ods.setUser("scott");
ods.setPassword("tiger");
OracleConnection conn = (OracleConnection) ods.getConnection();
conn.setAutoCommit(false);
short seqnum = 0;
String[] metric = new
String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
metric[OracleConnection.END_TO_END_ACTION_INDEX] = "insertEmp";
metric[OracleConnection.END_TO_END_MODULE_INDEX] = "bulkInsert";
metric[OracleConnection.END_TO_END_CLIENTID_INDEX] = "myClientId";
conn.setEndToEndMetrics(metric, seqnum);
DatabaseMetaData meta = conn.getMetaData();
System.out.println(
"JDBC driver version is " + meta.getDriverVersion());
Statement stmt = conn.createStatement();
stmt.execute("alter session set sql_trace=true");
stmt.execute("truncate table emp");
int numberOfEmployees = Integer.parseInt(args[0]);
STRUCT[] structEmployee = new STRUCT[numberOfEmployees];
oracle.sql.StructDescriptor descEmployee = oracle.sql.StructDescriptor.createDescriptor("SCOTT.TO_EMP",conn);
java.sql.Timestamp now = new java.sql.Timestamp( (new java.util.Date() ).getTime() );
for (int i = 0; i < numberOfEmployees; i++) {
Object[] empValues = {
(i), // EMPNO
("Name" + i), // ENAME
("Job"), // JOB
(i), // MGR
now , //now
(10000 + i), // SAL
(100 + i), // COMM
(10) // DEPTNO
};
structEmployee[i] = new oracle.sql.STRUCT(descEmployee, conn, empValues);
}
oracle.sql.ArrayDescriptor empArrayDescriptor = oracle.sql.ArrayDescriptor.createDescriptor("SCOTT.TT_EMP",conn);
ARRAY empArray = new ARRAY(empArrayDescriptor,conn,structEmployee);
OraclePreparedStatement psEmp = (OraclePreparedStatement) conn.prepareStatement(
"insert /* insEmpBulk */ into emp (EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO) select * from table (cast (? as tt_emp))");
psEmp.setObject(1,empArray);
long start1 = System.currentTimeMillis();
// Set the batch size for each statment
((OraclePreparedStatement) psEmp).execute();
conn.commit();
psEmp.close();
long elapsedTimeMillis1 = System.currentTimeMillis() - start1;
// Get elapsed time in seconds
float elapsedTimeSec1 = elapsedTimeMillis1 / 1000F;
System.out.println("elapsed seconds: " + elapsedTimeSec1);
conn.close();
} catch (Exception e) {
System.err.println("Got an exception! ");
System.err.println(e.toString());
}
}
}

5 responses so far ↓
Caustic Dave // December 1, 2007 at 3:46 pm |
Thank you for the tip. I’m trying like mad to speed up an Oracle app at my workplace. I’m new to Oracle and appreciate you posting your results.
Three months since my last confession… « OraStory // September 24, 2008 at 9:49 am |
[...] Unsurprisingly, blog traffic has slowed as the posts have dried up but I’m still surprised by the daily numbers. In terms of most popular posts, the old deadlock detection is way out in front, followed by the less than instructive DBMS Metadata witterings. However, rising quickly up the ranks has been an old article I wrote about using Oracle collections with Java. [...]
dombrooks // September 25, 2008 at 7:47 am |
Note that if I was doing this for real, I’d doubtless pass the array into a stored proc which did the insert…select rather than having the SQL in the java.
Arindam Mukherjee // December 29, 2008 at 11:42 am |
In our web page, there are two blocks – transaction header and transaction details. In header block, there is one record but in detail block, there are multiple records.
Now java needs to all those records (ONE for header and Multiple for Details) to oracle procedure and oracle procedure takes charge of data insertion with some calculation in oracle database.
My question – how to establish handshake between oracle and java – how do you send multiple rows or result sets of detail block to oracle procedure.
dombrooks // December 29, 2008 at 1:59 pm |
Thanks for stopping by Arindam.
You’ve a number of choices .
My preference would be to use Oracle collections. To me, it makes the most sense from a transactional view and from the perspective of mapping your objects to your relational database.
But it does mean using some ugly java code in terms of structdescriptors, etc.
I would presume that you have some sort of “transaction” object (your definition of whatever a transaction object is) , made up of one transaction header and zero, one or more transaction details.
And I would create the same structure in Oracle.
For example (and I don’t know the specifics of your transaction header/detail), something like this:
create or replace type to_transaction_detail as object
(linenumber number
,product_id number
,description varchar2(20)
,quantity number);
/
create or replace type tt_transaction_detail as table of to_transaction_detail;
/
create or replace type to_transaction_header as object
(transaction_id number
,customer_id number
,description varchar2(20)
,transaction_date date);
/
create or replace type to_transaction as object
(transaction_header to_transaction_header
,transaction_details tt_transaction_detail);
/
And then you need to get dirty with the structs and struct descriptors so that you make one call to the database to save your transaction object.
And that call can deal with any inserts or updates if they need to happen (or do nothing if the data is the same).
Otherwise you’re probably looking at making one call to procedure to save each transaction detail passing in the transaction id for each detail line.