How to properly call PostgreSQL functions (stored procedures) within Spring/Hibernate/JPA? - spring

I'm using Spring MVC 4, Hibernate and PostgreSQL 9.3 and have defined function (stored procedure) inside Postgres like this:
CREATE OR REPLACE FUNCTION spa.create_tenant(t_name character varying)
RETURNS void AS
$BODY$
BEGIN
EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I AUTHORIZATION postgres', t_name);
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION spa.create_tenant(character varying)
OWNER TO postgres;
If I run this function inside pgAdmin like this it's working fine:
select spa.create_tenant('somename');
Now I'm trying to run this function from my service like this:
#Override
#Transactional
public void createSchema(String name) {
StoredProcedureQuery sp = em.createStoredProcedureQuery("spa.create_tenant");
sp.registerStoredProcedureParameter("t_name", String.class, ParameterMode.IN);
sp.setParameter("t_name", name);
sp.execute();
}
If I run my method I'm getting following error:
javax.persistence.PersistenceException: org.hibernate.MappingException: No Dialect mapping for JDBC type: 1111
I'm guessing this is because of return type void that is defined in function so I changed return type to look like this:
RETURNS character varying AS
If I run my method again I'm getting this exception instead:
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Error calling CallableStatement.getMoreResults
Does anyone know what is going on here and how to properly call stored procedures in PostgreSQL even with void as return type?

In case you are using also spring data, you could just define a procedure inside your #Repository interface like this,
#Procedure(value = "spa.create_tenant")
public void createTenantOrSomething(#Param("t_name") String tNameOrSomething);
More in the docs.

In your entity class, define a NamedNativeQuery like you would call postgresql function with select.
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.Entity;
#NamedNativeQueries(
value={
// cast is used for Hibernate, to prevent No Dialect mapping for JDBC type: 1111
#NamedNativeQuery(
name = "Tenant.createTenant",
query = "select cast(create_tenant(?) as text)"
)
}
)
#Entity
public class Tenant
hibernate is not able to map void, so a workaround is to cast result as text
public void createSchema(String name) {
Query query = em.createNamedQuery("Tenant.createTenant")
.setParameter(1, name);
query.getSingleResult();
}

Since you're using PostgreSQL, you can, as you've already written, call any stored procedure of type function in SELECT (Oracle, otherwise, would let you only execute functions declared to be read only in selects).
You can use EntityManager.createNativeQuery(SQL).
Since you're using Spring, you can use SimpleJdbcTemplate.query(SQL) to execute any SQL statement, as well.

I think it's the RETURN VOID that's causing the issue. So, changed the FUNCTION definition like this:
CREATE OR REPLACE FUNCTION spa.create_tenant(t_name character varying)
RETURNS bigint AS
$BODY$
BEGIN
EXECUTE format('CREATE SCHEMA IF NOT EXISTS %I AUTHORIZATION postgres', t_name);
RETURN 1;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION spa.create_tenant(character varying)
OWNER TO postgres;
After you changed your function to return some dummy value, change the stored procedure query to this:
StoredProcedureQuery query = entityManager
.createStoredProcedureQuery("spa.create_tenant")
.registerStoredProcedureParameter(1,
Long.class, ParameterMode.OUT)
.registerStoredProcedureParameter(2,
String.class, ParameterMode.IN)
.setParameter(2, name);
query.getResultList();

If you want to keep it simple, just do this:
em.createSQLQuery("SELECT * FROM spa.create_tenant(:t_name) ")
.setParameter("t_name", name)").list();
Notice I used list() intentionally.. for some reason .update() didn't work for me.

PostgreSQL
Hibernate
Kotlin
CREATE OR REPLACE FUNCTION your_procedure() RETURNS text AS $$
BEGIN
RETURN 'Some text';
END;
$$ LANGUAGE plpgsql;
val query = session.createNativeQuery("SELECT your_procedure()")
query.list().map {
println("NativeQuery: $it")
}

For a procedure, try this:
#Procedure("spa.create_tenant")
String createTenant(String tenant);

Related

Calling database function JPA repository

i have a question.
I want to call a database function from my JPARepository in spring boot... My function is the next:
CREATE function sf_getval(seqname varchar2) return NUMBER IS ret_val number :=0;
begin
INSERT INTO schema.table(IDENT ,NAME) VALUES (12321,'Name');
return ret_val;
END sf_getval;
That is not doing anything, i just want a function that insert something in database and return a number, i need this, cant change, is the definition.
Then from JPA i need to consume like this:
#Repository
public interface myRepository extends JpaRepository<Some, SomeId> {
#Query(nativeQuery = true, value = "CALL \"pkgName\".\"sf_getval\"(:name) ")
int sf_getval(#Param("name") String name);
If i do a select pkgname.sf_getval() var from dual; did not work because that violates isolation in the database, is not an option to me. Necesary must be a call command.
I use de repository directly because in my project i've already configure spring.cloud.config and i dont need entityManager or something like that. Is not a solution do a jdbc call.
Thanks, sorry for my english.
Regards
Finally I found the answer:
String call = "{ ? = call FCRLIVE.AP_CH_GET_ACCT_BALANCES(?, ?, ?, ?, ?) }";
CallableStatement cstmt = conn.prepareCall(call);
cstmt.setQueryTimeout(1800);
cstmt.setString(1, inputCode);
cstmt.registerOutParameter(2, Types.NUMBER);
cstmt.executeUpdate();
Is not the exactly code, but getting the connection from the entityManager, in pure JPA is the solution.

How to create a dummy function in H2 embbeded db for integration test

I have a spring boot application that connects to an oracle database. The project contains a service class (userService) that calls the function VALIDATEUSER(USERNAME IN VARCHAR2,PASSWD IN VARCHAR2) in oracle and return 1 if user is valid and 0 invalid.
I need to create the same function in h2 db that always return true for my integration test.
Basically I wanted to created the function in sql script and load it during integration test as follows:
#Test
#Sql(scripts={"classpath:/sql/createFunction.sql"})
public void testUserInfo() throws Exception {
// userService calls VALIDATEUSER function
userService.isUserValid("testdb", "testdb");
}
How to create the function VALIDATEUSER in h2?
Thanks in advance.
You can execute the following SQL in H2 to create a function that accepts two VARCHAR parameters and returns an INTEGER result 1.
CREATE ALIAS VALIDATEUSER AS $$int validateUser(String name, String password) { return 1; }$$
Try this
CREATE ALIAS functionName AS 'int methodName(String name, String password) { return 1; }'
in Java you can use like this
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:h2:mem:", "sa", "");
Statement stat = conn.createStatement();
// Using a custom Java function
stat.execute("CREATE ALIAS functionName AS 'int methodName(String name, String password) { return 1; }' ");
stat.close();
conn.close();

To execute a dynamic query which using DB function

Requirement :
Having a query stored in DB with in a query there is a where condition in that its calling a database function.
Using spring MVC I need to get the query, pass the parameter and get the return value.
This is the query:
SELECT COUNT(*)
FROM IncidentHdr ih, IncidentUser iu
WHERE ih.incidentId = iu.incidentHdr.incidentId
AND get_response_team_access (ih.incidentId, :perscode)
Here get_response_team_access is a DB function which returns an integer. Query works fine as we tested in DB using dummy data.
What I tried So far :
#PersistenceContext
private EntityManager em;
#Override
public Long getAlertCount(String queryString, long persCode) throws DataAccessException {
Query q = em.createQuery(queryString);
q.setParameter("perscode", persCode);
return (long) q.getSingleResult();
}
Throws Exception:
ERROR org.hibernate.hql.internal.ast.ErrorCounter - <AST>:1:293: unexpected AST node: (
antlr.NoViableAltException: unexpected AST node: (
To call DB function from JPQL you have to use FUNCTION keyword.
SELECT COUNT(*) FROM IncidentHdr ih,IncidentUser iu
WHERE ih.incidentId = iu.incidentHdr.incidentId
AND FUNCTION('get_response_team_access',ih.incidentId, :perscode)
Use FUNCTION (formerly FUNC) to call database specific functions from
JPQL
Usage:
You can use FUNCTION to call database functions that are not supported
directly in JPQL and to call user or library specific functions.
Source: http://www.eclipse.org/eclipselink/documentation/2.4/jpa/extensions/j_func.htm

getting error CallableStatementCallback; bad SQL grammar while calling procedure from postgreSQL

i am using Spring-JDBC Support and PostgreSQL and getting error. I am able to run proc from back end. So it's compiled proc.
CREATE OR REPLACE PACKAGE schemaname.my_pkg_name
IS
PROCEDURE update_email(person_id numeric, OUT email_gratitude_id numeric);
END my_pkg_name;
CREATE OR REPLACE PACKAGE BODY schemaname.my_pkg_name
IS
PROCEDURE update_email(person_id numeric, OUT email_gratitude_id numeric) IS
begin
select email_gratitude_id into email_gratitude_id from schemaname.emp_email_dtls_tbl
where person_id=person_id;
end;
END my_pkg_name
private class EmployeeSP extends StoredProcedure
{
private static final String SPROC_NAME = "schemaname.my_pkg_name.update_email";
public EmployeeSP( DataSource datasource )
{
super( datasource, SPROC_NAME );
declareParameter( new SqlParameter("person_id", Types.INTEGER) );
declareParameter( new SqlOutParameter("email_gratitude_id", Types.INTEGER ) );
compile();
}
public Object execute(int emp_id)
{
Map<String,Object> results = super.execute(emp_id,null);
return results.get("email_gratitude_id");
}
};
EmployeeSP tp = new EmployeeSP(template.getDataSource());
tp.execute(123456);
=====================================================================
21:31:48,386 ERROR [com.myproject.dao.EmailDAOImpl] (http--0.0.0.0-8080-6) org.springframework.jdbc.BadSqlGrammarException: CallableStatementCallback; bad SQL grammar [{call schemaname.my_pkg_name.update_email(?, ?)}]; nested exception is org.postgresql.util.PSQLException: ERROR: function schemaname.my_pkg_name.update_email(integer) does not exist
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:98)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:1030)
at org.springframework.jdbc.core.JdbcTemplate.call(JdbcTemplate.java:1064)
try 'CREATE FUNCTION somefunc() RETURNS integer AS $$ ' i.e. instead of 'PACKAGE' use 'FUNCTION'. use can check Chapter 39. PL/pgSQL - SQL Procedural Language!
Also you can check difference between Oracle differences between PostgreSQL's PL/pgSQL language and Oracle's PL/SQL language

Call Oracle Stored procedure from JDBC with complex Input and Output type

I'm so close in solving this question but I'm apparently missing something. My requirement is to call a stored procedure in Oracle from JDBC. The stored procedure takes 1 user-defined Oracle object as INput and another user-defined Oracle object as OUTput. The INput and OUTput objects have mix of both primitive Oracle data types and collection of another set of user-defined objects. I'm successfully able to call the stored procedure and get results back as long as I set NULL for the collection types in the INput and OUTput objects. If I try to create ArrayDescriptor for the list of Oracle objects to send it to the stored procedure I keep hitting roadblocks. So I need help with figuring out how to set the Array to the INput object and set that to CallableStatement. Please note, I am aware of how I can send the primitive type and array as direct inputs to the stored procedure. But I do not want to go that way as we later have to send 10 additional fields to the procedure, I do not want to add them to method signature. Here's the list of classes. Also, there is no compilation errors for the code below.
Package in oracle:
CREATE OR REPLACE PACKAGE testPkg AS
PROCEDURE spGetTestData (
TESTDATA_IN IN TESTDATA_IN_OBJ,
TESTDATA_OUT OUT TESTDATA_OUT_OBJ
);
END;
INput object for the stored procedure:
CREATE OR REPLACE TYPE TESTDATA_IN_OBJ AS OBJECT(
testStr1 VARCHAR2(5),
arrObj1 ARR_OBJ_1_NT);
Array Object as part of INput Object:
create or replace TYPE ARR_OBJ_1_NT AS TABLE OF ARR_OBJ_1_OBJ;
UserDefined Object part of INput Object:
CREATE OR REPLACE TYPE ARR_OBJ_1_OBJ AS OBJECT
(
teststr VARCHAR2(14),
testNumber NUMBER(4),
);
TestDataINObj.java:
import java.sql.Array;
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
public class TestDataINObj implements SQLData
{
private String sql_type = "TESTDATA_IN_OBJ";
protected String testStr1;
protected Array arrObj1;
#Override
public String getSQLTypeName() throws SQLException
{
return this.sql_type;
}
// getter and setter for fields
#Override
public void readSQL(SQLInput stream, String typeName) throws SQLException
{
this.sql_type=typeName;
this.testStr1 = stream.readString();
this.arrObj1 = stream.readArray();
}
#Override
public void writeSQL(SQLOutput stream) throws SQLException
{
stream.writeString(this.testStr1);
stream.writeArray(this.arrObj1);
}
}
TestDataINObjConverter.java:
public class TestDataINObjConverter
{
public static TestDataINObj convertPOJOToDBInObj(Connection connection)
throws SQLException
{
TestDataINObj testDataINObj = new TestDataINObj();
testDataINObj.setTestStr1("some string");
ArrObj1NT[] ArrObj1NTList = ArrObj1NTConverter.convertPOJOToDBObj(); // this will return Java array of ArrObj1NT class
testDataINObj.setArrObj1(getOracleArray("ARR_OBJ_1_NT",connection, ArrObj1NTList));
return testDataINObj;
}
private static Array getOracleArray(final String typeName, Connection connection, ArrObj1NT[] ArrObj1NTList) throws SQLException
{
if (typeName == null)
{
return null;
}
Array oracleArray = new ARRAY(new ArrayDescriptor(typeName, connection), connection, ArrObj1NTList);
return oracleArray;
}
Code that actually executes call to stored procedure:
... //code to get connection
..// connection is of type T4CConnection
Map typeMap = connection.getTypeMap();
typeMap.put("TESTDATA_IN_OBJ", TestDataINObj.class);
typeMap.put("TESTDATA_OUT_OBJ", TestDataOUTObj.class);
typeMap.put("ARR_OBJ_1_NT", ArrObj1NT.class);
TestDataINObj testDataINObj = TestDataINObjConverter.convertPOJOToDBInObj(connection);
getMetaDataCallableStatement = connection.prepareCall("begin " + "testPkg" + ".spGetTestData (?,?);"+ " end;");
getMetaDataCallableStatement.setObject(1, testDataINObj);
getMetaDataCallableStatement.registerOutParameter(2, Types.STRUCT, "TESTDATA_OUT_OBJ");
rs = getMetaDataCallableStatement.executeQuery();
TestDataOUTObj testDataOUTObj = (TestDataOUTObj) getMetaDataCallableStatement.getObject(2, typeMap);
Miscellaneous:
1. The objects are declared in Schema level and is available for the db user to access it.
2. I've not included all of the corresponding Java objects here as it will take more space. They implement SQLData interface and their type names match with DB names. The read and writeSQL methods uses getString, getArray and corresponding setter methods.
This is a very old approach, why are you not using "Oradata" and "Oradatum" interface?
It will save lot of effort.
Your approach leaves a lot of scopr for error, you will have to read the stream in proper manner and check for ordering of fields yourself which can be tricky. Oradata approach will do that for you.
Coming to your approach, Your code is not very clear.
But just to give an overview, StructDescriptor will map to oracle record type and ArrayDescriptor will map to oracle table type, from your code i am confused about whta you are trying to achieve.
I can help if you can make it more clear.

Resources