In an instance of SQL Server 2016 I have a stored procedure with dozens of parameters. For example:
CREATE PROCEDURE spName (
#par1 INT = NULL,
#par2 VARCHAR(10) = NULL,
....
....
#par98 INT = NULL,
#par99 INT = NULL,
) AS
BEGIN
....
....
END
I have a client written in C# that calls the stored procedure specifying only the parameters with a value. Ex:
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "spName";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = dbConn;
cmd.Parameters.Add(new SqlParameter("par1", "val1"));
cmd.Parameters.Add(new SqlParameter("par47", "val47"));
...
cmd.ExecuteNonQuery();
It works perfectly! So, the procedure is executed and only the 2 parameters (par1 and par47) have a value. Other parameters maintain the default value (NULL).
I would do the same from a Java client using Microsoft JDBC driver 6.2.
I specify the parameters with List<Map<String, Object>>, so a list of couple parameterName-->parameterValue. The following method builds the PreparedStatement object:
private CallableStatement prepareStatement(String spName, Map<String, ?> parameters) throws SQLException {
setupConnection();
CallableStatement stmt = null;
try {
stmt = conn.prepareCall(getSpCallString(spName, parameters));
if (parameters != null) {
for (String parName : parameters.keySet())
stmt.setObject(parName, parameters.get(parName));
}
} catch (SQLException e) {
ApplicationLogging.severe("Cannot prepare callable statement", e);
throw e;
}
return stmt;
}
The method getSpCallString() generates a string of the type { call spName ?,?, ... , ? } with a number of ? as the number of parameters with a value passed to the procedure, so not all 99 parameters. If I have 2 parameter it generates the string { call spName ?,? }.
By passing for example par15=val15 and par47=val47 it raises the following exception:
com.microsoft.sqlserver.jdbc.SQLServerException: The index 2 is out of range.
I could resolve this putting in the call command the same number of ? as the number of parameter of the stored procedure but... I don't know the number of parameters for each stored procedure (and their position)!
In C# this is simply resolved because the parameters are assigned only with their name, so the number and the order of parameters can be really a black box.
Can I do this in some way in Java?
This is a confirmed deficiency in the current implementation of named parameter support for CallableStatement in the mssql-jdbc driver. Despite section 13.3.2 of the JDBC 4.2 specification stating ...
Named parameters can be used to specify only the values that have no default value.
... we seem to be required to provide a parameter placeholder for every possible parameter, and there doesn't appear to be a way to specify DEFAULT for the parameters we might otherwise simply omit.
As a workaround we could use code like this
public static ResultSet executeStoredProcedureQuery(
Connection conn, String spName, Map<String, Object> paramItems)
throws SQLException {
StringBuffer sqlBuf = new StringBuffer("EXEC ");
sqlBuf.append(spName);
int paramCount = 1;
for (String paramName : paramItems.keySet()) {
sqlBuf.append(
(paramCount++ > 1 ? ", " : " ") +
(paramName.startsWith("#") ? "" : "#") + paramName + "=?");
}
String sql = sqlBuf.toString();
myLogger.log(Level.INFO, sql);
// e.g., EXEC dbo.BreakfastSP #helpings=?, #person=?, #food=?
PreparedStatement ps = conn.prepareStatement(sql);
paramCount = 1;
for (String paramName : paramItems.keySet()) {
ps.setObject(paramCount++, paramItems.get(paramName));
}
return ps.executeQuery();
}
which we could call like this
// test data
Map<String, Object> paramItems = new HashMap<>();
paramItems.put("#person", "Gord");
paramItems.put("#food", "bacon");
paramItems.put("#helpings", 3);
//
ResultSet rs = executeStoredProcedureQuery(conn, "dbo.BreakfastSP", paramItems);
If using a third party library to facilitate calling such procedures is an option for you, then jOOQ certainly helps via its code generator for stored procedures, which generates stubs for each of your procedures, making such calls type safe. It includes support for:
Table valued functions
Table valued parameters
Defaulted parameters
In / Out parameters
Optional return value of procedures
Fetching undeclared update counts and result sets
Much more
In your case, you could write:
Spname sp = new Spname();
sp.setPar1("val1");
sp.setPar47("val47");
sp.execute(configuration); // The object containing your JDBC connection
sp.getResults(); // The result set(s) and update counts, if any
Behind the scenes, a JDBC CallableStatement is created, just like you would do manually:
try (CallableStatement s = c.prepareCall(
"{ ? = call [dbo].[spName] (#par1 = ?, #par47 = ?) }"
)) {
// Get the optional procedure return value that all procedures might return
s.registerOutParameter(1, Types.INTEGER);
s.setString(2, "val1");
s.setString(3, "val47");
s.execute();
// Lengthy procedure to fetch update counts and result set(s)
}
See this article if you want to generically fetch update counts and result set(s) with JDBC.
Disclaimer: I work for the company behind jOOQ.
Related
I'm using Spring Data JPA 1.10.2 with com.microsoft.sqlserver's sqljdbc 4.2. I get the following error:
o.h.engine.jdbc.spi.SqlExceptionHelper : Error preparing CallableStatement [User.pTest]
com.microsoft.sqlserver.jdbc.SQLServerException: The index 4 is out of range.
My entity class is:
#Entity
#NamedStoredProcedureQuery(name = "User.getUser", procedureName = "User.pTest", parameters = {
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "session", type = byte[].class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "name", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "date", type = Date.class)
})
#Data //lombok
public class User {
// serves no purpose other than to meet
// JPA requirement
#Id
private Long id;
}
The repository code is
public interface UserRepository extends Repository<User, Long> {
#Procedure("User.pTest")
byte[] getUserSession(#Param("name") String name,
#Param("date") Date date
);
}
My test code is as follows and when I run it I get the error:
#Test
public void testGettingApxSession() {
Calendar cal = new GregorianCalendar(2016,6,5);
byte[] b = userRepository.getUserSession("myName", cal.getTime());
}
How do I resolve the error?
Update
Forgot to include the relevant part of the SQL Server stored proc:
ALTER procedure [User].[pTest]
#session varbinary(max) out
,#name nvarchar(max) = null
,#opt nvarchar(max) = null
,#date datetime
as
begin
set #session = CAST(N'<?xml version="1.0" encoding="UTF-16" ?><Session session = 45"'/>' as varbinary(max))
end
If you specify the nth parameter, Microsoft SQL Server driver requires that you also include the other parameters up to n. For example, let's say your procedure has five parameters (in this order) each having a default value of null:
param1 = null
param2 = null
param3 = null
param4 = null
param5 = null
You may only want to specify a value for param4. But if you do, you also must at least specify param1, param2, and param3 in the call to the stored procedure.
That may not seem too much of an issue. But if you have many default params and only need to specify say the 15th parameter, it will be quite tedious to also have to specify the other 14.
At least that's not fatal. But it is fatal in combination with the following Hibernate rule: Hibernate requires that each specified param is not null. But what if the procedure requires that only param3 or param4 have a value (but not both)? If you specify param4 to have a value, then you must include param3 (per MS driver) and you must give it a value (per Hibernate). So, you're sunk.
The only solutions are
Use a different driver such as jTDS (which has an issue on truncating >8000 bytes) OR
Write a wrapper procedure to just have only the needed params (e.g. param4) and let it call the procedure with five parameters.
Details of the exception in the OP
The Microsoft SQL Server driver has found (in the db metadata) four stored procedure parameters and returns the name and index of each. Then, the Hibernate / JPA code is building the CallableStatement.Via #NamedStoredProcedureonly three parameters have been defined. So, it builds something like pTest(?,?,?). But then Hibernate uses the metadata to say that Date should be in the fourth position which is out of range. The built pTest only has three parameters.
Im using entity framework 4.3 code first for calling stored procedure the way i call the stored procedure is like this:
var parameters = new[]
{
new SqlParameter("member", 1),
**new SqlParameter("Code","0165210662660001"),**
new SqlParameter("PageSize", 1),
new SqlParameter("PageNumber",1)
};
var result = context.Database.SqlQuery<resultClass>(
"mySpName #member, #Code, #PageSize,#PageNumber" parameters).ToList();
It gets executed on the SqlServer and I get the result.
But if I change the order of the paramaters like this:
var result = context.Database.SqlQuery<resultClass>("mySpName #Code, #member,#PageSize,#PageNumber" parameters).ToList();
var parameters = new[]
{
**new SqlParameter("Code","0165210662660001"),**
new SqlParameter("Member", 1),
new SqlParameter("PageSize", 1),
new SqlParameter("PageNumber",1)
};
I got an error like this :
Error converting data type nvarchar to int
The stored procedure is like this :
ALTER PROCEDURE [c].[mySpName]
#Member INT ,
#Code VARCHAR (50) ,
#PageSize INT ,
#PageNumber INT
AS
Why do i get this order?
Is it important to keep parameters order?
What can i do so that I can call a stored procedure without being concerned about parameters order?
============ i find a workaround and it works perfectly ============
public class blahContext<T>
{
int i = 0;
public IEnumerable<T> ExecuteStoreQuery(string SPname, SqlParameter[] parameters)
{
using (var context = new CADAContext())
{
string para = string.Join(", ", (from p in parameters
where !"NULL".Equals(p.Value)
select string.Concat(new object[] { "#", p.ParameterName, "={", this.i++, "}" })).ToList<string>());
object[] x = (from p in parameters
where !"NULL".Equals(p.Value)
select p.Value).ToArray<object>();
return context.Database.SqlQuery<T>(SPname + " " + para, x).ToList();
}
}
It's not because of the parameter order in your parameters object - it's because in your second code snippet you're explicitly passing the #Code value as the first parameter when the SP is expecting a Member INT value.
var result = context.Database.SqlQuery<resultClass>("mySpName #Code, #member,#PageSize,#PageNumber" parameters).ToList();
...you're passing in "0165210662660001" as the first parameter and the conversion to INT is failing.
The order of your parameters in your parameters object is irrelevant as EF (ADO.NET actually) will map those parameters to the #parametername values in your query string. So the new SqlParameter("Code","0165210662660001") will be mapped into the #Code position in your query - which int the second code snipped is actually the position for the Member value as expected by the SP.
However... you can execute a SP using named parameters as well and in that case you can pass the parameters to the SP in any order as below:
db.Database.SqlQuery<resultClass>("mySpName PageNumber=#PageNumber,Code=#Code,PageSize=#PageSize,Member=#member", parameters).ToList();
You see that I'm not passing the params to the SP in the order they were defined [by the SP] but because they're named I don't have to care.
For different ways of passing params see: This Answer for some good examples.
In a SQL Server 2008 I have a simple stored procedure moving a bunch of records to another table:
CREATE PROCEDURE [dbo].MyProc(#ParamRecDateTime [datetime])
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].Table2
SELECT
...,
...
FROM [dbo].Table1
WHERE RecDateTime <= #ParamRecDateTime
DELETE FROM [dbo].Table1
WHERE RecDateTime <= #ParamRecDateTime
END
Running it from within SQL Server Management Studio, I get the job done and return value = 0
DECLARE #return_value int
EXEC #return_value = dbo.MyProc #ParamRecDateTime = '2011-06-25 11:00:00.000'
SELECT 'Return Value' = #return_value
But when I call the same stored procedure from an app using Entity framework, I also get the job done but the return value is "-1":
int result = myrepository.MyProc(datetimePar);
MessageBox.Show(result.ToString());
I didn't manage to find an explanation for this error, but found this discouraging post, where it's said that there is no standard for this type of return codes in SQL Server.
What is the good, reliable way of getting know of a Stored Procedure execution result when calling it from Entity Framework and when the Stored Procedure doesn't return any entities?
One way to do it is to call ExecuteStoreCommand, and pass in a SqlParameter with a direction of Output:
var dtparm = new SqlParameter("#dtparm", DateTime.Now);
var retval = new SqlParameter("#retval", SqlDbType.Int);
retval.Direction = ParameterDirection.Output;
context.ExecuteStoreCommand("exec #retval = MyProc #dtparm", retval, dtparm);
int return_value = (int)retval.Value;
Originally I tried using a direction of ReturnValue:
retval.Direction = ParameterDirection.ReturnValue;
context.ExecuteStoreCommand("MyProc #dtparm", retval, dtparm);
but retval.Value would always be 0. I realized that retval was the result of executing the MyProc #dtparm statement, so I changed it to capture the return value of MyProc and return that as an output parameter.
using (dbContext db = new dbContext())
{
var parameters = new[]
{
new SqlParameter("#1","Input Para value"),
new SqlParameter("#2",SqlDbType.VarChar,4){ Value = "default if you want"},
new SqlParameter("#3",SqlDbType.Int){Value = 0},
new SqlParameter("#4","Input Para Value"),
new SqlParameter("#5",SqlDbType.VarChar,10 ) { Direction = ParameterDirection.Output },
new SqlParameter("#6",SqlDbType.VarChar,1000) { Direction = ParameterDirection.Output }
};
db.ExecuteStoreCommand("EXEC SP_Name #1,#2,#3,#4,#5 OUT,#6 OUT", parameters);
ArrayList ObjList = new ArrayList();
ObjList.Add(parameters[1].Value);
ObjList.Add(parameters[2].Value);
}
See OUTPUT attribute for SQL param of store procedure,
here
For future reference: I had the same issue but needed multiple OUTPUT variables. The solution was a combination of both answers. Below is a complete sample.
public void MyStoredProc(int inputValue, out decimal outputValue1, out decimal outputValue2)
{
var parameters = new[] {
new SqlParameter("#0", inputValue),
new SqlParameter("#1", SqlDbType.Decimal) { Direction = ParameterDirection.Output },
new SqlParameter("#2", SqlDbType.Decimal) { Direction = ParameterDirection.Output }
};
context.ExecuteStoreCommand("exec MyStoredProc #InParamName=#0, #OutParamName1=#1 output, #OutParamName2=#2 output", parameters);
outputValue1 = (decimal)parameters[1].Value;
outputValue2 = (decimal)parameters[2].Value;
}
Please note the Types used (decimal.) If another type is needed, remember to not only change it in the method argument list but also the SqlDbType.XXX.
I saw this example somewhere:
rs = connection.prepareStatement("select * from table").executeQuery();
Could I use this format, if I want to execute a query like this "Select * from table where column = "hello" "?
The way in which I usual I use prepareStatement object is something like this:
String sql = "select * from adresa where column = ?";
PreparedStatement pre = con.prepareStatement(sql);
pre.setString(1, i);
rs = pre.executeQuery();
Later Edit:
I don't understand. Pascal Thivent wrote that I can use the short version with In parameters, but Liu tells me this is not possible. :) Anw, using Pascal's version, i receive this error: void cannot be dereferenced
Here's a partial example how to use this interface:
static final String USER = "root";
static final String PASS = "newpass";
Connection conn = DriverManager.getConnection(myUrl, USER, PASS);
// create a sql date object so we can use it in our INSERT statement
Calendar calendar = Calendar.getInstance();
java.sql.Date startDate = new java.sql.Date(calendar.getTime().getTime());
// the mysql insert statement
String query = " insert into students (ID, last_name, first_name, birthday, hometown)"
+ " values (?, ?, ?, ?, ?)";
// create the mysql insert preparedstatement
PreparedStatement preparedStmt = conn.prepareStatement(query);
preparedStmt.setInt(1, 808027);
preparedStmt.setString(2, "Davis");
preparedStmt.setString(3, "Felicita");
preparedStmt.setDate(4, startDate);
preparedStmt.setString(5, "Venice");
// execute the preparedstatement
preparedStmt.execute();
conn.close();
You can only use the first form if there are no bind variables (question marks) in the query. It's just a shortened version of what you posted.
Also, if you use the shortened form you won't have the opportunity to reuse the PreparedStatement object.
of course u can use a string variable for the query in which u put in ur dynamic data and run it.
rs = connection.prepareStatement(variable).executeQuery();
The long form is often, but prepared statements can be precompiled by the db, and if used properly will help prevent sql injection.
Connection conn = null;
ResultSet rs = null;
PreparedStatement ps = null;
try {
conn = getConn();
ps = conn.prepareStatement("select * from x where y = ? "); //note no sb.append()'s or +'s, to helps prevent sql injection
ps.setLong(1, 12l);
rs = ps.executeQuery();
while (rs.next()) {
... act ...
}
} catch ( Exception e) {
} finally {
if (rs != null) rs.close();
if (ps != null) ps.close();
if (conn != null) conn.close();
}
Who said java was verbose. :)
1.Database platform: SqlServer
2.Data Access: nHibernate 1.2
Now we need access the store procedure by nHibernate,like this:
ALTER PROCEDURE TestProc()
AS
BEGIN
Select * From User
Return 1234
END
I know I can get the User List by IQuery,
And I want to get the default return value "1234" too.
Question:
How to get this default return value?
If can't get it directly , can we get the value by output parameter?
NHibernate does not let you use stored procedures in this manner. But it does allow a way to make calls using the plain old ADO.NET API. The NHibernate Documentation says that if you want to use these procedures you have to execute them via session.Connection. Here's an example -
ISession session = sessionFactory.GetSession();
using(ITransaction transaction = session.BeginTransaction())
{
IDbCommand command = new SqlCommand();
command.Connection = session.Connection;
// Enlist IDbCommand into the NHibernate transaction
transaction.Enlist(command);
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "dbo.SetUserInfo";
// Set input parameters
var parm = new SqlParameter("#UserID", SqlDbType.Int);
parm.Value = 12345;
command.Parameters.Add(parm);
// Set output parameter
var outputParameter = new SqlParameter("#Quantity", SqlDbType.Int);
outputParameter.Direction = ParameterDirection.Output;
command.Parameters.Add(outputParameter);
// Set a return value
var returnParameter = new SqlParameter("#RETURN_VALUE", SqlDbType.Int);
returnParameter.Direction = ParameterDirection.ReturnValue;
command.Parameters.Add(returnParameter);
// Execute the stored procedure
command.ExecuteNonQuery();
}
You can find more details here -
http://refactoringaspnet.blogspot.com/2009/06/how-to-use-legacy-stored-procedures-in.html
this is how i do it:
in my .hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DocumentManagement.Data" namespace="DocumentManagement.Data.Repositories" >
<sql-query name="GetDocument">
<return class="DocumentManagement.Core.Models.PhysicalDocument, DocumentManagement.Core">
<return-property column="DocId" name="Id" />
<return-property column="Filepath" name="Filepath" />
<return-property column="Filename" name="Filename" />
</return>
exec Investor_GetDocumentById :userId, :docId
</sql-query>
</hibernate-mapping>
in my repository.cs
public PhysicalDocument GetDocumentPath(int userId, int docId)
{
var query = Session.GetNamedQuery("GetDocument")
.SetInt32("userId", userId)
.SetInt32("docId", docId).List<PhysicalDocument>();
return query[0];
}
First of all, that's not called a "default return value" anywhere I've ever seen. It's just the return value. It's usually used to return a success / error status.
I don't know how nHibernate does things, but in ADO.NET, you'd use a parameter with the Direction property set to "Return". Maybe there's an equivalent in nHibernate.
OTOH, it would be more usual to use an OUTPUT parameter to return an actual useful value, and keep the RETURN value for error codes, or for being ignored.
I've done this before (not in nHibernate).
You must completely process the entire recordset before retrieving the output parameters.
Another discussion