I am using JdbcTemplate to make a call to a stored procedure in as400. I know that the schema and procedure exist and I'm using the correct spelling etc.
The first time the application starts the call works as expected and gives no error. Subsequent calls fail with this error:
org.springframework.jdbc.BadSqlGrammarException: CallableStatementCallback; bad SQL grammar [{call PGM999.STOREDPROCNAME(?, ?, ?, ?, ?, ?, ?)}]; nested exception is com.ibm.as400.access.AS400JDBCSQLSyntaxErrorException: [SQL0204] NEWSP in RGRPGM type *N not found.
Here is some snippets of relevant code in an #Service class:
private ConcurrentMap<String, SimpleJdbcCall> storedProcedureCallsMap = new ConcurrentHashMap<String, SimpleJdbcCall>();
public Map<String, Object> executeStoredProcedure(String procedureName, MapSqlParameterSource paramSource, String libraryName) {
return executeStoredProcedureCall(procedureName, paramSource, createCallForStoredProcedure(procedureName, libraryName));
}
private CallCreator createCallForStoredProcedure(String procedureName, String libraryName) {
return new CallCreator() {
#Override
public SimpleJdbcCall createCall() {
return new SimpleJdbcCall(as400JdbcTemplate).withSchemaName(libraryName).withProcedureName(procedureName);
}
};
}
//
SimpleJdbcCall call = storedProcedureCallsMap.get(procedureName);
call = callCreator.createCall();
call.compile();
Map<String, Object> returnMap = call.execute(paramsSource);
What's odd is that when I call one of: Connection.commit() or Connection.endRequest() or Connection.setAutoCommit(true) it works every time, however, in all of these it locks up the connections in the thread pool, and even calling Connection.close() doesn't close them, and they stay active until there's no idle connections left to use and it locks the whole thing up.
So I either need to fix the 1st issue with it working every time without telling it to commit etc. or the second issue where I do call one of these but successfully force it to release the connections.
It turns out the the stored procedure in question was calling another stored procedure that hadn't been copied over to the schema that the main procedure was using. I still don't know why it would work the first time or when I told it to commit it would work every time. Either way copying over the missing procedure to the same schema seemed to solve the issue.
Related
I was running a stored procedure with Springs SimpleJdbcCall like this:
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate).withProcedureName("example_proc");
jdbcCall.execute();
// do other stuff on other subsystems
// the sysout below is just an example - the real scenario was somewhat different
System.out.println("Supposedly after the end of the stored procedure call");
The stored procedure was running for a long time, and it was overlapped with the stuff that was supposed to happen after that.
The stored procedure was written in Microsoft's SQL Server dialect, and looked like this:
CREATE PROCEDURE example_proc
AS
BEGIN
INSERT INTO example_table_1 SELECT * FROM example_table_2
UPDATE example_table_1 SET col1 = 'a' WHERE ...
END
The question is: how to make sure the SimpleJdbcCall waits until the stored procedure finishes?
There is a hack for this: make the stored procedure return something, and then retrieve it in the jdbc call.
The is the stored procedure:
CREATE PROCEDURE example_proc
AS
BEGIN
INSERT INTO example_table_1 SELECT * FROM example_table_2
UPDATE example_table_1 SET col1 = 'a' WHERE ...
-- this is just a hack for running it synchronously:
SELECT 1 AS success
END
Now that it returns something, the jdbc call can wait for that:
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("example_proc").
returningResultSet("success", new SingleColumnRowMapper<Integer>());
Map<String, Object> map = jdbcCall.execute();
#SuppressWarnings("unchecked")
List<Integer> storedProcedureResults = (List<Integer>) map.get(success);
int result = storedProcedureResults.get(0);
// I did something to the result. I am not sure if this is really necessary.
// But I was worried if the jvm or javac would optimize the dead code.
// I returned the value from a method. Printing it should also be enough.
System.out.println(result);
I am working on an application using Java, Eclipse, and Spring. We have an Oracle database that we are connecting to using JDBC.
Currently the application is using adhoc queries in the application to the database. Most of these were done by people working on the project before I came along. I have decided that using stored procedures is a better way of going. Decouple for another layer of abstraction. Not having to send the sql statement every time so less bandwidth and faster transactions. Oracle can optimize them unlike with the adhoc ones. Changes to them can occur without needing to be recompiled as long as inputs and outputs dont change. All that wonderful stuff.
The adhoc queries frequently get back multiple rows and are using the interface rowMapper and mapRow
return jdbcTemplate.query(sql, new adhoc_mapper1());
class adhoc_mapper1 implements RowMapper<List<String>> {
public List<String> mapRow(ResultSet rs, int arg1) throws SQLException{
ArrayList<String> arr1 = new ArrayList<String>();
arr1.add(rs.getString("OUT_POSITION_ID"));
arr1.add(rs.getString("OUT_POSITION_TITLE_EN"));
return arr1;
}
}
Adhoc Query in Spring
SELECT HR.POSITION_ID, HR.POSITION_TITLE_EN, HR.POSITION_TITLE_FR, HR.SECURITY_ID, HR.GROUP_NAME, HR.GROUP_LEVEL, HR.POSITION_IS_ACTIVE
FROM HR_POSITION HR JOIN DRILL_POSITION DP ON (HR.POSITION_ID = DP.POSITION_ID)
WHERE DP.TYPEVALUE = RECORD_TYPE;
Called Procedure in Oracle
CREATE OR REPLACE PROCEDURE DOCSADM.DRILL_RECORD_POSITION (
RECORD_TYPE IN VARCHAR2,
OUT_POSITION_ID OUT VARCHAR2,
OUT_POSITION_TITLE_EN OUT VARCHAR2,
OUT_POSITION_TITLE_FR OUT VARCHAR2,
OUT_SECURITY_ID OUT VARCHAR2,
OUT_GROUP_NAME OUT VARCHAR2,
OUT_GROUP_LEVEL OUT VARCHAR2,
OUT_POSITION_IS_ACTIVE OUT VARCHAR2
) AS
BEGIN
SELECT HR.POSITION_ID, HR.POSITION_TITLE_EN, HR.POSITION_TITLE_FR, HR.SECURITY_ID, HR.GROUP_NAME, HR.GROUP_LEVEL, HR.POSITION_IS_ACTIVE
INTO OUT_POSITION_ID, OUT_POSITION_TITLE_EN, OUT_POSITION_TITLE_FR, OUT_SECURITY_ID, OUT_GROUP_NAME, OUT_GROUP_LEVEL, OUT_POSITION_IS_ACTIVE
FROM HR_POSITION HR JOIN DRILL_POSITION DP ON (HR.POSITION_ID = DP.POSITION_ID) WHERE DP.TYPEVALUE = RECORD_TYPE;
END DRILL_RECORD_POSITION;
As you can see, the procedure returns multiple rows. I had asked a question about how to view the output from the procedure in Oracle but was not successful.
As the project is not using called procedures I have no examples in the code base to work back from. One of my coworkers involved in a different project has used them and was kind enough to show me their example, which regrettably was not helpful because it only called a procedure and had no returns. Their overall design also appears different. But I saw that they were using SimpleJdbcCall so I started looking online to use that.
Good examples online that I failed to get working.
I found examples that were doing what I needed to do, but not quite how I was expecting.
This example goes along the lines of what I was expecting to see and use, except that it only takes a single row as the result and I wasn't able to figure out how to alter the mapping to accept multiple rows.
This example however does use a procedure that returns multiple rows, but it uses ParameterizedBeanPropertyRowMapper and passes in a class.
This example has several ways of doing these calls but are all single row returns, but the answers comments do suggest that its easy to expand for multiple rows. But again I was unable to get it working.
I am not wanting to create a class for every procedure or family of procedures. Most of the queries end up displaying the information in a table, so I have been using a 2d object to hold and then display the data. It has been very convenient so far. I am fine with creating a mapping for each procedure as it needs to be done (unless there is a better way?), but I do not want to create a class for every procedure (and sometimes the mapper as well).
I have 150 lines of failed code attempts which I have not included to keep the question shorter. If they would help then I can include them.
TL;DR
I want to call a stored procedure in Oracle from Spring which has multiple rows being returned. I would like to use just the calling function, which either uses the RowMapper method of mapping, or an in function mapping. I want to avoid using class structures of data if possible. I am expecting/hoping it to look and get used like the first code block.
There is a basic error with my methodology for this. The way in which I was attempting to return multiple rows is wrong. As pointed out by someone in my linked question. So the ways that I was attempting to access it were also wrong. When I limited the return to a single row the following was successful.
First the imports
import java.sql.Connection;
import java.sql.CallableStatement;
Then the call
final String procedureCall = "{call DRILL_RECORD_POSITION(?, ?, ?, ?, ?, ?, ?, ?)}";
try (Connection connection = jdbcTemplate.getDataSource().getConnection();)
{
ArrayList<String> inner = new ArrayList<String>();
CallableStatement callableSt = connection.prepareCall(procedureCall);
callableSt.setString(1, "D");
callableSt.registerOutParameter(2, Types.VARCHAR);
callableSt.registerOutParameter(3, Types.VARCHAR);
callableSt.registerOutParameter(4, Types.VARCHAR);
callableSt.registerOutParameter(5, Types.VARCHAR);
callableSt.registerOutParameter(6, Types.VARCHAR);
callableSt.registerOutParameter(7, Types.VARCHAR);
callableSt.registerOutParameter(8, Types.VARCHAR);
//Call Stored Procedure
callableSt.executeUpdate();
for (int i=2; i<9; i++)
{
System.out.println(callableSt.getString(i));
}
}catch (SQLException e) {
e.printStackTrace();
}
Note that I am using the try with resources so I do not have to worry about closing the connection.
Other mistakes I made during this was not having enough ? in the String which was leading to invalid column index errors. I then made the mistake of trying to get the information before I actually executed the call.
SQL state [99999]; error code [17004]; Invalid column type; nested
exception is java.sql.SQLException: Invalid column
type\",\"error\":\"UncategorizedSQLException\"}"}
My case:
In our DB one user will have one Id but this one Id can can have multiple values. for example I have one userId, for my userID I have 10 health records. so I have to delete these 10 health records in one shot. so I'm passing userId and List for values(data type is Number). raw query works but when I go through java code it is giving Invalid column exception. any suggestions?
Java Implementation method is to make this call is
#Override
public void deleteSampleValue(BiometricPkDTO biometricPkDTO){
update(deleteSampleValueSql,log,biometricPkDTO.getSeriesPk(),biometricPkDTO.getSamplePks());
}
and the SQL query I added in resource folder is
delete from bio_sample
where BIO_SERIES_PK = ?
and BIO_SAMPLE_PK in (?)
Thanks.
As I'm performing multiple deletes action for one specific Id, it is suggested to use NamedParameterJdbcTemplate(getJdbcTemplate().getDataSource()). Instantiating MapSqlParameterSource object and by using the reference variable pass the inputs to sql query. The final Dao implementation method looks like:
#Override
public void deleteSample(BiometricPkDTO biometricPkDTO){
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
parameterSource.addValue("seriesPk",biometricPkDTO.getSeriesPk());
parameterSource.addValue("samplePks", biometricPkDTO.getSamplePks());
namedParameterJdbcTemplate.update(deleteSampleSql,parameterSource);
}
I'm developing a Spring + Hibernate application and everything is working pretty fine. Making a method I found out a strange behaviour that I can't really explain, so I'll show you what I got and maybe we'll find a solution.
This method retrieves a list of soccer players parsing a web page and I try to find if I already have a player with the same name already on the database; if I already have it, I set some parameters and update that object. If I have no player with that name I want to insert it. I obviously can't use the saveOrUpdate method as my parsed objects have no id as I didn't retrieve them from the db.
This is the code snippet that generates the error (it's in the Service layer, then declared as Transactional):
List<Calciatore> calciatoriAggiornati = PopolaDbCalciatori.getListaCalciatori(imagesPath);
for(Calciatore calciatore: calciatoriAggiornati){
Calciatore current = calciatoreDao.getCalciatoreByNome(calciatore.getNome());
if( current != null){
current.setAttivo(true);
current.setRuolo(calciatore.getRuolo());
current.setUrlFigurina(calciatore.getUrlFigurina());
current.setSquadraReale(calciatore.getSquadraReale());
calciatoreDao.update(current);
}
else{
calciatore.setAttivo(true);
calciatoreDao.insert(calciatore);
}
}
return true;
}
The getCalciatoreByName method is the following (it's working if used alone):
public Calciatore getCalciatoreByNome(String nomeCalciatore) {
List<Calciatore> calciatori = getSession().createCriteria(Calciatore.class)
.add(Restrictions.eq("nome",nomeCalciatore)).list();
return calciatori.size() == 0? null : calciatori.get(0);
}
The insert method, inherited by the class BaseDaoImpl works when used standalone too, and is the following:
public Boolean insert(T obj) throws DataAccessException {
getSession().save(obj);
return true;
}
The result is strange: the first object of the list passes the method getCalciatoreByNome without problem; as I have no instances on the database, the flow goes to the insert. After the first round of the for is over, this is the console:
Hibernate:
select
this_.kid as kid1_0_3_,
this_.attivo as attivo2_0_3_,
this_.dataDiNascita as dataDiNa3_0_3_,
this_.nome as nome4_0_3_,
this_.ruolo as ruolo5_0_3_,
this_.squadraCorrente_kid as squadraC9_0_3_,
this_.squadraReale as squadraR6_0_3_,
this_.urlFigurina as urlFigur7_0_3_,
this_.version as version8_0_3_,
squadrafan2_.kid as kid1_7_0_,
squadrafan2_.attiva as attiva2_7_0_,
squadrafan2_.nome as nome3_7_0_,
squadrafan2_.utenteAssociato_kid as utenteAs5_7_0_,
squadrafan2_.version as version4_7_0_,
utente3_.kid as kid1_10_1_,
utente3_.attivo as attivo2_10_1_,
utente3_.hashPwd as hashPwd3_10_1_,
utente3_.ruolo_kid as ruolo_ki6_10_1_,
utente3_.username as username4_10_1_,
utente3_.version as version5_10_1_,
ruolo4_.kid as kid1_5_2_,
ruolo4_.nome as nome2_5_2_,
ruolo4_.version as version3_5_2_
from
Calciatore this_
left outer join
SquadraFantacalcio squadrafan2_
on this_.squadraCorrente_kid=squadrafan2_.kid
left outer join
Utente utente3_
on squadrafan2_.utenteAssociato_kid=utente3_.kid
left outer join
Ruolo ruolo4_
on utente3_.ruolo_kid=ruolo4_.kid
where
this_.nome=?
Hibernate:
call next value for SEQ_CALCIATORE
As you can see no exception is raised but the behaviour is already compromised, as no insert is really executed! Last line of log show only the sequence generator!
On the second round of the for cycle, as the flow approaches the getCalciatoreByNome method, this is the console log:
Hibernate:
insert
into
Calciatore
(attivo, dataDiNascita, nome, ruolo, squadraCorrente_kid, squadraReale, urlFigurina, version, kid)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?)
24/06/2015 09:03:27 - INFO - (AbstractBatchImpl.java:208) - HHH000010: On release of batch it still contained JDBC statements
24/06/2015 09:03:27 - WARN - (SqlExceptionHelper.java:144) - SQL Error: -5563, SQLState: 42563
24/06/2015 09:03:27 - ERROR - (SqlExceptionHelper.java:146) - incompatible data type in operation
24/06/2015 09:03:39 - DEBUG - (AbstractPlatformTransactionManager.java:847) - Initiating transaction rollback
Wow, that's strange. As I try to execute the second time the select method, Hibernate tries to make the insert generating that error that I can't really find anywhere, and the rollback\exception generation is started.
I tried to debug as much as I could, but I can't really understand what's going on, as when I execute these operation as standalone everything seems to work fine.
Any suggestion?
When you use AUTO flushing, the current pending changes are flushed when:
the transaction commits
a query is executed
When you issue the insert, Hibernate only add an EntityInsertAction in the action queue, but it delays the INSERT until flush time.
The reason you see the insert executed on the second iteration cycle is because the select query triggers a flush.
How can I get SQL query that was received by MySQL server when it raises exception?
I have a code similar to the following:
Connection con = null;
PreparedStatement ps = null;
try {
con = DbConnectionManager.getConnection();
ps = con.prepareStatement(query);
setStatementParameters(ps, params);
ps.executeUpdate();
} catch (SQLExeption e) {
// How to get wrong query here?
} finally {
DbConnectionManager.closeConnection(ps, con);
}
Where query variable is like "INSERT into someTable (qwe, asd) VALUES (?, ?)"
The question is how can I get query string in the catch block?
The SQLException may or may not have the query string contained in its exception message. You can't depend on it. If you just want to see it for debugging purposes, though, then that's probably your best bet. However, in this particular example that's not a problem because you have direct access to the query variable that you used to set up the query in the first place.
I've run across another solution to this problem.
The MySQL JDBC driver overrides the toString of PreparedStatement to display the query as it is sent to the database. This is implementation dependent so it may not the best thing to rely on, but it's very simple to get at. I'm now using this to dump query text to a log file for debugging purposes. While there are probably other solutions that are more portable and future-proof, this has the advantage of getting exactly the string that the MySQL driver says it's sending to the database.
The string comes back with an object ID, then a colon, then the SQL string. You can split it on the colon to get just the SQL.
The type com.mysql.jdbc.PreparedStatement also exposes a protected method call asSql(). You could override the class with your own implementation that gives public access to this method. From looking at the disassembly of the class's toString() method, it seems to be using asSql() to get the actual SQL string. This approach adds the problem of how to instantiate your subclass, though; the simplest approach is just to use the toString that you already have access to, without even having to downcast your PreparedStatement to a MySQL-specific subtype.
Again, just be aware that the maintainers of the MySQL API probably don't consider this part of the public interface to their software (JDBC defines the standard interface), so they may make changes later that would break this mechanism. For the time being, though, it will get the job done.
This is true for the version of the MySQL driver I'm currently using, which is 5.1.7.