2008-12-19

Using Stored Procedure/Function with iBATIS

This is simple example for using stored function with iBATIS.

Recently I implemented number generating module using oracle stored function and iBATIS with lots of trial/error more than I expected. It's because I couldn't find a complete working example. The example in official manual from iBATIS is not sufficient , the example from the book 'iBATIS in Action' is not correct(I didn't check the corrections from the Manning site), and the search results from Goole didn't include excellent examples for me.

The following exmaple is using number table to generate number for construct new primary key for specified table.

The number table has following structure
CREATE TABLE test.PS_NUMBER (
  table_name VARCHAR2(50) NOT NULL,
  next_no NUMBER(20) NOT NULL,
  CONSTRAINT pk_number PRIMARY KEY (table_name)
);
To prevent same number is given in highly concurrent situation with Oracle database, SELECT ~ FOR UPDATE should be used.
But iBATIS dosen't provide ways to execute both select statement and update statement at the same time(as I know), I decided to use stored procedure. Because it is possible to lock the table row in case of server failure right after the first select(for update) statement is executed but before second update statement is excuted.

The stored procedure is like following
CREATE OR REPLACE FUNCTION gen_num_for_table(table_name_in IN VARCHAR2)
  RETURN NUMBER
  IS
  next_no_out NUMBER;
BEGIN
  SELECT next_no INTO next_no_out
  FROM ps_number
  WHERE table_name = table_name_in
  FOR UPDATE WAIT 3;

  UPDATE ps_number SET next_no = next_no + 1
  WHERE table_name = table_name_in;

  RETURN next_no_out;
END gen_num_for_table;
Now, for server applications the SQL Map is like following The statement part should be enclosed with curly brackets({}) and the statement is proceeded by '? = call' in case of function. In case of procedure, just start with 'call'. These are same syntax with java.sql.CallableStatement. Acually, iBATIS will execute callable statement using specified statement and parameterMap. The parameterMap should be specified surely. The order of parameter is important. In case of function, the first parameter is for return value and so should have 'OUT' mode attribute. For each parameter, jdbcType should be specified explicitly, so as to be interpreted to CallableStatment.registerOutParameter method with correct parameter and setXXX of CallableStatement.

Finally the application using above sql map is like following
public int generateNumberForTable(String tableName) throws Exception{

  java.util.Map map = new java.util.HashMap();
  map.put("table_name", tableName);

  this.sqlMapClient.queryForObject("Common.generateNumberForTable", map);

  return (Integer)map.get("next_no");
}
The input is given using map object as is expected from SQL Map definition. The important thing is that the result is not given as a return value of queryForObject method but is put in the map with the key specified in SQL Map definition.

For your information, the equivalent codes using plain JDBC would be like following. It could help you understand how the above codes would work.
public int generateNumberForTable(String tableName) throws Exception{
   Connection conn = null;
   CallableStatement cstmt = null;

   try{
      DataSource ds = this.sqlMapClient.getDataSource();
      conn = ds.getConnection();
      cstmt = conn.prepareCall("{? = call gen_num_for_table(?)}");
      cstmt.registerOutParameter(1, java.sql.Types.INTEGER);
      cstmt.setString(2, tableName);

      cstmt.executeQuery();

      return cstmt.getInt(1);
   }catch(Exception ex){
      ex.printStackTrace(System.err);
      throw ex;
   }finally{
      if(cstmt != null){ try{ cstmt.close();}catch(Exception ex2){} }
      if(conn != null){ try{ conn.close();}catch(Exception ex2){} }
   }
}
For more readings.
  • iBatis and Stored Oracle procedures/functions This is the article that I found after this writing and I think it gives you more information about iBATIS and Oracle stored procedure/function. I didn't execute personally the code in that article and I didn't understand what is 'ORACLECURSOR' type in the example code, but as a whole you can find hints or guide from the article.

6 comments:

  1. excellent man!...i was looking for some example like yours about returning values with ibatis from a stored, punctual & clear ... thanks!

    ReplyDelete
  2. It works just fine, thanks a lot!

    ReplyDelete
  3. nice explanation.
    at least for ibatis two, jdbcType must be spelled with a upper case T.

    ReplyDelete
  4. Note also that, due to the way ibatis two stored procedure code is implemented, the user must have select privileges on the mysql.procs table.

    ReplyDelete
  5. Could you please send me full code of the example ?.
    For some reason, I can't see tall the text in the example you posted .

    -Kiran

    ReplyDelete
  6. procedure is not being crated in oracle...it is giving the following error..

    ERROR at line 11: PL/SQL: SQL Statement ignored

    1. CREATE OR REPLACE FUNCTION gen_num_for_table(table_name_in IN VARCHAR2)
    2. RETURN NUMBER
    3. IS

    ReplyDelete