SPRING JDBC: PROBLEMA CON: 'SqlReturnType' [ORACLE TYPES]:

Que tal amigos,

Una consulta tengo una duda al momento de manejar SPRING JDBC al momento de llamar a PROCEDURE, pero en un escenario especifico:
Este escenario es cuando quiero retornar un ORACLE TYPE. Ya he probado conectarme enviando contra un procedure: VARIABLES NORMALES (Input/Output), Cursores (Input/Output), Oracle Types (Solo INPUT). y he podido sin problemas pero el escenario es mandar a un procedure un ORACLE TYPE y que me responda junto con otros 2 VARCHAR un 3er parametro de salida tambien de tipo ORACLE TYPE. Aqui el detalle:

-- ******************* TABLA: ******************* --
CREATE TABLE TB_SERV_LLAM(
SLL_ID INTEGER NOT NULL,
SLL_TELEFONO VARCHAR2( 20 ),
SLL_TECNOLOGIA VARCHAR2( 25 ),
SLL_CODIGO VARCHAR2( 10 ),
SLL_MENSAJE VARCHAR2( 100 ),
SLL_PAQUETE VARCHAR2( 25 ),
SLL_PLAN VARCHAR2( 25 ),
SLL_SALDO VARCHAR2( 10 )
)

-- ******************** TYPE: ******************* --
CREATE OR REPLACE TYPE LLAMADAS_TYPE AS OBJECT(
SLL_TELEFONO_IN VARCHAR2( 20 ),
SLL_TECNOLOGIA_IN VARCHAR2( 25 ),
SLL_CODIGO_IN VARCHAR2( 10 ),
SLL_MENSAJE_IN VARCHAR2( 100 ),
SLL_PAQUETE_IN VARCHAR2( 25 ),
SLL_PLAN_IN VARCHAR2( 25 ),
SLL_SALDO_IN VARCHAR2( 10 ),
CONSTRUCTOR FUNCTION LLAMADAS_TYPE RETURN SELF AS RESULT
);

CREATE OR REPLACE TYPE BODY LLAMADAS_TYPE
AS
CONSTRUCTOR FUNCTION LLAMADAS_TYPE RETURN SELF AS RESULT
AS
BEGIN
RETURN;
END;
END;

-- ******************** PROCEDURE (DENTRO DEL PAQUETE): ******************* --
--SP_OBTENER_SERV_LLAM_TYPE:
PROCEDURE SP_OBTENER_SERV_LLAM_TYPE( LLAMADAS_TYPE_IN IN RGUERRA.LLAMADAS_TYPE,

ERR_OUT OUT VARCHAR2,
MESSAGE_OUT OUT VARCHAR2,
LLAMADAS_TYPE_OUT OUT RGUERRA.LLAMADAS_TYPE
) AS

LLAMADAS_TYPE_TEMP RGUERRA.LLAMADAS_TYPE;
SLL_CODIGO_AUX VARCHAR2( 20 ) := LLAMADAS_TYPE_IN.SLL_CODIGO_IN;

TEMP_01 VARCHAR2( 20 ) := '';
TEMP_02 VARCHAR2( 20 ) := '';
TEMP_03 VARCHAR2( 20 ) := '';
TEMP_04 VARCHAR2( 20 ) := '';
TEMP_05 VARCHAR2( 20 ) := '';
TEMP_06 VARCHAR2( 20 ) := '';
TEMP_07 VARCHAR2( 20 ) := '';

BEGIN
DBMS_OUTPUT.PUT_LINE( '- SLL_CODIGO_AUX: ' || SLL_CODIGO_AUX );
LLAMADAS_TYPE_TEMP := RGUERRA.LLAMADAS_TYPE(); --INICIALIZACION X CONSTRUCTOR.

IF( SLL_CODIGO_AUX IS NOT NULL ) THEN

SELECT X.SLL_TELEFONO,
X.SLL_TECNOLOGIA,
X.SLL_CODIGO,
X.SLL_MENSAJE,
X.SLL_PAQUETE,
X.SLL_PLAN,
X.SLL_SALDO
INTO TEMP_01,
TEMP_02,
TEMP_03,
TEMP_04,
TEMP_05,
TEMP_06,
TEMP_07
FROM RGUERRA.TB_SERV_LLAM X
WHERE X.SLL_ID = SLL_CODIGO_AUX;

--ASIGNACION DE 'TYPE':
LLAMADAS_TYPE_TEMP.SLL_TELEFONO_IN := TEMP_01;
LLAMADAS_TYPE_TEMP.SLL_TECNOLOGIA_IN := TEMP_02;
LLAMADAS_TYPE_TEMP.SLL_CODIGO_IN := TEMP_03;
LLAMADAS_TYPE_TEMP.SLL_MENSAJE_IN := TEMP_04;
LLAMADAS_TYPE_TEMP.SLL_PAQUETE_IN := TEMP_05;
LLAMADAS_TYPE_TEMP.SLL_PLAN_IN := TEMP_06;
LLAMADAS_TYPE_TEMP.SLL_SALDO_IN := TEMP_07;

LLAMADAS_TYPE_OUT := LLAMADAS_TYPE_TEMP;

--SALIDA:
ERR_OUT := '1';
MESSAGE_OUT := 'PROCESO EXITOSO [OBJETO]';
END IF;

EXCEPTION

WHEN OTHERS THEN
ERR_OUT := TO_CHAR( SQLCODE );
MESSAGE_OUT := SUBSTR( UPPER( SQLERRM ), 1, 100 );

END SP_OBTENER_SERV_LLAM_TYPE;

-- ******************** MÉTODO JAVA: ******************* --
/**
* obtenerServicioLlamadasType
* @param objServicioLlamadasType
* @return DataBaseValidatorBean
*/
@Override
public DataBaseValidatorBean obtenerServicioLlamadasType( final ServicioLlamadasTypeBean objServicioLlamadasType ){
this.logger.info( "*********** [INICIO] - DENTRO: [obtenerServicioLlamadasType - DAO] ***********" );

JdbcTemplate objJdbcTemplate = null;
DataBaseValidatorBean objDataBaseValidatorBean = null;
String OWNER = null;
String PAQUETE = null;
String PROCEDURE = null;
long tiempoInicio = System.currentTimeMillis();

try{
this.logger.info( "DATA SOURCE: [" + this.servicioLlamadasJNDI + "]" );
this.servicioLlamadasJNDI.setLoginTimeout( UtilPropertiesMapper.DB_TIMEOUT_CONEXION );

OWNER = UtilPropertiesMapper.DB_OWNER;
PAQUETE = UtilPropertiesMapper.DB_PAQUETE_OBTENER_TYPE;
PROCEDURE = UtilPropertiesMapper.DB_PROCEDURE_OBTENER_TYPE;

this.logger.info( "PROCEDURE: [" + OWNER + "." + PAQUETE + "." + PROCEDURE + "]" );

this.objJdbcCall = new SimpleJdbcCall( this.servicioLlamadasJNDI );
objJdbcTemplate = this.objJdbcCall.getJdbcTemplate();
objJdbcTemplate.setQueryTimeout( UtilPropertiesMapper.DB_TIMEOUT_EXECUTION );

this.objJdbcCall.withSchemaName( OWNER );
this.objJdbcCall.withCatalogName( PAQUETE );
this.objJdbcCall.withProcedureName( PROCEDURE );
this.objJdbcCall.addDeclaredParameter( new SqlParameter( "LLAMADAS_TYPE_IN", OracleTypes.STRUCT, UtilPropertiesMapper.DB_TYPE_LLAMADAS ) );
this.objJdbcCall.addDeclaredParameter( new SqlOutParameter( "ERR_OUT", OracleTypes.VARCHAR ) );
this.objJdbcCall.addDeclaredParameter( new SqlOutParameter( "MESSAGE_OUT", OracleTypes.VARCHAR ) );
this.objJdbcCall.addDeclaredParameter( new SqlOutParameter( "LLAMADAS_TYPE_OUT", OracleTypes.STRUCT, UtilPropertiesMapper.DB_TYPE_LLAMADAS, new SqlReturnType(){
@Override
public Object getTypeValue( CallableStatement cs,
int colIndx,
int sqlType,
String typeName ) throws SQLException{
STRUCT item = (STRUCT)cs.getObject( colIndx );
Object[] arregloOut = item.getAttributes();
ServicioLlamadasTypeBean objLlamadasRESP = new ServicioLlamadasTypeBean();

objLlamadasRESP.setTelefono( (String)arregloOut[ 0 ] );
objLlamadasRESP.setTecnologia( (String)arregloOut[ 1 ] );
objLlamadasRESP.setCodigo( (String)arregloOut[ 2 ] );
objLlamadasRESP.setMensaje( (String)arregloOut[ 3 ] );
objLlamadasRESP.setPaquete( (String)arregloOut[ 4 ] );
objLlamadasRESP.setPlan( (String)arregloOut[ 5 ] );
objLlamadasRESP.setSaldo( (String)arregloOut[ 6 ] );

return objLlamadasRESP;
}
} ) );
this.objJdbcCall.compile();

SqlTypeValue sqlTypeINPUT = new AbstractSqlTypeValue(){
protected Object createTypeValue( Connection conexion,
int sqlType,
String typeName ) throws SQLException{
StructDescriptor itemDescriptor = new StructDescriptor( typeName, conexion );
ServicioLlamadasTypeBean objLlamadas = objServicioLlamadasType;

STRUCT item = new STRUCT( itemDescriptor, conexion, new Object[]{
objLlamadas.getTelefono(),
objLlamadas.getTecnologia(),
objLlamadas.getCodigo(),
objLlamadas.getMensaje(),
objLlamadas.getPaquete(),
objLlamadas.getPlan(),
objLlamadas.getSaldo()
} );

return item;
}
};

Map objMapIN = new HashMap();
objMapIN.put( "LLAMADAS_TYPE_IN", sqlTypeINPUT ); //IN
objMapIN.put( "ERR_OUT", OracleTypes.VARCHAR ); //OUT
objMapIN.put( "MESSAGE_OUT", OracleTypes.VARCHAR ); //OUT
objMapIN.put( "LLAMADAS_TYPE_OUT", OracleTypes.STRUCT ); //OUT
this.objJdbcCall.execute( objMapIN );

//PARAMETROS [OUT]:
Map objMapOUT = this.objJdbcCall.execute( objMapIN );

String vCodigoErr_OUT = (String)objMapOUT.get( "ERR_OUT" );
String vMensajeErr_OUT = (String)objMapOUT.get( "MESSAGE_OUT" );
ServicioLlamadasTypeBean objServicioLlamadasTypeBean = (ServicioLlamadasTypeBean)objMapOUT.get( "LLAMADAS_TYPE_OUT" );

this.logger.info( "ERR_OUT: " + vCodigoErr_OUT );
this.logger.info( "MESSAGE_OUT: " + vMensajeErr_OUT );
this.logger.info( "LLAMADAS_TYPE_OUT: " + objServicioLlamadasTypeBean );

objDataBaseValidatorBean = new DataBaseValidatorBean();
objDataBaseValidatorBean.setCodigo_OUT( vCodigoErr_OUT );
objDataBaseValidatorBean.setMensaje_OUT( vMensajeErr_OUT );
objDataBaseValidatorBean.setObjLlamadasBeanType( objServicioLlamadasTypeBean );
}
catch( SQLException e ){
this.logger.error( "ERROR: [SQLException] - [" + e.getMessage() + "] ", e );

objDataBaseValidatorBean = new DataBaseValidatorBean();
objDataBaseValidatorBean.setCodigo_OUT( UtilPropertiesMapper.ESTADO_NOK );
objDataBaseValidatorBean.setMensaje_OUT( e.getMessage() );
objDataBaseValidatorBean.setListaDatos( null );
}
catch( Exception e ){
this.logger.error( "ERROR: [Exception] - [" + e.getMessage() + "] ", e );

objDataBaseValidatorBean = new DataBaseValidatorBean();
objDataBaseValidatorBean.setCodigo_OUT( UtilPropertiesMapper.ESTADO_NOK );
objDataBaseValidatorBean.setMensaje_OUT( e.getMessage() );
objDataBaseValidatorBean.setCodServLlam( null );
}
catch( Throwable e ){
this.logger.error( "ERROR: [Throwable] - [" + e.getMessage() + "] ", e );

objDataBaseValidatorBean = new DataBaseValidatorBean();
objDataBaseValidatorBean.setCodigo_OUT( UtilPropertiesMapper.ESTADO_NOK );
objDataBaseValidatorBean.setMensaje_OUT( e.getMessage() );
objDataBaseValidatorBean.setCodServLlam( null );
}
finally{
this.logger.info( "Tiempo TOTAL Proceso: [" + (tiempoInicio-System.currentTimeMillis()) + " milisegundos ]" );
this.logger.info( "*********** [FIN] - DENTRO: [registrarServicioLlamadasType - DAO] ***********" );
}

return objDataBaseValidatorBean;
}

El error que me muestra al ejecutar el PROCESO es el siguiente (indica un tipo de dato invalido pero ya valide que todo este bien):

org.springframework.jdbc.BadSqlGrammarException: CallableStatementCallback; bad SQL grammar [{call RGUERRA.PKG_SERVICIO_LLAMADAS.SP_OBTENER_SERV_LLAM_TYPE(?, ?, ?, ?)}]; nested exception is java.sql.SQLSyntaxErrorException: ORA-00902: tipo de dato no válido

at org.springframework.jdbc.support.SQLExceptionSubclassTranslator.doTranslate(SQLExceptionSubclassTranslator.java:94)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:969)
at org.springframework.jdbc.core.JdbcTemplate.call(JdbcTemplate.java:1003)
at org.springframework.jdbc.core.simple.AbstractJdbcCall.executeCallInternal(AbstractJdbcCall.java:391)
at org.springframework.jdbc.core.simple.AbstractJdbcCall.doExecute(AbstractJdbcCall.java:376)
at org.springframework.jdbc.core.simple.SimpleJdbcCall.execute(SimpleJdbcCall.java:177)
at pe.com.javaman.dummy.servicioLlamadasWS.dao.ServicioLlamadasDaoImp.obtenerServicioLlamadasType(ServicioLlamadasDaoImp.java:366)

Si me apoyan con algún dado se los agradecería ya que en Internet no hay mucha info sobre: SimpleJdbcCall con SqlReturnType.

Saludos.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de JavaMan

Cambio de JAR.

He estado usando el API: OJDBC14.jar por si las dudas he cambiado del jar al OJDBC6.jar y ahora el error ha cambiado pero es mas especifico:

SERVER: AdminServer [ INFO] [16-09-2012 04:48:00.216] (ServicioLlamadasDaoImp.java:287) - *********** [INICIO] - DENTRO: [obtenerServicioLlamadasType - DAO] ***********
SERVER: AdminServer [ INFO] [16-09-2012 04:48:00.217] (ServicioLlamadasDaoImp.java:297) - DATA SOURCE: [weblogic.jdbc.common.internal.RmiDataSource@17f1907]
SERVER: AdminServer [ INFO] [16-09-2012 04:48:00.218] (ServicioLlamadasDaoImp.java:304) - PROCEDURE: [RGUERRA.PKG_SERVICIO_LLAMADAS.SP_OBTENER_SERV_LLAM_TYPE]
SERVER: AdminServer [ERROR] [16-09-2012 04:48:01.022] (ServicioLlamadasDaoImp.java:393) - ERROR: [Exception] - [weblogic.jdbc.wrapper.Struct_oracle_sql_STRUCT cannot be cast to oracle.sql.STRUCT]
java.lang.ClassCastException: weblogic.jdbc.wrapper.Struct_oracle_sql_STRUCT cannot be cast to oracle.sql.STRUCT
at pe.com.javaman.dummy.servicioLlamadasWS.dao.ServicioLlamadasDaoImp$2.getTypeValue(ServicioLlamadasDaoImp.java:322)
at org.springframework.jdbc.core.JdbcTemplate.extractOutputParameters(JdbcTemplate.java:1096)
at org.springframework.jdbc.core.JdbcTemplate$5.doInCallableStatement(JdbcTemplate.java:1015)
at org.springframework.jdbc.core.JdbcTemplate$5.doInCallableStatement(JdbcTemplate.java:1)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:953)
at org.springframework.jdbc.core.JdbcTemplate.call(JdbcTemplate.java:1003)
at org.springframework.jdbc.core.simple.AbstractJdbcCall.executeCallInternal(AbstractJdbcCall.java:391)
at org.springframework.jdbc.core.simple.AbstractJdbcCall.doExecute(AbstractJdbcCall.java:376)
at org.springframework.jdbc.core.simple.SimpleJdbcCall.execute(SimpleJdbcCall.java:177)
at pe.com.javaman.dummy.servicioLlamadasWS.dao.ServicioLlamadasDaoImp.obtenerServicioLlamadasType(ServicioLlamadasDaoImp.java:366)
at pe.com.javaman.dummy.servicioLlamadasWS.services.ServicioLlamadasServiceImpl.servicioLlamadas(ServicioLlamadasServiceImpl.java:204)
at pe.com.javaman.dummy.servicioLlamadasWS.ws.ServicioLlamadasPortTypeImpl.servicioLlamadas(ServicioLlamadasPortTypeImpl.java:63)

Imagen de Sr. Negativo

Re: cambio de JAR

Tu excepción es muy específica:
 

Imagen de JavaMan

SOLUCIÓN

En si me base en la documentación de SPRING referente al manejo de SimpleJdbcCall para la interface SqlReturnType que es la encargada del manejo de TYPES para el retorno.

(13.7.4 Handling complex types for stored procedure calls)

Ahí veo que están manejando la clase oracle.sql.STRUCT para la recepcion como para el parsing, pero a mi me genero el problema anterior mencionado.

La SOLUCIÓN aplicada es la siguiente:

1. El cambio de API al OJDBC14.jar por el OJDBC6.jar
2. Obtener la implementación del vendor en el struct: java.sql.Struct struct = weblogic.jdbc.vendor.oracle.OracleStruct)(rs.getObject(2));

Aquí la parte modificada:

this.objJdbcCall.withSchemaName( OWNER );
this.objJdbcCall.withCatalogName( PAQUETE );
this.objJdbcCall.withProcedureName( PROCEDURE );
this.objJdbcCall.addDeclaredParameter( new SqlParameter( "LLAMADAS_TYPE_IN", Types.STRUCT, UtilPropertiesMapper.DB_TYPE_LLAMADAS ) );
this.objJdbcCall.addDeclaredParameter( new SqlOutParameter( "ERR_OUT", Types.VARCHAR ) );
this.objJdbcCall.addDeclaredParameter( new SqlOutParameter( "MESSAGE_OUT", Types.VARCHAR ) );
this.objJdbcCall.addDeclaredParameter( new SqlOutParameter( "LLAMADAS_TYPE_OUT", Types.STRUCT, UtilPropertiesMapper.DB_TYPE_LLAMADAS, new SqlReturnType(){
@Override
public Object getTypeValue( CallableStatement cs,
int colIndx,
int sqlType,
String typeName ) throws SQLException{
Struct item = ((OracleStruct)cs.getObject( colIndx ) );
Object[] arregloOut = item.getAttributes();
ServicioLlamadasTypeBean objLlamadasRESP = new ServicioLlamadasTypeBean();

objLlamadasRESP.setTelefono( (String)arregloOut[ 0 ] );
objLlamadasRESP.setTecnologia( (String)arregloOut[ 1 ] );
objLlamadasRESP.setCodigo( (String)arregloOut[ 2 ] );
objLlamadasRESP.setMensaje( (String)arregloOut[ 3 ] );
objLlamadasRESP.setPaquete( (String)arregloOut[ 4 ] );
objLlamadasRESP.setPlan( (String)arregloOut[ 5 ] );
objLlamadasRESP.setSaldo( (String)arregloOut[ 6 ] );

return objLlamadasRESP;
}
} ) );
this.objJdbcCall.compile();

SALIDA DE LOG:

SERVER: AdminServer [ INFO] [16-09-2012 16:37:59.061] (ServicioLlamadasDaoImp.java:287) - *********** [INICIO] - DENTRO: [obtenerServicioLlamadasType - DAO] ***********
SERVER: AdminServer [ INFO] [16-09-2012 16:37:59.061] (ServicioLlamadasDaoImp.java:297) - DATA SOURCE: [weblogic.jdbc.common.internal.RmiDataSource@1655fb9]
SERVER: AdminServer [ INFO] [16-09-2012 16:37:59.062] (ServicioLlamadasDaoImp.java:304) - PROCEDURE: [RGUERRA.PKG_SERVICIO_LLAMADAS.SP_OBTENER_SERV_LLAM_TYPE]
SERVER: AdminServer [ INFO] [16-09-2012 16:37:59.317] (ServicioLlamadasDaoImp.java:373) - ERR_OUT: 1
SERVER: AdminServer [ INFO] [16-09-2012 16:37:59.318] (ServicioLlamadasDaoImp.java:374) - MESSAGE_OUT: PROCESO EXITOSO [OBJETO]
SERVER: AdminServer [ INFO] [16-09-2012 16:37:59.319] (ServicioLlamadasDaoImp.java:375) - LLAMADAS_TYPE_OUT: ServicioLlamadasTypeBean [telefono=5214956, tecnologia=PREPAGO, codigo=0, mensaje=OK, paquete=PAQUETON, plan=FAMILIA PREPAGO, saldo=5000]
SERVER: AdminServer [ INFO] [16-09-2012 16:37:59.320] (ServicioLlamadasDaoImp.java:407) - Tiempo TOTAL Proceso: [-259 milisegundos ]
SERVER: AdminServer [ INFO] [16-09-2012 16:37:59.320] (ServicioLlamadasDaoImp.java:408) - *********** [FIN] - DENTRO: [registrarServicioLlamadasType - DAO] **********

Gracias.