I am writing a simple web application to call a stored procedure and retrieve some data.
Its a very simple application, which interacts with client's database. We pass employee id and company id and the stored procedure will return employee details.
Web application cannot update/delete data and is using SQL Server.
I am deploying my web application in Jboss AS. Should I use JPA to access the stored procedure or CallableStatement. Any advantage of using JPA in this case.
Also what will be the sql statement to call this stored procedure. I have never used stored procedures before and I am struggling with this one. Google was not much of a help.
Here is the stored procedure:
CREATE procedure getEmployeeDetails (#employeeId int, #companyId int)
as
begin
select firstName,
lastName,
gender,
address
from employee et
where et.employeeId = #employeeId
and et.companyId = #companyId
end
Update:
For anyone else having problem calling stored procedure using JPA.
Query query = em.createNativeQuery("{call getEmployeeDetails(?,?)}",
EmployeeDetails.class)
.setParameter(1, employeeId)
.setParameter(2, companyId);
List<EmployeeDetails> result = query.getResultList();
Things I have noticed:
Parameter names didn't work for me, so try using parameter index.
Correct sql statement {call sp_name(?,?)} instead of call sp_name(?,?)
If stored procedure is returning a result set, even if you know with only one row, getSingleResult wont work
Pass a resultSetMapping name or result class details
JPA 2.1 now support Stored Procedure, read the Java doc here.
Example:
StoredProcedureQuery storedProcedure = em.createStoredProcedureQuery("sales_tax");
// set parameters
storedProcedure.registerStoredProcedureParameter("subtotal", Double.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("tax", Double.class, ParameterMode.OUT);
storedProcedure.setParameter("subtotal", 1f);
// execute SP
storedProcedure.execute();
// get result
Double tax = (Double)storedProcedure.getOutputParameterValue("tax");
See detailed example here.
I am deploying my web application in Jboss AS. Should I use JPA to access the stored procedure or CallableStatement. Any advantage of using JPA in this case.
It is not really supported by JPA but it's doable. Still I wouldn't go this way:
using JPA just to map the result of a stored procedure call in some beans is really overkill,
especially given that JPA is not really appropriate to call stored procedure (the syntax will be pretty verbose).
I would thus rather consider using Spring support for JDBC data access, or a data mapper like MyBatis or, given the simplicity of your application, raw JDBC and CallableStatement. Actually, JDBC would probably be my choice. Here is a basic kickoff example:
CallableStatement cstmt = con.prepareCall("{call getEmployeeDetails(?, ?)}");
cstmt.setInt("employeeId", 123);
cstmt.setInt("companyId", 456);
ResultSet rs = cstmt.executeQuery();
Reference
JDBC documentation: Java SE 6
You need to pass the parameters to the stored procedure.
It should work like this:
List result = em
.createNativeQuery("call getEmployeeDetails(:employeeId,:companyId)")
.setParameter("emplyoyeeId", 123L)
.setParameter("companyId", 456L)
.getResultList();
Update:
Or maybe it shouldn't.
In the Book EJB3 in Action, it says on page 383, that JPA does not support stored procedures (page is only a preview, you don't get the full text, the entire book is available as a download in several places including this one, I don't know if this is legal though).
Anyway, the text is this:
JPA and database stored procedures
If you’re a big fan of SQL, you may be
willing to exploit the power of
database stored procedures.
Unfortunately, JPA doesn’t support
stored procedures, and you have to
depend on a proprietary feature of
your persistence provider. However,
you can use simple stored functions
(without out parameters) with a native
SQL query.
For a simple stored procedure that using IN/OUT parameters like this
CREATE OR REPLACE PROCEDURE count_comments (
postId IN NUMBER,
commentCount OUT NUMBER )
AS
BEGIN
SELECT COUNT(*) INTO commentCount
FROM post_comment
WHERE post_id = postId;
END;
You can call it from JPA as follows:
StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("count_comments")
.registerStoredProcedureParameter(1, Long.class,
ParameterMode.IN)
.registerStoredProcedureParameter(2, Long.class,
ParameterMode.OUT)
.setParameter(1, 1L);
query.execute();
Long commentCount = (Long) query.getOutputParameterValue(2);
For a stored procedure which uses a SYS_REFCURSOR OUT parameter:
CREATE OR REPLACE PROCEDURE post_comments (
postId IN NUMBER,
postComments OUT SYS_REFCURSOR )
AS
BEGIN
OPEN postComments FOR
SELECT *
FROM post_comment
WHERE post_id = postId;
END;
You can call it as follows:
StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("post_comments")
.registerStoredProcedureParameter(1, Long.class,
ParameterMode.IN)
.registerStoredProcedureParameter(2, Class.class,
ParameterMode.REF_CURSOR)
.setParameter(1, 1L);
query.execute();
List<Object[]> postComments = query.getResultList();
For a SQL function that looks as follows:
CREATE OR REPLACE FUNCTION fn_count_comments (
postId IN NUMBER )
RETURN NUMBER
IS
commentCount NUMBER;
BEGIN
SELECT COUNT(*) INTO commentCount
FROM post_comment
WHERE post_id = postId;
RETURN( commentCount );
END;
You can call it like this:
BigDecimal commentCount = (BigDecimal) entityManager
.createNativeQuery(
"SELECT fn_count_comments(:postId) FROM DUAL"
)
.setParameter("postId", 1L)
.getSingleResult();
At least when using Hibernate 4.x and 5.x because the JPA StoredProcedureQuery does not work for SQL FUNCTIONS.
For more details about how to call stored procedures and functions when using JPA and Hibernate, check out the following articles
How to call Oracle stored procedures and functions with JPA and Hibernate
How to call SQL Server stored procedures and functions with JPA and Hibernate
How to call PostgreSQL functions (stored procedures) with JPA and Hibernate
How to call MySQL stored procedures and functions with JPA and Hibernate
How to retrieve Stored Procedure output parameter using JPA (2.0 needs EclipseLink imports and 2.1 does not)
Even though this answer does elaborate on returning a recordset from a stored procedure,
I am posting here, because it took me ages to figure it out and this thread helped me.
My application was using Eclipselink-2.3.1, but I will force an upgrade to
Eclipselink-2.5.0, as JPA 2.1 has much better support for stored procedures.
Using EclipseLink-2.3.1/JPA-2.0: Implementation-Dependent
This method requires imports of EclipseLink classes from "org.eclipse.persistence", so it is specific to Eclipselink implementation.
I found it at "http://www.yenlo.nl/en/calling-oracle-stored-procedures-from-eclipselink-with-multiple-out-parameters".
StoredProcedureCall storedProcedureCall = new StoredProcedureCall();
storedProcedureCall.setProcedureName("mypackage.myprocedure");
storedProcedureCall.addNamedArgument("i_input_1"); // Add input argument name.
storedProcedureCall.addNamedOutputArgument("o_output_1"); // Add output parameter name.
DataReadQuery query = new DataReadQuery();
query.setCall(storedProcedureCall);
query.addArgument("i_input_1"); // Add input argument names (again);
List<Object> argumentValues = new ArrayList<Object>();
argumentValues.add("valueOf_i_input_1"); // Add input argument values.
JpaEntityManager jpaEntityManager = (JpaEntityManager) getEntityManager();
Session session = jpaEntityManager.getActiveSession();
List<?> results = (List<?>) session.executeQuery(query, argumentValues);
DatabaseRecord record = (DatabaseRecord) results.get(0);
String result = String.valueOf(record.get("o_output_1")); // Get output parameter
Using EclipseLink-2.5.0/JPA-2.1: Implementation-Independent (documented already in this thread)
This method is implementation independent (don't need Eclipslink imports).
StoredProcedureQuery query = getEntityManager().createStoredProcedureQuery("mypackage.myprocedure");
query.registerStoredProcedureParameter("i_input_1", String.class, ParameterMode.IN);
query.registerStoredProcedureParameter("o_output_1", String.class, ParameterMode.OUT);
query.setParameter("i_input_1", "valueOf_i_input_1");
boolean queryResult = query.execute();
String result = String.valueOf(query.getOutputParameterValue("o_output_1"));
For me, only the following worked with Oracle 11g and Glassfish 2.1 (Toplink):
Query query = entityManager.createNativeQuery("BEGIN PROCEDURE_NAME(); END;");
query.executeUpdate();
The variant with curly braces resulted in ORA-00900.
If using EclipseLink you can use the #NamedStoredProcedureQuery or StoreProcedureCall to execute any stored procedure, including ones with output parameters, or out cursors. Support for stored functions and PLSQL data-types is also available.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Stored_Procedures
The following works for me:
Query query = em.createNativeQuery("BEGIN VALIDACIONES_QPAI.RECALC_COMP_ASSEMBLY('X','X','X',0); END;");
query.executeUpdate();
May be it's not the same for Sql Srver but for people using oracle and eclipslink it's working for me
ex: a procedure that have one IN param (type CHAR) and two OUT params (NUMBER & VARCHAR)
in the persistence.xml declare the persistence-unit :
<persistence-unit name="presistanceNameOfProc" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/DataSourceName</jta-data-source>
<mapping-file>META-INF/eclipselink-orm.xml</mapping-file>
<properties>
<property name="eclipselink.logging.level" value="FINEST"/>
<property name="eclipselink.logging.logger" value="DefaultLogger"/>
<property name="eclipselink.weaving" value="static"/>
<property name="eclipselink.ddl.table-creation-suffix" value="JPA_STORED_PROC" />
</properties>
</persistence-unit>
and declare the structure of the proc in the eclipselink-orm.xml
<?xml version="1.0" encoding="UTF-8"?><entity-mappings version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd">
<named-stored-procedure-query name="PERSIST_PROC_NAME" procedure-name="name_of_proc" returns-result-set="false">
<parameter direction="IN" name="in_param_char" query-parameter="in_param_char" type="Character"/>
<parameter direction="OUT" name="out_param_int" query-parameter="out_param_int" type="Integer"/>
<parameter direction="OUT" name="out_param_varchar" query-parameter="out_param_varchar" type="String"/>
</named-stored-procedure-query>
in the code you just have to call your proc like this :
try {
final Query query = this.entityManager
.createNamedQuery("PERSIST_PROC_NAME");
query.setParameter("in_param_char", 'V');
resultQuery = (Object[]) query.getSingleResult();
} catch (final Exception ex) {
LOGGER.log(ex);
throw new TechnicalException(ex);
}
to get the two output params :
Integer myInt = (Integer) resultQuery[0];
String myStr = (String) resultQuery[1];
This worked for me.
#Entity
#Table(name="acct")
#NamedNativeQueries({
#NamedNativeQuery(callable=true, name="Account.findOne", query="call sp_get_acct(?), resultClass=Account.class)})
public class Account{
// Code
}
Note : in future if you decide to use default version of findOne then just comment the NamedNativeQueries annotation and JPA will switch to default
This answer might be helpful if you have entity manager
I had a stored procedure to create next number and on server side I have seam framework.
Client side
Object on = entityManager.createNativeQuery("EXEC getNextNmber").executeUpdate();
log.info("New order id: " + on.toString());
Database Side (SQL server) I have stored procedure named getNextNmber
You can use #Query(value = "{call PROC_TEST()}", nativeQuery = true) in your repository. This worked for me.
Attention: use '{' and '}' or else it will not work.
JPA 2.0 doesn't support RETURN values, only calls.
My solution was. Create a FUNCTION calling PROCEDURE.
So, inside JAVA code you execute a NATIVE QUERY calling the oracle FUNCTION.
From JPA 2.1 , JPA supports to call stored procedures using the dynamic StoredProcedureQuery, and the declarative #NamedStoredProcedureQuery.
To call stored procedure we can use Callable Statement in java.sql package.
Try this code:
return em.createNativeQuery("{call getEmployeeDetails(?,?)}",
EmployeeDetails.class)
.setParameter(1, employeeId)
.setParameter(2, companyId).getResultList();
persistence.xml
<persistence-unit name="PU2" transaction-type="RESOURCE_LOCAL">
<non-jta-data-source>jndi_ws2</non-jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties/>
codigo java
String PERSISTENCE_UNIT_NAME = "PU2";
EntityManagerFactory factory2;
factory2 = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
EntityManager em2 = factory2.createEntityManager();
boolean committed = false;
try {
try {
StoredProcedureQuery storedProcedure = em2.createStoredProcedureQuery("PKCREATURNO.INSERTATURNO");
// set parameters
storedProcedure.registerStoredProcedureParameter("inuPKEMPRESA", BigDecimal.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("inuPKSERVICIO", BigDecimal.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("inuPKAREA", BigDecimal.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("isbCHSIGLA", String.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("INUSINCALIFICACION", BigInteger.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("INUTIMBRAR", BigInteger.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("INUTRANSFERIDO", BigInteger.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("INTESTADO", BigInteger.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("inuContador", BigInteger.class, ParameterMode.OUT);
BigDecimal inuPKEMPRESA = BigDecimal.valueOf(1);
BigDecimal inuPKSERVICIO = BigDecimal.valueOf(5);
BigDecimal inuPKAREA = BigDecimal.valueOf(23);
String isbCHSIGLA = "";
BigInteger INUSINCALIFICACION = BigInteger.ZERO;
BigInteger INUTIMBRAR = BigInteger.ZERO;
BigInteger INUTRANSFERIDO = BigInteger.ZERO;
BigInteger INTESTADO = BigInteger.ZERO;
BigInteger inuContador = BigInteger.ZERO;
storedProcedure.setParameter("inuPKEMPRESA", inuPKEMPRESA);
storedProcedure.setParameter("inuPKSERVICIO", inuPKSERVICIO);
storedProcedure.setParameter("inuPKAREA", inuPKAREA);
storedProcedure.setParameter("isbCHSIGLA", isbCHSIGLA);
storedProcedure.setParameter("INUSINCALIFICACION", INUSINCALIFICACION);
storedProcedure.setParameter("INUTIMBRAR", INUTIMBRAR);
storedProcedure.setParameter("INUTRANSFERIDO", INUTRANSFERIDO);
storedProcedure.setParameter("INTESTADO", INTESTADO);
storedProcedure.setParameter("inuContador", inuContador);
// execute SP
storedProcedure.execute();
// get result
try {
long _inuContador = (long) storedProcedure.getOutputParameterValue("inuContador");
varCon = _inuContador + "";
} catch (Exception e) {
}
} finally {
}
} finally {
em2.close();
}
the simplest way is to use JpaRepository
1- Create a stored procedure
CREATE PROCEDURE dbo.getEmployeeDetails
(
#employeeId int,
#companyId int
) AS
BEGIN
SELECT firstName,lastName,gender,address
FROM employee et
WHERE et.employeeId = #employeeId and et.companyId = #companyId
END
2- Create Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class EmployeeDetails {
#Id
private String firstName;
private String lastName;
private String gender;
private String address;
}
3- Create Repository
public interface EmployeeDetailsRepository extends
JpaRepository<EmployeeDetails,String> {
#Query(value = "EXEC dbo.getEmployeeDetails #employeeId=:empId,
#companyId=:compId",nativeQuery =true)
List<EmployeeDetails> getEmployeeList(#Param("employeeId") Integer empId,
#Param("companyId") Integer compId);
}
4- create Controller
#CrossOrigin(origins = "*")
#RestController
#RequestMapping(value = "/api/employee")
public class EmployeeController {
#Autowired
private EmployeeDetailsRepository empRepo;
#GetMapping(value = "/details")
public ResponseEntity<List<EmployeeDetails>> getEmployeeDetails(#RequestParam
String empId, #RequestParam String compId) {
try {
List<EmployeeDetails> result = empRepo.getEmployeeList(
Integer.valueOf(empId),Integer.valueOf(compId));
return ResponseEntity.status(HttpStatus.OK).body(result);
}
catch (Exception ex)
{
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(null);
}
}
}
you can now call http://localhost:8080/api/employee/details?empId=1&compId=25
If you're not too attached to calling this particular procedure with JPA or JDBC, you could use jOOQ, a third party library that generates stubs for all of your stored procedures to simplify calling them, and making the calls type safe.
Calling procedures returning unspecified cursors
In your particular case, the procedure returns an untyped, undeclared cursor (it could return several cursors and interleaved update counts). So, you could call the procedure like this with jOOQ:
GetEmployeeDetails proc = new GetEmployeeDetails();
proc.setEmployeeId(1);
proc.setCompanyId(2);
proc.execute(configuration);
// Iterate over potentially multiple results
for (Result<?> result : proc.getResults()) {
// Print the first result set (your employee query)
System.out.println(result);
// Use your implicit knowledge of the content of the query
// Without type safety
for (Record record : result) {
// All tables / columns are also generated
System.out.println("First name: " + record.get(EMPLOYEE.FIRSTNAME));
System.out.println("Last name: " + record.get(EMPLOYEE.LASTNAME));
System.out.println("Gender: " + record.get(EMPLOYEE.GENDER));
System.out.println("Address: " + record.get(EMPLOYEE.ADDRESS));
}
}
Using an actual table valued function, instead
Personally, I don't really like that feature of a few RDBMS (including SQL Server, MySQL) of returning arbitrary untyped cursors. Why not just declare the result type? SQL Server has powerful table valued functions. E.g. just use this syntax here:
CREATE FUNCTION getEmployeeDetails (#employeeId int, #companyId int)
RETURNS TABLE
AS RETURN
SELECT
firstName,
lastName,
gender,
address
FROM employee et
WHERE et.employeeId = #employeeId
AND et.companyId = #companyId
Now, you have the full type information associated with this function in your catalog, and if you're still using jOOQ, that information will be available to the code generator, so you can call the function like this:
for (GetEmployeeDetailsRecord record : ctx.selectFrom(getEmployeeDetails(1, 2))) {
System.out.println("First name: " + record.getFirstName());
System.out.println("Last name: " + record.getLastName());
System.out.println("Gender: " + record.getGender());
System.out.println("Address: " + record.getAddress());
}
Disclaimer: I work for the company behind jOOQ
I have this select in PostgreSQL:
SELECT "field_1", "field_2","field_3", MIN(COALESCE(NULLIF("field_4",'') ,'TBD')) MINDP,MIN("field_5") MINBOD FROM "MY_TABLE" GROUP BY "field_1", "field_2","field_3"
And I want to use net.java.ao.Query to query my database. My function:
import net.java.ao.Query;
public List<myClass> find() {
String SQL = "SELECT \"field_1\", \"field_2\",\"field_3\", MIN(COALESCE(NULLIF(\"field_4\",'') ,'TBD')) MINDP,MIN(\"field_5\") MINBOD FROM \"MY_TABLE\" GROUP BY \"field_1\", \"field_2\",\"field_3\""
return newArrayList(ao.find(myClass.class, Query.select(SQL)));
}
The problem is: this code return all issues of my table.
When I run this SQL in postgreSQL console it works fine.
My code has different results than SQL console.
Does anybody know why is this happening?
Old question but if somebody steps over this -
Query.select() is not meant to put plain SQL into. It is more of a builder to create 'database independant' query. See some examples here: Finding entities
public MyEntity[] findMyEntities(String fieldValue) {
final Query query = Query.select()
.where("FIELD_VALUE = ?", fieldValue);
// evaluates to something like this
// SELECT * FROM MY_ENTITY WHERE FIELD_VALUE = x;
return ao.find(MyEntity.class, query);
}
This is why there's another method (which you used in your comment), that can handle plain SQL (findWithSQL).
Attention Query and find() often do not work as expected, so you might not get your original query to work using the query builder.
I have a SQL Server Stored Procedure that looks like this:
CREATE PROCEDURE [dbo].[my_stored_procedure]
(
#num INT,
#name VARCHAR(50),
#start_date DATETIME,
#end_date DATETIME
)
AS
BEGIN
...
END
And an Entity Object with a NamedNativeQuery that looks like this:
#Entity
#NamedNativeQuery(
name = "myObject.myStoredProcedure",
query = "call my_stored_procedure(:num, :name, :start_date, :end_date)",
callable = true,
readOnly=true,
resultSetMapping="implicit"
)
#SqlResultSetMapping(
name="implicit",
entities=#EntityResult(entityClass=org.mycompany.object.MyObject.class)
)
public class MyObject implements Serializable {
...
But when I try to call it in my DAO like so:
List<MyObject> objects = (List<MyObject>) getHibernateTemplate().execute(new HibernateCallback() {
#Override
public Object doInHibernate(Session session) throws HibernateException {
return session.getNamedQuery("myObject.myStoredProcedure")
.setInteger("num", num)
.setString("name", name)
.setDate("start_date", startDate)
.setDate("end_date", endDate)
.list();
}
});
But I get this error:
12 May 2010 10:55:43,040 100833 [http-8080-Processor23] ERROR org.hibernate.util.JDBCExceptionReporter - Invalid parameter index 4.
12 May 2010 10:55:43,042 100835 [http-8080-Processor23] FATAL org.mycompany.web.controller.BasePagingController - org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute query; nested exception is org.hibernate.exception.SQLGrammarException: could not execute query
org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute query; nested exception is org.hibernate.exception.SQLGrammarException: could not execute query
It seems like it's expecting another parameter, like a return parameter, but I tried adding a '?' to the call and all the Hibernate documentation suggests against this.
Any help would be appreciated. Thanks
The Hibernate Documentation on calling stored procedures states:
The recommended call form is standard SQL92: { ? = call functionName(<parameters>) } or { ? = call procedureName(<parameters>) }. Native call syntax is not supported.
So the 4th line in your second snippet should probably be
query = "{ ? = call my_stored_procedure(:num, :name, :start_date, :end_date) }",
I don't know, what your procedure returns, but you might want to check the following as well.Even more Hibernate Documentation:
For Sybase or MS SQL server the
following rules apply:
The procedure must return a result
set. Note that since these servers can
return multiple result sets and update
counts, Hibernate will iterate the
results and take the first result that
is a result set as its return value.
Everything else will be discarded.
If you can enable SET NOCOUNT ON in your
procedure it will probably be more
efficient, but this is not a
requirement.