Error calling CallableStatement.getMoreResults. Calling Stored Procedure using JPA Repository - spring

I use this page to guide myself on how to do it
https://dzone.com/articles/calling-stored-procedures-from-spring-data-jpa
I need to call a stored procedure in an Oracle DB, using Hibernate and Repositories. My procedure receives 1 IN parameter and 2 OUT parameters, I don't really use the OUT parameters in my application.
My procedure receives this
create or replace procedure PRD_DIC_SISTEMA(P_NRO_EVALUACION number
,P_DICTAMEN out varchar2
,P_RESPUESTA out varchar2) is
My service
public ResultsDTO getSystemEvaluation(Long evaluationCode) throws BusinessException {
ResultsDTO response = new ResultsDTO();
dictaminationOperationRepository.systemDictaminationResults(evaluationCode);
return response;
}
Repository
public interface DictaminationOperationRepository extends CrudRepository<DictaminationOperation,DictaminationOperationPK>{
#Transactional
#Procedure(procedureName="prd_dic_sistema")
void systemDictaminationResults(#Param("p_nro_evaluacion") Long evaluationCode);
}
My bean class
#Entity
#Table(name="WP_EVA_DIC_OPERACIONES")
#JsonInclude(Include.NON_NULL)
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(
name = "prd_dic_sistema",
procedureName = "prd_dic_sistema",
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "p_nro_evaluacion", type = Long.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "p_dictamen", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "p_respuesta", type = String.class)
}
)
}
)
public class DictaminationOperation implements Serializable {
attributes, constructor, getters and setters...
}
My error
Error calling CallableStatement.getMoreResults; SQL [prd_dic_sistema];
wrong number of types of arguments in call to 'PRD_DIC_SISTEMA'

Related

How to convert Postgres Json array to Java model class

I am working on POC in that I am getting Json data as return from Postgres function I want to map that data to java object.I am using hibernate type-52 vlad michea I had gone through his blog but there are example with entity . how can I convert Json data to Java model.I am able to get the data by registring the custom dialect with JsonNodeBinayType.But I want to get in List .do i need to custom object mapper implementation?
This is my Model class.
public class CreateUserRequest {
private String username;
private List<String> authoritieses;
private UserPhoneNumber primaryPhoneNumber;
}
public class CreateUserResponse{
private CreateUserRequest user;
private String message;
}
This is My method to call Postgres function
public List<CreateUserResponse> createUsers(List<CreateUserRequest> createUserRequest ) {
StoredProcedureQuery query = (StoredProcedureQuery) this.entityManager.createStoredProcedureQuery("create_users")
.registerStoredProcedureParameter("create_user_request", CreateUserRequestHibernateType.class,
ParameterMode.IN)
.registerStoredProcedureParameter("created_user", String.class, ParameterMode.IN);
query.setParameter("create_user_request",
createUserRequest.toArray(new CreateUserRequest[createUserRequest.size()]));
query.setParameter("created_user", "'spring'");
// List<CreateUserResponse> response = (List<CreateUserResponse>) query.getSingleResult();
Object value =query.getSingleResult();
return null;
}
This is my function which return json
CREATE OR REPLACE FUNCTION "user".create_users
(create_user_request "user".create_user_request[],created_user character varying)
RETURNS json AS $$
DECLARE
response "user".create_user_response[];
BEGIN
---Perform Operation and return the create user response
RETURN to_json(response);
END
$$ LANGUAGE 'plpgsql';

SqlResultSetMapping to POJO class from a NamedNativeQuery throwing 'could not locate appropriate constructor'

I made a #NamedNativeQuery and attached it to an entity 'Doctor', on the same entity I attached a #SqlResultSetMapping which takes the columns of the query's result and maps them to a constructor of a specifically made POJO class. This query is also connected to a JPA method, which resides in the repository of the same entity.
However I keep getting an error that the appropriate constructor could not be located, as if the #SqlResultSetMapping or the POJO constructors are not in sync. (stack trace is at the bottom)
My entity, #NamedNativeQuery and #SqlResultSetMapping:
I tried the query directly on the DB and it gave the expected result, so I am just writing the select clause
#Entity
#NamedNativeQuery(
name =
"Doctor.findFreeExaminationTimes", // name of the JPA method in entity's repository (definition below)
query =
"SELECT on_date AS onDate, LAG(to_time, 1, '00:00') OVER mojWindow AS fromTime, from_time AS toTime " +
"...",
resultSetMapping = "freeTimesByDoctorId" // name of the mapping below
)
#SqlResultSetMapping(
name = "freeTimesByDoctorId", // result set mapping name
classes = #ConstructorResult(
targetClass = DoctorAvailabilityResponse.class, // my POJO class (definition below)
columns = { // order and types are the same as in the select clause above and the POJO constructor below
#ColumnResult(name = "onDate", type = java.sql.Date.class),
#ColumnResult(name = "fromTime", type = java.sql.Time.class),
#ColumnResult(name = "toTime",type = java.sql.Time.class)
}
)
)
public class Doctor extends User {...}
The POJO class which I mention in the #ConstructorResult under 'targetClass' has a constructor with the exact order, number and type, of arguments, specified under 'columns'
My POJO class which should be mapped to the query's result:
public class DoctorAvailabilityResponse {
final private java.sql.Date onDate;
final private java.sql.Time fromTime;
final private java.sql.Time toTime;
public DoctorAvailabilityResponse(java.sql.Date onDate, java.sql.Time fromTime, java.sql.Time toTime) {
this.onDate = onDate;
this.fromTime = fromTime;
this.toTime = toTime;
}
// getters
}
My repository:
#RepositoryRestResource
public interface DoctorRepository extends UserRepository<Doctor> {
// JPA method mapped to the named native query above
List<DoctorAvailabilityResponse> findFreeExaminationTimes(#Param("doctorId") Long doctorId);
}
However when testing this JPA method I get an exception with the message 'could not locate appropriate constructor'.
My test:
#SpringBootTest
public class DoctorTests {
#Autowired
private DoctorRepository doctorRepository;
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
#Test
public void shouldReturnDoctorAvailability() {
// Exception thrown here
List<DoctorAvailabilityResponse> freeTimes = doctorRepository.findFreeExaminationTimes(4L);
LOGGER.info(freeTimes.toString());
}
}
I cannot understand why is this happening. Is there a way to manually map this result set to the POJO while maintaining the JPA repository method?
Stack trace:
org.springframework.dao.InvalidDataAccessApiUsageException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse; nested exception is java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:256)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
...
...
Caused by: java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.resolveConstructor(ConstructorResultColumnProcessor.java:92)
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.performDiscovery(ConstructorResultColumnProcessor.java:45)
at org.hibernate.loader.custom.CustomLoader.autoDiscoverTypes(CustomLoader.java:494)
at org.hibernate.loader.Loader.processResultSet(Loader.java:2333)
at org.hibernate.loader.Loader.getResultSet(Loader.java:2289)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2045)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2007)
at org.hibernate.loader.Loader.doQuery(Loader.java:953)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354)
at org.hibernate.loader.Loader.doList(Loader.java:2810)
at org.hibernate.loader.Loader.doList(Loader.java:2792)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2624)
at org.hibernate.loader.Loader.list(Loader.java:2619)
at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:338)
at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:2137)
at org.hibernate.internal.AbstractSharedSessionContract.list(AbstractSharedSessionContract.java:1134)
at org.hibernate.query.internal.NativeQueryImpl.doList(NativeQueryImpl.java:173)
at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1526)
at org.hibernate.query.Query.getResultList(Query.java:165)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:409)
at com.sun.proxy.$Proxy212.getResultList(Unknown Source)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:126)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:618)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:353)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 73 more
Sanity check and alternative approach
To make a sanity check I deleted the #SqlResultSetMapping, in which case the query is supposed to return a list of 'Object[]' values, and then I tested each individual value in that array to check its type, it showed me that the types are what I assumed them to be 'java.sql.Date' and 'java.sql.Time' twice, and all three of them were in the expected order, (Date, Time, Time), which matches the order of the constructor parameters of my POJO class.
My entity and namedNativeQuery:
#Entity
#NamedNativeQuery(
name =
"Doctor.findFreeExaminationTimes",
query =
"SELECT on_date AS onDate, LAG(to_time, 1, '00:00') OVER mojWindow AS fromTime, from_time AS toTime " +
"..."
)
public class Doctor extends User {...}
My repository with a new return type:
#RepositoryRestResource
public interface DoctorRepository extends UserRepository<Doctor> {
List<Object[]> findFreeExaminationTimes(#Param("doctorId") Long doctorId);
}
My test:
#SpringBootTest
public class DoctorTests {
#Autowired
private DoctorRepository doctorRepository;
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
#Test
public void shouldReturnDoctorAvailability() {
// Exception thrown here
List<DoctorAvailabilityResponse> freeTimes = doctorRepository.findFreeExaminationTimes(4L);
freeTimes.stream().forEach((ft) -> {
// Values are in expected order and of expected types
String classNameOnDate = ft[0].getClass().toString(); // java.sql.Date
String classNameFromTime = ft[1].getClass().toString(); // java.sql.Time
String classNameToTime = ft[1].getClass().toString(); // java.sql.Time
// I suppose the mapping mechanism is supposed to do something like this, but fails for some reason
DoctorAvailabilityResponse dar = new DoctorAvailabilityResponse((Date)ft[0], (Time)ft[1], (Time)ft[2]);
});
LOGGER.info(freeTimes.toString());
}
}
Running this test works perfectly which supposedly shows that the problem is in the #SqlResultSetMapping or in POJO class.
I would appreciate any feedback. Thanks!
SOLUTION
I had to change the types in the #SqlResultSetMapping and in the constructor of my POJO class.
Changed #SqlResultSetMapping
#SqlResultSetMapping(
name = "freeTimesByDoctorId",
classes = #ConstructorResult(
targetClass = DoctorAvailabilityResponse.class,
columns = {
#ColumnResult(name = "onDate", type = String.class),
#ColumnResult(name = "fromTime", type = String.class),
#ColumnResult(name = "toTime",type = String.class)
}
)
)
Changed POJO class constructor
public DoctorAvailabilityResponse(String onDate, String fromTime, String toTime) {
this.onDate = Date.valueOf(onDate);
this.fromTime = Time.valueOf(fromTime);
this.toTime = Time.valueOf(toTime);
}
This alone did not solve my problem however as I got a hibernate exception as mentioned and solved in this SO question. According to this answer I also changed my repository and added an additional annotation.
Changed repository
#RepositoryRestResource
public interface DoctorRepository extends UserRepository<Doctor> {
#Query(nativeQuery = true) // This is added
List<DoctorAvailabilityResponse> findFreeExaminationTimes(#Param("doctorId") Long doctorId);
}
Now everything works, though the question remains why didn't #SqlResultSetMapping map java.sql.* types to the constructor in the first place.
#ConstructorResult does not work very well with java.sql.Date.class or java.sql.Time.class type. A way to fix your issue is to use String.class instead and then convert the String values to Date/Time in DoctorAvailabilityResponse's constructor

wrong number or types of arguments while calling Stored Proc

I am calling Stored Proc from Spring Data JPA :
Procedure is:
create or replace procedure GET_LATEST_GC (arg1 IN VARCHAR2, res1 OUT VARCHAR2, res2 OUT VARCHAR2)
AS
BEGIN
DELETE FROM GC_T WHERE id = arg1;
COMMIT;
BEGIN
SELECT gc.NAME, s.SIP INTO res1, res2
FROM GC_T gc, STAFF_T s WHERE s.id = gc.id
AND START_TIME = (SELECT MAX(START_TIME) FROM GC_T);
EXCEPTION
WHEN others THEN
res1 := '';
END;
END;
Spring Data JPA code
//Repository
public interface ActiveDao extends JpaRepository<GcT,Integer> {
#Procedure(procedureName="GET_LATEST_GC")
Object[] plus1(#Param("arg1") String arg1);
}
//Entity
#Data
#Entity
#NamedStoredProcedureQuery(name = "GET_LATEST_GC",
procedureName = "GET_LATEST_GC", parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "arg1", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "res1", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "res2", type = String.class)})
#Table(schema = "abc", name = "GC_T")
public class GcT implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
private String id;
#Column(name = "NAME")
private String name;
}
//Call
Object[] activeGCInfo =activeDao.plus1(arg);
Procedure is accepting one parameter and I am also passing 1 argument.Then also I am getting this error:
Hibernate: {call GET_LATEST_GC(?,?)}
ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ORA-06550: line 1, column 7:\nPLS-00306: wrong number or types of arguments in call to 'GET_LATEST_GC'\nORA-06550: line 1, column 7:\nPL/SQL: Statement ignored\n
Please let me know where I am doing wrong.
Thank you
Update- Tried to add OUT params also as per suggestion
//Repo
public interface ActiveDao extends JpaRepository<GcT,Integer> {
#Procedure(procedureName="GET_LATEST_GC")
Object[] plus1(#Param("arg1") String arg1,#Param("res1") String res1,#Param("res2") String res2);
}
//Call
Object[] activeGCInfo =activeDao.plus1(arg,"","");
I am sending three args but it is showing me 4 args in error:
Hibernate: {call GET_LATEST_GC(?,?,?,?)} SqlExceptionHelper - ORA-06550: line 1, column 7:\nPLS-00306: wrong number or types of
arguments in call to 'GET_LATEST_GC'\nORA-06550: line 1, column
7:\nPL/SQL: Statement ignored\n
Try changing the result from Object[] to Map<String, Object, along with referencing the proc name with name instead of procedureName. Based on the error, I'm not sure that it will fix it. Spring Data JPA does expect a Map as the return value for multiple output params, so each output param can be found as the key in that Map. But I think the main error is that procedureName maps directly to the db, but name= will map to the correct Entity
//Repo
public interface ActiveDao extends JpaRepository<GcT,Integer> {
#Procedure(name="GET_LATEST_GC")
Map<String, Object> plus1(#Param("arg1") String arg1);
}
//Call
Map<String, Object> activeGCInfo =activeDao.plus1(arg);
Here's what happened:
you declared a procedure with 3 parameters: 1 in and 2 out
you said: "Procedure is accepting one parameter and I am also passing 1 argument"
that was the 1st procedure's parameter (arg1 IN)
it results in "PLS-00306: wrong number or types of arguments"
Of course it does; you need to provide 2 more arguments (datatype should be able to accept VARCHAR2 values returned by the procedure).

Jackson java.util.Optional serialization does not include type ID

I got the following classes:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
...
}
public class MySubEntity extends Entity {
...
}
Now when I serialize that MySubEntity wrapped in an Optional then JSON does not contain the clazz attribute containing the type ID. Bug? When I serialize to List<MySubEntity> or just to MySubEntity it works fine.
Setup: jackson-databind 2.9.4, jackson-datatype-jdk8 2.9.4, serialization is done in Spring Boot application providing a RESTful web service.
EDIT: Here is the Spring REST method that returns the Optional:
#RequestMapping(method = RequestMethod.GET, value = "/{uuid}", produces = "application/json")
public Optional<MySubEntity> findByUuid(#PathVariable("uuid") String uuid) {
...
}
EDIT:
I made a SSCCE with a simple Spring REST controller and two tests. The first test is using ObjectMapper directly which is successful in deserialization although the clazz is missing. The second test calls the REST controller and fails with an error because clazz is missing:
Error while extracting response for type [class com.example.demo.MySubEntity] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'
This, indeed, looks like a bug. There is one workaround that I can suggest for this case, is to use JsonTypeInfo.As.EXISTING_PROPERTY and add field clazz to your Entity. There only one case with this approach is that the clazz must be set in java code manually. However this is easy to overcome.
Here is the full code for suggested workaround:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY, //field must be present in the POJO
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
#JsonProperty
private String uuid;
//Here we have to initialize this field manually.
//Here is the simple workaround to initialize in automatically
#JsonProperty
private String clazz = this.getClass().getSimpleName();
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
}

Spring Data JPA : Stored Procedure with Schema Name

Please if anyone has experienced calling oracle stored procedure from spring data specifying schema, package and procedure name.
I have the following entity :
#Entity
#Table(name = "ENTITY", schema = "SCHEMA_ENTITY")
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(name = "name1",
procedureName = "packageName.procName",
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "param1", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "return_value", type = BigDecimal.class)
})})
public class EntityExp {
#Id
private Long keyId;
...
}
Repository :
public interface EntityRepository extends JpaRepository<EntityExp, Long> {
#Procedure(name = "name1")
BigDecimal test(#Param("param1") String param1);
}
In the service implemetation, after autowiring it, I call the procedure like :
BigDecimal returnVal = entityRepository.test(param1);
The oracle stored proc definition is :
create or replace PACKAGE packageName as
function procName(param1 IN VARCHAR)
RETURN NUMBER;
END packageName;
create or replace PACKAGE BODY packageName
IS
function procName (param1 IN VARCHAR)
RETURN NUMBER
IS
BEGIN
return 1;
END;
END;
The procedure works fine if I call it using PL/Sql..
And I got the following error :
PLS-00201: identifier 'package.procName' must be declared
I also tested many configs, like specifying the schema in the procedure:
#Procedure(name = "SCHEMA_ENTITY.name1")
BigDecimal test(#Param("param1") String param1);
But still fails...
I can't find any example using schema + package + procedureName while calling the stored procedure...
Any suggestions ?
I'm not quite sure that understood your issue completely, but I'll try to share some information since I had a related issue recently.
The first item is related to the way you call the stored procedure. It looks like the returned type of the stored procedure is not currently supported by Spring-data. For more info link. However, with void it is working fine.
I resolved that by creating a separate Repository where I call the procedures which return result through the entityManager. An example which illustrates that link.
The second item is about schema name. I believe when you resolve the first item you could use this question to try to resolve it. In a nutshell, I set the schema name in the procedureName annotation attribute name.
#NamedStoredProcedureQuery(
name="procName",
procedureName="<schema_name>.proc_name"
)
#Entity
#Table
public class User {
...
}
Hope it helps.

Resources