Insert byte[] into blob field with spring's jdbcTemplate and stored procedure - spring

I'm try to insert byte[] into blob field with stored procedure, and get an Exception :
Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT ID FROM sp_NEWFILE(?,?,?)]; nested exception is org.firebirdsql.jdbc.field.TypeConversionException: Error converting to object.
Model:
public class fileBody {
private int ID;
private byte[] BODY;
private String FILENAME; //getters an setters}
Insert it to database
public class FileBodyDaoImpl implements FileBodyDao {
public int insertData(final FileBody fileBody) throws IOException {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
LobHandler lobHandler = new DefaultLobHandler();
final InputStream in = new ByteArrayInputStream(fileBody.getBODY());
final int fileSize = in.available();
Map<String, Object> out = jdbcTemplate.queryForMap("SELECT ID FROM sp_NEWFILE(?,?,?)",
new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException,
DataAccessException {
ps.setString(1, fileBody.getFILENAME());
lobCreator.setBlobAsBinaryStream(ps, 2, in, fileSize);
ps.setNull(3, java.sql.Types.INTEGER);
}
});
int last_inserted = Integer.parseInt(String.valueOf(out.get("ID")));
return last_inserted;
}
And my stored procedure
create or alter procedure sp_NEWFILE (
FILENAME varchar(255),
BODY blob sub_type 0 segment size 80,
USEID integer)
returns (
ID integer)
as
begin
if (useid is not null) then ID=USEID;
else ID=GEN_ID(gen_filebody_id,1);
if ((FILENAME is NULL) or (FILENAME='')) then FILENAME='UNDEFINED';
INSERT INTO t_filebody(ID,BODY,FILENAME) VALUES(:ID,:BODY,:FILENAME);
suspend;
end^
and i get an Exception:
Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException:
PreparedStatementCallback;
bad SQL grammar [SELECT ID FROM sp_NEWFILE(?,?,?)]; nested exception is org.firebirdsql.jdbc.field.TypeConversionException: Error converting to object.
Versions:
jaybird-jdk17-2.2.5;
Source: firebird2.5 Version: 2.5.1.26351.ds4-2ubuntu0.1;

The problem is that queryForMap does not support a PreparedStatementCallback (contrary to for example execute), instead your anonymous object is considered a normal parameter for the query to execute, and Jaybird does not support this object type. And if Jaybird had supported it, you would have received an error for missing parameters 2 and 3.
Your code can be greatly simplified by passing the byte array:
Map<String, Object> out = jdbcTemplate.queryForMap("SELECT ID FROM sp_NEWFILE(?,?,?)",
fileBody.getFILENAME(), fileBody.getBODY(), null);
This works as Jaybird considers a BLOB SUB_TYPE 0 as a java.sql.Types.LONGVARBINARY and JDBC 4.2 appendix B declares that byte[] is the default type for that (although you can also use it as a java.sql.Types.BLOB).
As a side note, your stored procedure does not need to be selectable (removing SUSPEND makes it executable), and the procedure could also be replaced by using a TRIGGER to generate the primary key and retrieving the value either by using INSERT .. RETURNING .. or through the JDBC generated keys facility (which in turn is implemented in Jaybird through INSERT .. RETURNING ..).

For those seeking non-Jaybird solution to insert BLOB using spring jdbctemplate, the following syntax worked for me to use stored procedures which is different compared to insert via queries.
Insert via Queries
ByteArrayInputStream inputStream = new ByteArrayInputStream(file.getBytes());
ps.setBlob(1, inputStream);
Insert via Stored Procedure Call
Map<String, Object> inParams = new HashMap<>();
inParams.put("pi_some_id", id);
inParams.put("pi_file_blob", new SqlLobValue(file.getBytes()));
SqlParameterSource sqlParameterSource = new MapSqlParameterSource(inParams);
SqlParameter[] sqlParameters = {
new SqlParameter("pi_some_id", Types.VARCHAR),
new SqlParameter("pi_file_blob", Types.BLOB),
new SqlOutParameter("po_error_flag", Types.VARCHAR),
new SqlOutParameter("po_message", Types.VARCHAR)};
SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate).withoutProcedureColumnMetaDataAccess().
withProcedureName(storedProcName).withCatalogName(packageName).
declareParameters(sqlParameters);
Map<String, Object> storedProcResult = simpleJdbcCall.execute(sqlParameterSource);

Related

Using H2 alias for mocking stored procedure which has output parameters. Is this even possible with H2?

I have created an H2 alias function which is being called when I execute the corresponding spring jdbc StoredProcedure.
public static ResultSet dao_cashflow_read(Integer contractId, Integer contractVersion, Date rateDtmDate, String refSrc,
String rateStatus, String rateType, String message, Integer rowCount) throws SQLException, IOException {
File f = new File(FixingDaoIT.class.getResource("/").getFile().concat("csv/ldn/fixing.csv"));
return new Csv().read(new FileReader(f), null);
}
This part is working as expected as the associated row mapper function parses the result set correctly and generates the entity objects. The problem is, the JdbcTemplate class then attempts to extract the associated output parameters that were registered with the StoredProcedure implementation. Which in turn calls the method below in class JdbcCallableStatement.java
private JdbcResultSet getOpenResultSet() throws SQLException {
try {
checkClosed();
if (resultSet == null) {
throw DbException.get(ErrorCode.NO_DATA_AVAILABLE);
}
if (resultSet.isBeforeFirst()) {
resultSet.next();
}
return resultSet;
} catch (Exception e) {
throw logAndConvert(e);
}
}
This call fails as the resultSet reference is now null and it throws the DbException NO_DATA_AVAILBLE
If I run the application against Sybase, the output params are as expected. Is this an issue with H2 not supporting output params?

Used "SimpleJdbcCall" to call stored procedure encountered Error java.lang.ClassCastException :weblogic .jdbc.wrapper.array_oracle_sql_Array

Oracle Types : User Defined Datatypes
create or replace type id_array_input VARRAY(20) OF NUMBER
create or replace type output_data_array VARRAY(20) OF VARCHAR(25)
Procedure
Stored Procedure to be invoked
CREATE OR REPLACE PROCEDURE TESTPROC(id_array in id_array_input,output_data out output_data_array)
as
declare
doc_name varchar2(55);
begin
output_data:=output_data_array();
for i in 1..id_array.count loop
select document_name into doc_name from document_details where doc_id =id_array(i);
output_data.extend;
output_data(i):=doc_name;
end loop;
end;
end TESTPROC;
Spring Part: Request Mapping containing call to procedure with In as an array of integers and out as an array of string
#Autowired
private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall proceCall;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
#RequestMapping(value="/procedure",method=RequestMethod.GET,produces=MediaType.APPLICATION_JSON_UTF8_VALUE)
public #ResponseBody Object getProcedure(){
List<Integer> aa_array=new ArrayList<Integer>();
aa_array.add(10);
aa_array.add(20);
aa_array.add(30);
Integer[] int_array=new Integer[aa_array.size()];
int_array=aa_array.toArray(int_array);
proceCall=new SimpleJdbcCall(jdbcTemplate).withProcedureName("TESTPROC")
.declareParameters(new SqlParameter("id_array",OracleTypes.ARRAY,"id_array_input"),
new SqlOutParameter("output_data",Types.ARRAY,"output_data_array" ,new SqlReturnArray()));
Map in = Collections.singletonMap("id_array",new SqlArrayValue(int_array) );
Map<String,Object> st_array=proceCall.execute(String[].class,in);
return st_array;
}
In your example, exception is coming because of new SqlReturnArray(). Refer below code. This will work.
procedureCall=new SimpleJdbcCall(jdbctemplate.getDataSource()).withProcedureName("TESTPROC")
.declareParameters( new SqlParameter("id_array",OracleTypes.ARRAY, "COMPANY_COMMITTEE_ID_ARRAY")
,new SqlOutParameter("output_data",Types.ARRAY, "STRING_ARRAY"));
aa_array=alpha.toArray(aa_array);
Map in =Collections.singletonMap("id_array",new SqlArrayValue(aa_array));
Array hi= procedureCall.executeFunction(Array.class,in);
String[] hh=(String[]) hi.getArray();
You can also make it work by disabling the "Wrap Data Types" option in the WebLogic Administration Console under Services->Data Sources->Connection Pool->Advanced:

Spring JdbcTemplate and oracle arrayofnumber

I'm using Spring and Oracle database in my solution and i need to execute script
select count(1) from ELEMENTS, table(cast(? as arrayofnumbers)) session_ids
where root_session_id in session_ids.VALUE
but i have a problem with passing input parameter.
i try to pass List or array of BigInteger into
JdbcTemplate.queryForObject("select count(1) from ELEMENTS, table(cast(? as arrayofnumbers)) session_ids
where root_session_id in session_ids.VALUE", Integer.class, INPUT_PARAMS)
but has an Exception:
java.sql.SQLException: Invalid column type
at oracle.jdbc.driver.OraclePreparedStatement.setObjectCritical(OraclePreparedStatement.java:8861)
at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:8338)
at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:9116)
at oracle.jdbc.driver.OraclePreparedStatement.setObject(OraclePreparedStatement.java:9093)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.setObject(OraclePreparedStatementWrapper.java:234)
at weblogic.jdbc.wrapper.PreparedStatement.setObject(PreparedStatement.java:357)
Does anyone have the same problem?
EDIT:
Forget to describe arrayofnumber. It's custom type:
TYPE arrayofnumbers as table of number(20)
Found the solution:
final BigInteger[] ids = new BigInteger[]{BigInteger.valueOf(9137797712513092132L)};
int count = jdbc.query("select count(1) from NC_DATAFLOW_ELEMENTS\n" +
" where root_session_id in (select /*+ cardinality(t 10) */ * from table(cast (? as arrayofnumbers)) t)"
, new PreparedStatementSetter() {
public void setValues(PreparedStatement preparedStatement) throws SQLException {
Connection conn = preparedStatement.getConnection();
OracleConnection oraConn = conn.unwrap(OracleConnection.class);
oracle.sql.ARRAY widgets = oraConn.createARRAY("ARRAYOFNUMBERS", ids);
preparedStatement.setArray(1, widgets);
}
}, new ResultSetExtractor<Integer>() {
public Integer extractData(ResultSet resultSet) throws SQLException, DataAccessException {
resultSet.next();
return resultSet.getInt(1);
}
});
out.println(count);
should note that type of array (ARRAYOFNUMBER) should be in upper case

Retrieve values fom database using JDBC Template in hashmap

i am using JDBC template for getting data from database in Spring MVC.
my query is:
SELECT count(A.MEETING_ID),ITEM_TBL.REG_EMAIL FROM ITEM_TBL,MEETINGS_TBL WHERE ITEM_TBL.MEETING_ID=MEETINGS_TBL.MEETING_ID
GROUP BY ITEM_TBL.REG_EMAIL
this is returning rows like:
11 nishant#gmail.com
12 abhilasha#yahoo.com
13 shiwani#in.com
i want to store these value into Hash MAP. Can you please help how can i do this using JDBC TEMPLATE?
Thanks
You need ResultExtractor.
You can achieve that using below code.
String sql = "SELECT count(A.MEETING_ID),ITEM_TBL.REG_EMAIL FROM ITEM_TBL,MEETINGS_TBL WHERE ITEM_TBL.MEETING_ID=MEETINGS_TBL.MEETING_ID
GROUP BY ITEM_TBL.REG_EMAIL";
ResultExtractor mapExtractor = new ResultSetExtractor() {
public Object extractData(ResultSet rs) throws SQLException {
Map<String, String> mapOfKeys = new HashMap<String, String>();
while (rs.next()) {
String key = rs.getString("MEETING_ID");
String obj = rs.getString("REG_EMAIL");
/* set the business object from the resultset */
mapOfKeys.put(key, obj);
}
return mapOfKeys;
}
};
Map map = (HashMap) jdbcTemplate.query(sql.toString(), mapExtractor);

How to catch the exception of the execute method while using Spring jdbcTemplate

I use the Spring JDBCTemplate to call a stored procedure ,like this
Map receive10PrmtBill = (Map) getJdbcTemplate().execute(sql, new CallableStatementCallback() {
#Override
public Object doInCallableStatement(CallableStatement cs) throws SQLException,
DataAccessException {
cs.setString(1, Constants.ADD_PROC_TYPE);
Map<String,PrmtBillInfoDatagram> tempMap = new HashMap();
cs.execute();
but the execute method return false and didn't throw any exception ,so I don't know what's wrong with my program , how to catch the Exception? Any help?
This is the proc
create proc sp_xx ( #userid int)
as
begin
select personid, personname from person where personid = #userid
select teamid, teamname from team
end
Try setting "IGNORE_DONE_IN_PROC" property to "true". See at:
http://javabob64.wordpress.com/2011/04/12/sybase-and-the-jdbc-driver/

Resources