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

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

Related

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

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

using Spring jdbctemplate to invoke a oracle function returning cursor

Hi I am using spring jdbctemplate to invoke an oracle function which takes an integer input.
Oracle function is :
FUNCTION get_key_types (type_id IN integer)
RETURN tcur_key_types_det
IS
lv_cur tcur_key_types_det;
BEGIN
IF type_id IS NULL
THEN
RAISE KEY_ERROR;
ELSE
OPEN lv_cur FOR
SELECT key_criteria_cd,
key_type_name,
KEY_COLUMN_TXT,
data_type_cd
FROM key_criteria
WHERE criteria_type_id = type_id
ORDER BY key_type_name;
END IF;
RETURN lv_cur;
END get_key_types;
I have a Java class that extends from Class StoredProcedure which passes and integer argument to invoke the oracle function as follows.
public class KeyTypeService extends StoredProcedure{
public KeyTypeService(DataSource dataSource,String sqlString) {
setDataSource(dataSource);
setFunction(true);
setSql(sqlString);
declareParameter(new SqlParameter("type_id",Types.INTEGER));
declareParameter(new SqlOutParameter("functionName",OracleTypes.CURSOR,new KeyTypeMapper()));
compile();
}
public Map execute(int category_id) {
Map<String, Object> inputs = new HashMap<String, Object>();
inputs.put("type_id", category_id);
Map output = execute(inputs);
return output;
}
}
I invoke the oracle function as follows.
int i = 60;
KeyTypeService keyTypeService = new KeyTypeService((DataSource)c.getBean ("DataSource"),"get_key_types");
map = keyTypeService.execute(i);
I get the following error indicating that I am not passing the correct data type expected.
PLS-00306: wrong number or types of arguments in call to 'get_key_types'
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
Any help would be highly appreciated..
Since my comment was correct:
The out parameter must come first.
Spring documentation might not explicitly talk about the order but the parameter order must be the same order as when you would call a pl/sql function without spring. I.e.:
out = call pacakge.function(in)

H2 DB identity() function not working in embedded mode

I am using the H2 database. I have a table with an identity column. I perform an insert and then try and call the identity() function to determine the id assigned to the newly created row.
here is the code snippet I use to call the identity function:
CallableStatement cs = dbConnection.prepareCall("{ ? = call IDENTITY()}");
cs.registerOutParameter(1, Types.BIGINT);
cs.execute();
id = cs.getLong(1);
cs.close();
The problem I run into is that for some reason this function works in server mode, but gives me this error when I call the function in embedded mode:
org.h2.jdbc.JdbcSQLException: Parameter "#1" is not set; SQL statement:
? = call IDENTITY() [90012-160]

Resultset Metadata from Spring JDBCTemplate Query methods

Is there any way I can get resultset object from one of jdbctemplate query methods?
I have a code like
List<ResultSet> rsList = template.query(finalQuery, new RowMapper<ResultSet>() {
public ResultSet mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs;
}
}
);
I wanted to execute my sql statement stored in finalQuery String and get the resultset. The query is a complex join on 6 to 7 tables and I am select 4-5 columns from each table and wanted to get the metadata of those columns to transform data types and data to downstream systems.
If it is a simple query and I am fetching form only one table I can use RowMapper#mapRow and inside that maprow method i can call ResultsetExtractor.extractData to get list of results; but in this case I have complex joins in my query and I am trying to get resultset Object and from that resultset metadata...
The above code is not good because for each result it will return same resultset object and I dont want to store them in list ...
Once more thing is if maprow is called for each result from my query will JDBCTemplate close the rs and connection even though my list has reference to RS object?
Is there any simple method like jdbcTemplate.queryForResultSet(sql) ?
Now I have implemented my own ResultSet Extractor to process and insert data into downstream systems
sourceJdbcTemplate.query(finalQuery, new CustomResultSetProcessor(targetTable, targetJdbcTemplate));
This CustomResultSetProcessor implements ResultSetExtractor and in extractData method I am calling 3 different methods 1 is get ColumnTypes form rs.getMetaData() and second is getColumnTypes of target metadata by running
SELECT NAME, COLTYPE, TBNAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME ='TABLENAME' AND TABCREATOR='TABLE CREATOR'
and in 3rd method I am building the insert statement (prepared) form target columntypes and finally calling that using
new BatchPreparedStatementSetter()
{
#Override
public void setValues(PreparedStatement insertStmt, int i) throws SQLException{} }
Hope this helps to others...
Note that the whole point of Spring JDBC Template is that it automatically closes all resources, including ResultSet, after execution of callback method. Therefore it would be better to extract necessary data inside a callback method and allow Spring to close the ResultSet after it.
If result of data extraction is not a List, you can use ResultSetExtractor instead of RowMapper:
SomeComplexResult r = template.query(finalQuery,
new ResultSetExtractor<SomeComplexResult>() {
public SomeResult extractData(ResultSet) {
// do complex processing of ResultSet and return its result as SomeComplexResult
}
});
Something like this would also work:
Connection con = DataSourceUtils.getConnection(dataSource); // your datasource
Statement s = con.createStatement();
ResultSet rs = s.executeQuery(query); // your query
ResultSetMetaData rsmd = rs.getMetaData();
Although I agree with #axtavt that ResultSetExtractor is preferred in Spring environment, it does force you to execute the query.
The code below does not require you to do so, so that the client code is not required to provide the actual arguments for the query parameters:
public SomeResult getMetadata(String querySql) throws SQLException {
Assert.hasText(querySql);
DataSource ds = jdbcTemplate.getDataSource();
Connection con = null;
PreparedStatement ps = null;
try {
con = DataSourceUtils.getConnection(ds);
ps = con.prepareStatement(querySql);
ResultSetMetaData md = ps.getMetaData(); //<-- the query is compiled, but not executed
return processMetadata(md);
} finally {
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, ds);
}
}

Resources