Get stored procedure name from StoredProcedureQuery - jpa

After creating a stored procedure query, I later need to access the procedure name starting from the StoredProcedureQuery instance:
EntityManager em;
StoredProcedureQuery sp = em.createStoredProcedureQuery("sp_name");
// get sp_name from sp instance
// ?
Is this possible using the public methods of StoredProcedureQuery?

you could use the following
if (sp instanceof ProcedureCall) {
String procedureName = ((ProcedureCall) sp).getProcedureName();
}

Related

How to call PostgreSQL function with user-defined type parameter using JdbcTemplate in SpringBoot

I want to call the stored procedure from the SpringBoot app. The procedure has a single parameter of custom-defined type.
create type t_item as (
name text,
count integer
);
The procedure just takes the data passed in and iterates through, performing some actions.
create procedure process_items (
p_items t_item[]
)
language plpgsql
as
$$
declare
v_item t_item;
begin
foreach v_item IN ARRAY p_items
loop
-- do something with v_item
end loop;
end
$$
Then in the SpringBoot app, I have service from where I want to call the procedure. I thought I would use JdbcTemplate. But I don't know how to bind the parameter. Is that even possible with custom-defined types?
#Service
class MyService {
#Autowired
lateinit var objectMapper: ObjectMapper
#Autowired
private lateinit var jdbcTemplate: JdbcTemplate
...
fun processItems(items: ItemModel[]) {
jdbcTemplate.update(
"call process_items(?)",
// ??? how to bind the parameter using ObjectMapper
// or any other method?
)
}
...
}

JPA StoredProcedureQuery: pass UUID as a parameter

I use JPA 2.1 (Hibernate), Postgres 9.6, and I need to pass java.util.UUID as a parameter to StoredProcedureQuery like this:
StoredProcedureQuery proc = em.createStoredProcedureQuery(myProc)
.registerStoredProcedureParameter(0, UUID.class, ParameterMode.IN)
.registerStoredProcedureParameter(1, ...)
.setParameter(0, myUuid)
.setParameter(1, ...);
By default, the Java type UUID is interpreted as Postgres type bytea and I get something like:
ERROR: function my_function(bytea, ...) does not exist.
Of course it does not exist, because my function is:
my_function(UUID, ...)
So, is there any way to define explicitly, which database-level type must be used for a particular parameter?
Might be something like the one we use in entity classes with the annotation:
#Type(type="pg-uuid")
private UUID uuid;
One obvious workaround is to pass the value as a String, and then cast it to UUID inside the function, but...
With EclipseLink 2.7.6 and Postgres 11.8 it works, I expect it should work with Hibernate too; originally I ended with the "bytea" too. First I needed this trivial converter, I have no idea why I have to convert UUID to UUID, but it works.
import java.util.UUID;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class UUIDConverter implements AttributeConverter<UUID, UUID> {
#Override
public UUID convertToDatabaseColumn(final UUID uuid) {
return uuid;
}
#Override
public UUID convertToEntityAttribute(final UUID uuid) {
return uuid;
}
}
The function api is this:
create or replace function generate_namespace(idSubscription uuid) returns integer as
...
Just a side note - I wasn't able to return the whole record as managed entity, because JPA will not receive all required metadata and does not know what (and if) is the primary key etc. So instead of returns Namespace I return only it's primary key and then I call entityManager.find to get the managed entity generated by the function:
final StoredProcedureQuery query = manager.createStoredProcedureQuery("generate_namespace");
query.registerStoredProcedureParameter(1, UUID.class, ParameterMode.IN);
query.setParameter(1, idSubscription);
final Object[] result = (Object[]) query.getSingleResult();
return manager.find(Namespace.class, result[0]);

Returning Postgresql json in Entity Manager

I'm attempting to use ENTITY MANAGER to return a POSTGRESQL 9.6 Stored
Procedure/Function JSON result. THIS RETURNS 1 RECORD ONLY.
**POSTGRESQL TABLES**
TABLE: **customer**
[enter image description here][1]
TABLE: **names**
[enter image description here][2]
TABLE: **address**
[enter image description here][3]
**POSTGRESQL STORED PROCEDURE**
CREATE OR REPLACE FUNCTION jsp_member_main(
IN cid numeric,
OUT t json)
RETURNS json
LANGUAGE 'sql'
COST 100.0
VOLATILE
AS $function$
select json_agg(t)
from (SELECT
c.id customer_id,
n.first_name,
n.last_name,
a.street1,
a.city,
a.state,
a.postal_code
FROM customer c
LEFT OUTER JOIN names n ON (c.name_id = n.id)
LEFT OUTER JOIN address a ON (c.id = a.customer_id)
WHERE c.id=cid
) t;
$function$
 
**POSTGRESQL STORED PROCEDURE QUERY**
select jsp_member_main(99964);
**RETURNS VIA SQL**
[{"customer_id":99964,"first_name":"JOHN","last_name":"SMITH","street1":"123 W. MAIN ST.","city":"SPINDALE","state":"MD","postal_code":"21791"}]
So far, so good. Now I try to use ENTITY MANAGER to return results:
#GET
#Path("SP Member Main")
#Produces({"application/xml", "application/json"})
public JSONObject memberMain() throws JSONException {
EntityManager em = getEntityManager();
StoredProcedureQuery storedProcedure = em.createStoredProcedureQuery("jsp_member_main");
storedProcedure.registerStoredProcedureParameter("cid", Integer.class, ParameterMode.IN);
storedProcedure.registerStoredProcedureParameter("t", JSONObject.class, ParameterMode.OUT);
storedProcedure.setParameter("cid", 99964);
storedProcedure.execute();
JSONObject retval = (JSONObject) storedProcedure.getOutputParameterValue("t");
return retval;
}
No matter what return (OUT) type class I try, I get the following error:
***javax.ejb.EJBException
root cause
javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: A CallableStatement function was executed and the out parameter 1 was of type java.sql.Types=1111 however type java.sql.Types=12 was registered.
Error Code: 0
Call: {?= CALL ross.jsp_member_main(?)}
bind => [2 parameters bound]
Query: ResultSetMappingQuery()***
I've tried as String class, returning toString, JSONArray cobverting JSONObject to JSONArray, java OBJECT, etc. Nothing works.
Is there a way through ENTITY MANAGER to return json from a Postgresql stored procedure/function? Many thanks for your consideration.

Error: Parameter value did not match expected type

I am trying to retrieve a USER from my database using the ID in the WHERE clause. However I am receiving an error and my program is failing.
This is the error I'm receiving when I run my program:
ERROR [org.jboss.as.ejb3.invocation] (default task-19)
JBAS014134: EJB Invocation failed on component CustomerServiceBeanImpl
for method public abstract package.name.entity.ICustomer
package.name.bean.CustomerServiceBean.getCustomerById(long):
javax.ejb.EJBException: java.lang.IllegalArgumentException:
Parameter value [19533] did not match expected type [package.name.entity.User (n/a)]
Note: [19533] is a test value I used.
This is the method that is having the error in the CustomerServiceBeanImpl.java:
#Override
public Customer getCustomerById (final long id)
{
return Customer.getById (this.em, id);
}
This is the method that's being called by the component CustomerServiceBeanImpl:
public static Customer getById (final EntityManager em, final long id)
{
for (final Customer c : em.createNamedQuery ("Customer.getById", Customer.class)
.setParameter ("id", id).setMaxResults (1).getResultList ())
{
return c;
}
return null;
}
The name query is this:
#NamedQuery (name = "Customer.getById",
query = "SELECT o FROM gnf.Customer o WHERE o.user = :id")
In the Customer.java class itself the relevant column is this one:
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn (name = "user_id")
private User user;
Doing a quick check of my ERD the "id" column in my "customer" table has a data type of BIGINT. However I'm not sure if that matters. (PostgreSQL database by the way.)
How can I fix this error?
The WHERE clause in your named query seems to be the problem. The attribute user in your Customer.class is of type User and your query expects it to be a type compatible to long.
...
Parameter value [19533] did not match expected type [package.name.entity.User ...
So if you need more help on this it would be great to see the complete entities User and Customer.
It is happening because in your database the parameter will be a #oneToOne object and you have to call the id inside the Object so you have to give the query as following :
"select user from User user where user.customer.id=:param"

JPA could not locate parameter while working with Stored procedure using Postgres

Any efforts in following problem would be highly appreciable.
I am trying to invoke stored procedure using #NamedStoredProcedureQueries
Here is the code snippet of my entity class
#Entity
#Table(name = "tx_oppertunity")
#NamedStoredProcedureQueries({ //
#NamedStoredProcedureQuery(name = "oppertunity.pastOppertunities", procedureName = "pastOppertunities",resultSetMappings="myMapping")})
#SqlResultSetMappings({
#SqlResultSetMapping(
name = "myMapping",
classes = #ConstructorResult(targetClass = DataHolder.class, columns = { #ColumnResult(name = "oppertunityid") }))
})
public class Oppertunity {
/** The oppertunity id. */
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "GEN_TX_OPPERTUNITY_SEQ")
#SequenceGenerator(allocationSize = 1, name = "GEN_TX_OPPERTUNITY_SEQ", sequenceName = "tx_oppertunity_seq")
#Column(name = "oppertunity_id", nullable = false, unique = true)
private Long oppertunity_id;
// other fields to follow
}
My stored procedure is written in postgres and looks like this.
CREATE OR REPLACE FUNCTION pastOppertunities()
RETURNS TABLE(oppertunityid integer) AS
$BODY$
BEGIN
RETURN QUERY select cast((opp.oppertunity_id) as integer) from tx_oppertunity opp where (opp.end_date)< (NOW() - INTERVAL '2 days');
END;
$BODY$
LANGUAGE plpgsql
Since I am using SQL Resultset mapping to map my result from stored procedure I have defined target class to hold those values as DataHolder.class which looks like this.
public class DataHolder implements Serializable{
private Integer oppertunityid;
/**
* #return the oppertunityid
*/
public Integer getOppertunityid() {
return oppertunityid;
}
/**
* #param oppertunityid the oppertunityid to set
*/
public void setOppertunityid(Integer oppertunityid) {
this.oppertunityid = oppertunityid;
}
public DataHolder(Integer oppertunityid) {
this.oppertunityid = oppertunityid;
}
}
However If I try to run the procedure using, JPA repository
#Procedure(name = "oppertunity.pastOppertunities")
public List<DataHolder> findPastOppertunities();
It gives me following exception,
11:51:07.200 [http-nio-8000-exec-7] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Could not locate parameter registered using that position [1]; nested exception is java.lang.IllegalArgumentException: Could not locate parameter registered using that position [1]] with root cause
org.hibernate.procedure.NoSuchParameterException: Could not locate parameter registered using that position [1]
at org.hibernate.procedure.internal.ProcedureCallImpl.getParameterRegistration(ProcedureCallImpl.java:338) ~[hibernate-core-4.3.10.Final.jar:4.3.10.Final]
at org.hibernate.procedure.internal.ProcedureOutputsImpl.getOutputParameterValue(ProcedureOutputsImpl.java:68) ~[hibernate-core-4.3.10.Final.jar:4.3.10.Final]
at org.hibernate.jpa.internal.StoredProcedureQueryImpl.getOutputParameterValue(StoredProcedureQueryImpl.java:276) ~[hibernate-entitymanager-4.3.10.Final.jar:4.3.10.Final]
at org.springframework.data.jpa.repository.query.StoredProcedureJpaQuery.extractOutputValue(StoredProcedureJpaQuery.java:120) ~[spring-data-jpa-1.7.3.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$ProcedureExecution.doExecute(JpaQueryExecution.java:298) ~[spring-data-jpa-1.7.3.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:74) ~[spring-data-jpa-1.7.3.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:99) ~[spring-data-jpa-1.7.3.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:90) ~[spring-data-jpa-1.7.3.RELEASE.jar:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:415) ~[spring-data-commons-1.9.3.RELEASE.jar:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:393) ~[spring-data-commons-1.9.3.RELEASE.jar:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$DefaultMethodInvokingMethodInterceptor.invoke(RepositoryFactorySupport.java:506) ~[spring-data-commons-1.9.3.RELEASE.jar:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281) ~[spring-tx-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.1.7.RELEASE.jar:4.1.7.RELEASE]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:122) ~[spring-data-jpa-1.7.3.RELEASE.jar:na]
I am not sure what exactly I am missing here all parameters are specified correctly in #SQLREsultSetMapping and I added explicit typecast in procedure to compulsory return Integer value.
There was some post which was suggesting to use Entity manager to call stored procedure, after a long fight I made call using entity manager and it worked like a charm
public List<DataHolder> findPastOppertunities() {
// TODO Auto-generated method stub
StoredProcedureQuery spq = em.createStoredProcedureQuery("pastOppertunities");
List<DataHolder> resultList = spq.getResultList();
return resultList;
//return oppRepo.findPastOppertunities();
}
However I want to understand if its bug with JPA repositories, Why JPA is not able to map result from strored procedure using #SqlResultSetMapping.
Thanks,
Dipesh