Calling database function JPA repository - spring

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.

Related

How to succinctly call a PL/SQL procedure and return its output variables in Spring with JdbcTemplate?

New to Oracle here. My experience is building web apps which send queries to a database and get a "result set" back, for example in Java with Spring and JdbcTemplate.
For example, here's a code sample with a regular SQL query, using a RowMapper class to turn the rows of a result set into a Java list:
public List<Widget> findAllWidgets() {
return jdbcTemplate.query("SELECT * FROM widgets", new WidgetRowMapper());
}
Now I am tasked with writing a query that calls a PL/SQL stored procedure. This procedure has two input arguments (these I can handle) and two output arguments (let's call them error_code and error_message). I want to send a query to the database that will (a) run the procedure with my inputs and (b) return the two outputs. I would be equally happy to have the two outputs as a "row" or simply bind them to two Java variables.
Here's what I've tried, and it's not throwing an error but it's not getting the output values, either. The Java variables errorCode and errorMessage remain empty strings:
public Map<String,String> callMyProcedure() {
String errorCode="";
String errorMessage="";
jdbcTemplate.update("call myprocedure(?,?,?,?)","input1","input2",errorCode,errorMessage);
return Map.of("errorCode",errorCode,"errorMessage",errorMessage);
}
The question is: How can I capture the values of the PL/SQL procedure's "OUT" variables when calling the procedure from Java with JdbcTemplate?
EDIT: I accepted Alex's answer which doesn't use JdbcTemplate, because it seems to be the better way. My own answer does use JdbcTemplate but takes a lot more code, so if you're searching for something that specifically answers the question, that will do it.
You can use plain JDBC.
final String charlie;
final String zulu;
try (CallableStatement cs = connection.prepareCall("{call myprocedure(?,?,?,?,?,?,?,?)}")) {
cs.setString(1, "foo");
cs.setString(2, "bar");
cs.setString(3, "baz");
cs.setString(4, "whisky");
cs.setString(5, "tango");
cs.setString(6, "foxtrot");
cs.registerOutParameter(7, Types.VARCHAR);
cs.registerOutParameter(8, Types.VARCHAR);
cs.execute();
connection.commit(); // optional
charlie = cs.getString(7);
zulu = cs.getString(8);
}
When using JDBC, it is dangerous to use the getInt method and similar ones, since they convert the type to primitive and zero is replaced by 0. It is better to use a (Integer) cs.getObject(). Similarly, setInt does not support the reference type.
You can get the connection under jdbcTemplate and get output using get methods as getNString
try (Connection connection = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
CallableStatement statement = connection.prepareCall("{call myprocedure(?,?,?,?,?,?,?,?)}");
statement.execute();
statement.getNString(1); // using index or your parameter name
Retrieves the value of the designated NCHAR, NVARCHAR or LONGNVARCHAR parameter as a String in the Java programming language.
I found some guidance from an older question here, and came up with this monstrosity:
public Map<String,Object> callMyProcedure() {
return jdbcTemplate.call(new CallableStatementCreator() {
#Override
public CallableStatement createCallableStatement(Connection connection) throws SQLException {
CallableStatement cs = connection.prepareCall("{call myprocedure(?,?,?,?,?,?,?,?)}");
cs.setString(1,"foo");
cs.setString(2,"bar");
cs.setString(3,"baz");
cs.setString(4,"whisky");
cs.setString(5,"tango");
cs.setString(6,"foxtrot");
cs.registerOutParameter(7, Types.VARCHAR);
cs.registerOutParameter(8, Types.VARCHAR);
return cs;
}
},Arrays.asList(
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlParameter(Types.VARCHAR),
new SqlOutParameter("errorCode",Types.VARCHAR),
new SqlOutParameter("errorMessage",Types.VARCHAR)
));
}
It does work, but I'm looking for an answer that can do the same thing more succinctly. Maybe Spring has added a new interface to JdbcTemplate in the years since that older answer?

Why can JPQLs modifying queries only return void or int?

When i want to modify the database via JPQL i have to mark the query as Transactional and Modiyfing. If i do so, the return type of the method representing the query has to be either void or int(representing the number of edited rows i think). Why are only the two return types allowed? If i do a HTTP-PUT request and update the object with an own JPQL query, i would like to return the updated object again. Whats the best way to do it if the return type of the query has to be void or int? Do i have to do a seperate query/request again which selects the object after it was updated?
EDIT:
Thats how i call the query:
if (inactivityListDTO.getProjectIds().size() > 0) {
projectRepository.updateProjectsIsArchivedByProjectIds(inactivityListDTO.getProjectIds(), inactivityListDTO.getIsArchived());
}
Thats the query:
#Transactional
#Modifying
#Query("UPDATE Project project SET project.isArchived = :isArchived,
project.archivedDate = current_date " +
"WHERE project.id IN :ids")
void updateProjectsIsArchivedByProjectIds(#Param("ids") List<Long> ids, #Param("isArchived") boolean isArchived);
Because it finally boils down to execute a standard UPDATE SQL in the DB , and the UPDATE in standard SQL only returns the number of records being updated and does not return a result set.
And yes , if you need get a record 's value after update , you have to query it again. Alternatively , you should consider using a JPA way to update a record , which first query the object , then update it by changing its state . Something like below (Assume you are using spring #Transactional to manage the transactional boundary):
#Transactional
public void changeEmployeeSalary(Integer employeeId , Integer salary){
Employee employee = entityManager.find(Employee.class , employeeId);
employee.setSalary(salary);
}
In this way , you do not need to query the record again after it is updated and you also do not need to manually write a UPDATE SQL.

JDBC: Call Oracle function like a procedure

I use an Oracle procedure and I batch it.
CallableStatement st = con.prepareCall ("{call MyProc (123)}");
...
st.addBatch ();
Now the procedure was converted into a function. Therefore batching is not longer working and ignoring the return-value does not work too.
If I do not set the "? =" the function is not found.
CallableStatement st = con.prepareCall ("{? = call MyFunc (123)}");
But without batching it takes too long to run all the calls. I have to do many of them.
So is there a way I can ignore the return-value on the JDBC level without touching the function? I want my batching back.
You may of course used the Oracle syntax and ignore the function value in the PL/SQL Block.
Something like this
con.prepareCall("""
declare
v_ignore number;
begin
v_ignore := MyFunc (?);
end;""")
for oracle db it will be like:
private Long getResultOfFunction(final long param1) {
CallableStatementCallback<Long> action = new CallableStatementCallback<Long>() {
public Long doInCallableStatement(CallableStatement cs)
throws SQLException, DataAccessException {
cs.registerOutParameter(1, Types.NUMERIC);
cs.setLong(2, param1);
cs.executeQuery();
return cs.getLong(1);
}
};
return getJdbcTemplate().execute("{call ? := package_name.function_name (?)}", action);
}

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

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

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

Resources