How to call a stored procedure from Java and JPA

JavaSqlStored ProceduresJpaJboss

Java Problem Overview


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:

  1. Parameter names didn't work for me, so try using parameter index.
  2. Correct sql statement {call sp_name(?,?)} instead of call sp_name(?,?)
  3. If stored procedure is returning a result set, even if you know with only one row, getSingleResult wont work
  4. Pass a resultSetMapping name or result class details

Java Solutions


Solution 1 - Java

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.

Solution 2 - Java

> 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

Solution 3 - Java

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.

Solution 4 - Java

  1. 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);
    
  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();
    
  3. 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

Solution 5 - Java

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.

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

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"));

Solution 6 - Java

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.

Solution 7 - Java

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

Solution 8 - Java

The following works for me:

Query query = em.createNativeQuery("BEGIN VALIDACIONES_QPAI.RECALC_COMP_ASSEMBLY('X','X','X',0); END;");
query.executeUpdate();

Solution 9 - Java

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];

Solution 10 - Java

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

Solution 11 - Java

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

Solution 12 - Java

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.

Solution 13 - Java

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.

Solution 14 - Java

From JPA 2.1 , JPA supports to call stored procedures using the dynamic StoredProcedureQuery, and the declarative @NamedStoredProcedureQuery.

Solution 15 - Java

To call stored procedure we can use Callable Statement in java.sql package.

Solution 16 - Java

Try this code:

return em.createNativeQuery("{call getEmployeeDetails(?,?)}",
                               EmployeeDetails.class)           
                               .setParameter(1, employeeId)
                               .setParameter(2, companyId).getResultList();

Solution 17 - Java

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();
    }

Solution 18 - Java

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser431514View Question on Stackoverflow
Solution 1 - JavaPau Kiat WeeView Answer on Stackoverflow
Solution 2 - JavaPascal ThiventView Answer on Stackoverflow
Solution 3 - JavaSean Patrick FloydView Answer on Stackoverflow
Solution 4 - JavaVlad MihalceaView Answer on Stackoverflow
Solution 5 - JavaMalcolm BoekhoffView Answer on Stackoverflow
Solution 6 - JavaDmitry ChornyiView Answer on Stackoverflow
Solution 7 - JavaJamesView Answer on Stackoverflow
Solution 8 - Javaroberto quevedoView Answer on Stackoverflow
Solution 9 - JavaRédaView Answer on Stackoverflow
Solution 10 - JavaAmitView Answer on Stackoverflow
Solution 11 - Javatosha ShahView Answer on Stackoverflow
Solution 12 - JavaEverton OliveiraView Answer on Stackoverflow
Solution 13 - JavaMusashi MiyamotoView Answer on Stackoverflow
Solution 14 - JavaH.OstwalView Answer on Stackoverflow
Solution 15 - JavavinothView Answer on Stackoverflow
Solution 16 - JavaPatricio DominguezView Answer on Stackoverflow
Solution 17 - JavaElohim Julio Cesar CView Answer on Stackoverflow
Solution 18 - JavaRED-ONEView Answer on Stackoverflow