Oracle Stored Procedures using Spring JDBC - sql

Hi I am trying to execute stored procedures using Spring JDBC.
Here's the SP class
class IncrementExtraBalanceStoredProcedure extends StoredProcedure {
/**
* #param jdbcTemplate
* #param procedureName
*/
public IncrementExtraBalanceStoredProcedure(JdbcTemplate jdbcTemplate, String procedureName) {
super(jdbcTemplate, procedureName);
declareParameter(new SqlOutParameter(O_RETURN_CODE, Types.INTEGER));
declareParameter(new SqlParameter(P_NUMEC, Types.INTEGER));
declareParameter(new SqlParameter(P_GBYTES, Types.INTEGER));
compile();
}
/**
* #param inputBean
* #return resultObjects
*/
public Map<String, Object> execute(RateLimitLogBean inputBean) {
Map<String,Object> sqlMap = new HashMap<String,Object>();
sqlMap.put(P_NUMEC, inputBean.getNumec());
sqlMap.put(P_GBYTES, inputBean.getGb());
return super.execute(sqlMap);
}
}
I am calling this class from this method.
public int incrementExtraBalance(RateLimitLogBean inputBean) {
IncrementExtraBalanceStoredProcedure procedure = new IncrementExtraBalanceStoredProcedure(this.jdbcTemplate, "RATELIMIT_OWN.increment_extra_balance");
Map<String, Object> resultMap = procedure.execute(inputBean);
if (!StringUtils.isEmpty(resultMap)) {
return ((Integer) resultMap.get(O_RETURN_CODE)).intValue();
}
return -1;
}
But I am getting null value as O_RETURN_CODE. It's supposed to return 0
The execution of this function from Toad - Oracle Db
var z number
exec RATELIMIT_OWN.unlimit_contract (0123,:z)
print z
I got 0 as output in Toad.
Why I am getting null value as return from Java code (no sql exceptions).
Is there anything wrong with code?
native calls returning proper output
public void unlimitContract(RateLimitLogBean inputBean, boolean load) throws SQLException {
String sql = "{call RATELIMIT_OWN.unlimit_contract (?,?)}";
CallableStatement callableStatement = this.dataSource.getConnection().prepareCall(sql);
callableStatement.setInt(1, 0123);
callableStatement.registerOutParameter(2, java.sql.Types.INTEGER);
callableStatement.executeUpdate();
int resultCode = callableStatement.getInt(2);
}
SQL SP
CREATE OR REPLACE PROCEDURE RATELIMIT_OWN.increment_extra_balance (p_numec IN NUMBER,
p_gbytes IN NUMBER,
o_return_code OUT NUMBER)
AS
message logs.errormsg%TYPE;
BEGIN
update balance set extrabalance=extrabalance+(p_gbytes*1073741824),limited=0 WHERE numec = p_numec;
IF SQL%ROWCOUNT = 0
THEN
o_return_code:=1;
ELSE
o_return_code:=0;
message := 'Cops added ' || p_gbytes || ' gb extra volume';
INSERT INTO logs (logid, eventid, origin, numec, VALUE, errormsg) VALUES (seq_log.NEXTVAL, 'NEXTRAROV', 'increment_extra_balance', p_numec, p_gbytes, message);
END IF;
commit;
EXCEPTION
WHEN OTHERS
THEN
o_return_code := SQLCODE;
ROLLBACK;
END;
/

The order of your parameters looks wrong. Try:
declareParameter(new SqlParameter(P_NUMEC, Types.INTEGER));
declareParameter(new SqlParameter(P_GBYTES, Types.INTEGER));
declareParameter(new SqlOutParameter(O_RETURN_CODE, Types.INTEGER));

Related

Creating Trigger to auto-increment id by sequence in JDBC

I am writing methods through JDBC to create a table and a sequence to recall in a Trigger, I want to set up an id column which auto-increments before every insert on the table. I succeeded in building both the createTable method and the createSequence method in the DAO, but when I run the method to create the Trigger I got the java.sql.SQLException: Missing IN or OUT parameter at index:: 1
public void createTrigger() {
PreparedStatement ps;
StringBuilder queryTrigger = new StringBuilder();
queryTrigger.append("CREATE OR REPLACE TRIGGER ");
queryTrigger.append(Tables.getInstance().getName() + "_INSERTED\n");
queryTrigger.append("BEFORE INSERT ON " + Tabelle.getInstance().getName());
queryTrigger.append("\nFOR EACH ROW\n");
queryTrigger.append("BEGIN\n");
queryTrigger.append("SELECT " + Tables.getInstance().getName() + "SEQ.NEXTVAL\n");
queryTrigger.append("INTO :new.id\n");
queryTrigger.append("FROM dual;\n ");
queryTrigger.append("END;\n");
queryTrigger.append("/\n");
queryTrigger.append("ALTER TRIGGER " +Tabelle.getInstance().getName() + "_INSERTED ENABLE\n");
queryTrigger.append("/\n");
String stringQueryTrigger = queryTrigger.toString();
Connection conn = JDBCUtility.openConnection();
try {
ps = (PreparedStatement) conn.prepareStatement(stringQueryTrigger);
ps.executeUpdate();
ps.close();
} catch(SQLException e) {
e.printStackTrace();
}
JDBCUtility.closeConnection(conn);}
Here instead the creation of the table does actually work even if I don't
write the classic lines with parametrized "?" for the preparedStatement.setString(index, String)
public void createTable(Columns c) {
PreparedStatement ps;
StringBuilder query = new StringBuilder();
query.append("CREATE TABLE " + Tabelle.getInstance().getName() + "(");
query.append(Columns.getInstance().getColumnName() + " ");
query.append(Columns.getInstance().getDataType());
if(Columns.getInstance().isNullOrNot() == true) {
query.append(" NOT NULL");
}
else {
query.append("");
}
if(Columns.getInstance().isPrimaryKeyOrNot() == true) {
query.append(" PRIMARY KEY)");
}
else {
query.append(")");
}
String queryToString = query.toString();
Connection conn = JDBCUtility.openConnection();
try {
ps = (PreparedStatement) conn.prepareStatement(queryToString);
ps.executeUpdate();
ps.close();
} catch(SQLException e) {
e.printStackTrace();
}
JDBCUtility.closeConnection(conn);
}
//EDIT
turns out that is enough to substitute the PreparedStatement with a simple Statement, to get rid of the indexes mechanism and get the DB to accept the query
I would suggest creating an auto-increment sequnce in oracle that can be used for all ids and just add the string id.NextVal to the string query
What I mean is:
Rem in oracle create sequence
CREATE SEQUENCE ID
START BY 1
INCREMENT 1
// in java to execute query
String query = "INSERT INTO TABLE VALUES(ID.NEXTVAL);" ;
//rest of code

Query through PreparedStatement in H2 throws exception: This method is not allowed for a prepared statement; use a regular statement instead

A simple query fails when run through a prepared statement while using JDBC 4 to access an H2 database in Java 11.
When running this line:
try ( ResultSet rs = pstmt.executeQuery( sql ) ; ) {
…I get this error:
org.h2.jdbc.JdbcSQLException: This method is not allowed for a prepared statement; use a regular statement instead. [90130-197]
I tried using the H2 Error Analyzer, but no help there.
Here is a complete example app, in a single .java file. You can copy-paste and run yourself.
package com.basilbourque.example.work.basil.example.h2.pstmt_query;
import org.h2.jdbcx.JdbcDataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class App {
public static void main ( String[] args ) {
App app = new App();
app.doIt();
}
private void doIt ( ) {
// Create database.
try {
Class.forName( "org.h2.Driver" );
} catch ( ClassNotFoundException e ) {
e.printStackTrace();
}
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL( "jdbc:h2:mem:pstmt_query_example_db;DB_CLOSE_DELAY=-1" ); // Set `DB_CLOSE_DELAY` to `-1` to keep in-memory database in existence after connection closes.
dataSource.setUser( "scott" );
dataSource.setPassword( "tiger" );
// Create table.
try (
Connection conn = dataSource.getConnection() ;
Statement stmt = conn.createStatement() ;
) {
String sql = "CREATE TABLE person_ ( \n" +
" pkey_ UUID NOT NULL DEFAULT RANDOM_UUID() PRIMARY KEY , \n" +
" name_ VARCHAR NOT NULL \n" +
");";
System.out.println( sql );
stmt.execute( sql );
} catch ( SQLException e ) {
e.printStackTrace();
}
// Query table.
List < UUID > list = new ArrayList <>();
String sql = "SELECT * FROM person_ WHERE name_ = ? ;";
try (
Connection conn = dataSource.getConnection() ;
PreparedStatement pstmt = conn.prepareStatement( sql ) ;
) {
String name = "Wendy Melvoin";
pstmt.setString( 1 , name );
try ( ResultSet rs = pstmt.executeQuery( sql ) ; ) { // org.h2.jdbc.JdbcSQLException: This method is not allowed for a prepared statement; use a regular statement instead. [90130-197]
while ( rs.next() ) {
UUID pkey = rs.getObject( "pkey_" , UUID.class );
list.add( pkey );
}
}
} catch ( SQLException e ) {
e.printStackTrace();
}
}
}
Exception reported:
org.h2.jdbc.JdbcSQLException: This method is not allowed for a prepared statement; use a regular statement instead. [90130-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.message.DbException.get(DbException.java:144)
at org.h2.jdbc.JdbcPreparedStatement.executeQuery(JdbcPreparedStatement.java:302)
at com.basilbourque.example.work.basil.example.h2.pstmt_query.App.doIt(App.java:53)
at com.basilbourque.example.work.basil.example.h2.pstmt_query.App.main(App.java:13)
Passing the SQL string twice
On a PreparedStatement, you never pass the SQL string to executeQuery method. You do so in an unprepared Statement, but not PreparedStatement. Notice how the JavaDoc for PreparedStatement::executeQuery takes no argument.
So your line:
try ( ResultSet rs = pstmt.executeQuery( sql ) ; ) {
…should be:
try ( ResultSet rs = pstmt.executeQuery() ; ) {
You already passed the SQL string named sql above that line, when you prepared the statement:
PreparedStatement pstmt = conn.prepareStatement( sql ) ;
Since the PreparedStatement named pstmt already holds your SQL statement(s), there is no need to pass into executeQuery.
This mistake might have been the result of copy-pasting some code using Statement for reuse in some other code using PreparedStatement.

Hive UDF includes query statement

I'm facing a problem when writing some UDFs, I searched related posts in site but I'm afraid I haven't got any useful ideas yet.
The question is:
I'm going to execute a SQL statement in UDF, then print query result. Here's my code:
public final class AnalyzeConstraints extends UDF {
private Connection connToHive = null;
public Connection getHiveConn() throws SQLException {
if (connToHive == null) {
try {
Class.forName("org.apache.hive.jdbc.HiveDriver");
} catch (ClassNotFoundException err) {
err.printStackTrace();
System.exit(1);
}
// hive cluster IP address
connToHive = DriverManager.getConnection(
"jdbc:hive://XXXXX:10004/default", "user", "passwd");
System.out.println("loggin");
}
return connToHive;
}
public void closeHiveConn() throws SQLException {
if (connToHive != null) {
connToHive.close();
}
}
//# end region
//# region HiveUtility
// query data
public ResultSet queryData(String sql) throws SQLException {
Connection conn = getHiveConn();
Statement stmt = conn.createStatement();
ResultSet res = stmt.executeQuery(sql);
return res;
}
//# end region
//# region UDF implement
public String evaluate(String table) throws SQLException {
StringBuffer result = new StringBuffer();
String schema = null;
String table_name = null;
if (table.length() > 0 && table.indexOf(".") > 0 && table.split("\\.").length == 2) {
schema = table.split("\\.")[0];
table_name = table.split("\\.")[1];
// For debug
System.out.println(schema + ":" + table_name);
}
else
result.append("ERROR: \'" + table + "\' is not a valid table in the current search_path\n");
//# region analyze PK
StringBuffer sqlPK = new StringBuffer();
sqlPK.append(String.format("select constraint_name, column_name from catalog.constraint_columns where table_schema = \'%s\' and Upper(table_name) = \'%s\' and constraint_type = \'p\';\n", schema, table_name.toUpperCase()));
// For debug
System.out.println(sqlPK.toString());
ResultSet resPK = queryData(sqlPK.toString());
// Print resultset here
}
Here's error message:
FAILED: SemanticException [Error 10014]: Line 1:8 Wrong arguments ''catalog.systables'': org.apache.hadoop.hive.ql.metadata.HiveException: Unable to execute method public java.lang.String AnalyzeConstraints.evaluate(java.lang.String) throws java.sql.SQLException on object AnalyzeConstraints#4f86c135 of class AnalyzeConstraints with arguments {catalog.systables:java.lang.String} of size 1
hive>
Any ideas will be highly appreciated! Thanks in advance!

NHibernate does not seems doing Bulk Inserting into PostgreSQL

I am interfacing with a PostgreSQL database with NHibernate.
Background
I made some simple tests...it seems it's taking 2 seconds to persist 300 records.
I have a Perl program with identical functionality, but issue direct SQL instead, takes only 70% of the time.
I am not sure if this is expected. I thought C#/NHibernate would be faster or at least on par.
Questions
One of my observation is that (with show_sql turned on), the NHibernate is issuing INSERTs a few hundreds times, instead of doing bulk INSERT that take cares of multiple rows. And note I am assigning the primary key myself, not using the "native" generator.
Is that expected? Is there anyway I could make it issue bulk INSERT statement instead? It seems to me that this could be one of the area I could speed up the performance.
As stachu found out correctly: NHibernate does not have *BatchingBatcher(Factory) for PostgreSQL(Npgsql)
As stachu askes: Did anybody managed to force Nhibarnate to do batch inserts to PostgreSQL
I wrote a Batcher that doesn't use any Npgsql batching stuff, but does manipulate the SQL String "oldschool style" (INSERT INTO [..] VALUES (...),(...), ...)
using System;
using System.Collections;
using System.Data;
using System.Diagnostics;
using System.Text;
using Npgsql;
namespace NHibernate.AdoNet
{
public class PostgresClientBatchingBatcherFactory : IBatcherFactory
{
public virtual IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
{
return new PostgresClientBatchingBatcher(connectionManager, interceptor);
}
}
/// <summary>
/// Summary description for PostgresClientBatchingBatcher.
/// </summary>
public class PostgresClientBatchingBatcher : AbstractBatcher
{
private int batchSize;
private int countOfCommands = 0;
private int totalExpectedRowsAffected;
private StringBuilder sbBatchCommand;
private int m_ParameterCounter;
private IDbCommand currentBatch;
public PostgresClientBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
: base(connectionManager, interceptor)
{
batchSize = Factory.Settings.AdoBatchSize;
}
private string NextParam()
{
return ":p" + m_ParameterCounter++;
}
public override void AddToBatch(IExpectation expectation)
{
if(expectation.CanBeBatched && !(CurrentCommand.CommandText.StartsWith("INSERT INTO") && CurrentCommand.CommandText.Contains("VALUES")))
{
//NonBatching behavior
IDbCommand cmd = CurrentCommand;
LogCommand(CurrentCommand);
int rowCount = ExecuteNonQuery(cmd);
expectation.VerifyOutcomeNonBatched(rowCount, cmd);
currentBatch = null;
return;
}
totalExpectedRowsAffected += expectation.ExpectedRowCount;
log.Info("Adding to batch");
int len = CurrentCommand.CommandText.Length;
int idx = CurrentCommand.CommandText.IndexOf("VALUES");
int endidx = idx + "VALUES".Length + 2;
if (currentBatch == null)
{
// begin new batch.
currentBatch = new NpgsqlCommand();
sbBatchCommand = new StringBuilder();
m_ParameterCounter = 0;
string preCommand = CurrentCommand.CommandText.Substring(0, endidx);
sbBatchCommand.Append(preCommand);
}
else
{
//only append Values
sbBatchCommand.Append(", (");
}
//append values from CurrentCommand to sbBatchCommand
string values = CurrentCommand.CommandText.Substring(endidx, len - endidx - 1);
//get all values
string[] split = values.Split(',');
ArrayList paramName = new ArrayList(split.Length);
for (int i = 0; i < split.Length; i++ )
{
if (i != 0)
sbBatchCommand.Append(", ");
string param = null;
if (split[i].StartsWith(":")) //first named parameter
{
param = NextParam();
paramName.Add(param);
}
else if(split[i].StartsWith(" :")) //other named parameter
{
param = NextParam();
paramName.Add(param);
}
else if (split[i].StartsWith(" ")) //other fix parameter
{
param = split[i].Substring(1, split[i].Length-1);
}
else
{
param = split[i]; //first fix parameter
}
sbBatchCommand.Append(param);
}
sbBatchCommand.Append(")");
//rename & copy parameters from CurrentCommand to currentBatch
int iParam = 0;
foreach (NpgsqlParameter param in CurrentCommand.Parameters)
{
param.ParameterName = (string)paramName[iParam++];
NpgsqlParameter newParam = /*Clone()*/new NpgsqlParameter(param.ParameterName, param.NpgsqlDbType, param.Size, param.SourceColumn, param.Direction, param.IsNullable, param.Precision, param.Scale, param.SourceVersion, param.Value);
currentBatch.Parameters.Add(newParam);
}
countOfCommands++;
//check for flush
if (countOfCommands >= batchSize)
{
DoExecuteBatch(currentBatch);
}
}
protected override void DoExecuteBatch(IDbCommand ps)
{
if (currentBatch != null)
{
//Batch command now needs its terminator
sbBatchCommand.Append(";");
countOfCommands = 0;
log.Info("Executing batch");
CheckReaders();
//set prepared batchCommandText
string commandText = sbBatchCommand.ToString();
currentBatch.CommandText = commandText;
LogCommand(currentBatch);
Prepare(currentBatch);
int rowsAffected = 0;
try
{
rowsAffected = currentBatch.ExecuteNonQuery();
}
catch (Exception e)
{
if(Debugger.IsAttached)
Debugger.Break();
throw;
}
Expectations.VerifyOutcomeBatched(totalExpectedRowsAffected, rowsAffected);
totalExpectedRowsAffected = 0;
currentBatch = null;
sbBatchCommand = null;
m_ParameterCounter = 0;
}
}
protected override int CountOfStatementsInCurrentBatch
{
get { return countOfCommands; }
}
public override int BatchSize
{
get { return batchSize; }
set { batchSize = value; }
}
}
}
I also found that NHibernate is not doing batch inserts into PostgreSQL.
I identified two possible reasons:
1) Npgsql driver does not support batch inserts/updates (see forum)
2) NHibernate does not have *BatchingBatcher(Factory) for PostgreSQL(Npgsql). I tried using Devart dotConnect driver with NHibernate (I wrote custom driver for NHibernate) but it still did not worked.
I suppose this driver should also implement IEmbeddedBatcherFactoryProvider interface, but it seems not trivial for me (using one for Oracle did not worked ;) )
Did anybody managed to force Nhibarnate to do batch inserts to PostgreSQL or can confirm my conclusion?

when is an EJB CMP entity bean actually created

I have a session bean that provides a business method, in which it creates several CMP entity beans, something like this
public void businessMethod(int number) {
try {
MyBeanHome home = lookupMyBean();
DataSource dataSource = getMyDataSource();
Statement statement = dataSource.getConnection().createStatement();
ResultSet result;
String tableName = "MYBEAN";
for (int i = 0; i < number; i++) {
result = statement.executeQuery("select max(ID) + 1 from " + tableName);
result.next();
int newID = result.getInt(1);
System.out.println(newID);
MyBeanLocal lineLocal = home.create(newID);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
The create method of MyBean simply creates a new bean with newID. However, the above code only works with number = 1. If number > 1, it tries to create a second bean with the same ID (System.out.println(newID); prints the same value). I'm guessing the new bean is not stored in the database yet, and thus the query returns the same value. What can be done to this?
Many thanks!
I figured it out that the transaction is only executed when the business method finishes, so the first entity beans would not be stored in the database for retrieval. A simple solution is below
public void businessMethod(int number) {
try {
MyBeanHome home = lookupMyBean();
DataSource dataSource = getMyDataSource();
Statement statement = dataSource.getConnection().createStatement();
ResultSet result;
String tableName = "MYBEAN";
result = statement.executeQuery("select max(ID) + 1 from " + tableName);
result.next();
int newID = result.getInt(1);
for (int i = 0; i < number; i++) {
System.out.println(newID);
MyBeanLocal lineLocal = home.create(newID++);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}