How to define Oracle Package Procedure in H2 for Testing - oracle

I am testing an spring boot application that reads/writes data to an Oracle DB. This Oracle DB has Oracle packages and in those packages procedures. At some point, the spring boot application calls this procedure via a Entity Repository as follows
#Repository
public interface StudentRepository extends JpaRepository<Student, String> {
#Modifying
#Query(value = "begin sch1.STUDENT_PACKAGE.Set_Grades_To_A('A'); end;", nativeQuery = true)
public void setStudentGradeToA();
}
So, it uses a native query to make the call to to a procedure Set_GradesToA in the STUDENT_PACKAGE package of the sch1 schema.
I am currently testing the functionality of the Spring Boot application and NOT the integration between it and the Oracle database. Therefore, I have decided to use an in-memory database (H2) (with the Oracle compatibility option) to replace the Oracle DB for now. BUT how can I fake out these java package procedures?
I have tried creating an alias in my schema.sql (or data.sql) as follows:
CREATE SCHEMA if not exists sch1;
CREATE ALIAS sch1.STUDENT_PACKAGE AS $$ void Set_Grades_To_A(String s) { new String(s); } $$;
I really don't care what is inside the Set_Grades_To_A procedure what I care about is how to define it.
When I create the alias as above, I'm still getting a Syntax Error.
Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "BEGIN SCH1[*].STUDENT_PACKAGE.Set_Grades_To_A('A'); END; "; SQL statement:
begin sch1.STUDENT_PACKAGE.Set_Grades_To_A('A'); end; [42000-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.message.DbException.getSyntaxError(DbException.java:203)
I guess I have two questions:
How can I fake out a stored procedure inside an Oracle package in the schema sch1?
Why am I getting the Syntax Error above?

Here is what I did.
Question #2: To answer this question I had to change the native query as follows
#Repository
public interface StudentRepository extends JpaRepository<Student, String> {
#Modifying
#Query(value = "call sch1.STUDENT_PACKAGE.Set_Grades_To_A('A')", nativeQuery = true)
public void setStudentGradeToA();
}
Question #1: Three things are involved to answer this. Now that I had changed the native query as above I got a different error:
Caused by: org.h2.jdbc.JdbcSQLException: Database "sch1" not found; SQL statement:
call sch1.STUDENT_PACKAGE.Set_Grades_To_A('A') [90013-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
It was looking for a database called sch1. It seems like the pattern used to call a stored procedure in H2 is database.schema.procedure_name. Since I don't care what that procedure actually does I was able to fake this out by creating a database called sch1 a schema called STUDENT_PACKAGE and the procedure name Set_Grades_To_A
To create the in memory database, you have to set the following property spring.datasource.url in the application.properties file.
Create the sch1 database as follows spring.datasource.url=jdbc:h2:mem:sch1;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=Oracle;INIT=CREATE SCHEMA IF NOT EXISTS first_schema. Notice the database name is sch1
Create the STUDENT_PACKAGE schema by adding this \\;CREATE SCHEMA IF NOT EXISTS STUDENT_PACKAGE to the end of the spring.datasource.url. This adds a second schema called STUDENT_PACKAGE. The property should look like this spring.datasource.url=jdbc:h2:mem:sch1;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=Oracle;INIT=CREATE SCHEMA IF NOT EXISTS first_schema\\;CREATE SCHEMA IF NOT EXISTS STUDENT_PACKAGE
Create a the Set_Grades_To_A stored procedure by adding this to your schema.sql CREATE ALIAS STUDENT_PACKAGE.Set_Grades_To_A AS $$ void setGradesToA(String s) { new StringBuilder(s).reverse().toString(); } $$;

I was able to fix the issue by adding IGNORE_CATALOGS=TRUE
spring.datasource.url = jdbc:h2:mem:testdb;MODE=Oracle;IGNORE_CATALOGS=TRUE
This will ignore the database name

Related

How do I use Spring Data JPA(Hibernate) ORM mapping to a Oracle Tables from other Schema?

The oracle db is version 12c. I use ojdbc8 connector.
I was only granted access to an account, let's call it schema "USR". Login with USR, I can see tables of another schema, let's call it schema "ADM". There is a table "TGT_TABLE" that I want to map it with JPA inside schema ADM. And under USR console, I am able to query "select * from ADM.TGT_TABLE" to get correct result. Now I write up the Entity class as:
#Data
#Entity
#Table(name = "ADM.TGT_TABLE") // or #Table(name = "TGT_TABLE") , Neither worked
public class ApiHeaderLogs {
#Id
#Column(name = "id")
String id;
....
and my config:
spring.jpa.hibernate.ddl-auto=none
# Oracle settings
spring.datasource.url=jdbc:oracle:thin:#10.119.125.70:1540:dhtlm4
spring.datasource.username=USR
spring.datasource.password=******
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
and my test:
Optional<ApiHeaderLogsEntity> ahl = apiHeaderLogsService.findById(id);
I got error:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
....
Caused by: java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist
and the SQL query in console is:
select ... from adm_tgt_table where ... //#Table(name = "ADM.TGT_TABLE")
Or
select ... from tgt_table where ... //#Table(name = "TGT_TABLE")
Clearly it does not address the table ADM.TGT_TABLE.
How can I map to ADM.TGT_TABLE?
My experience was mainly on MySQL, which has no account related to schema access, and my colleague already proposed a solution using JDBC directly... which I really want to avoid, please let me know if there is a proper way to handle this, thanks
You must define explicitely the table_name and the schema in the #Table annotation
Example
#Table(name="TGT_TABLE", schema="ADM")
See the documentation in javax.persistence.Table
Ommiting the schema assumes the deafult owner of the connecting session, which leads to an error.
Neither can you pass a qualified name (ADM.TGT_TABLE) as a table name.

The stored procedure call with cursors throws invalid column name exception

We have a Spring Boot application where we need to connect to Oracle DB and fetch data via stored procedures. Each of our stored procedure has REF_CURSOR as OUT parameters. I am trying the same using #NamedStoredProcedureQuery and #Entity annotations. We are using ojdbc14.jar in pom.xml and Oracle12cDialect in application.properties file. I get the exception Invalid Column Name while executing my piece of code. Also in the entity class I had to introduce a field with annotation #Id, although there is no such field being returned by the REF_CURSOR of my stored procedure. Can this be a problem? Also not defining #Id field is not an option since Hibernate throws an exception then. Any hints would be highly appreciated.
Implementation and Problem is very similar to the question
Invalid column name exception when calling an Oracle stored procedure with ref_cursor through JPA 2.1
But no answer is posted there
The simple example how you can achieve it:
The database schema.
create table MY_PATIENT
(
PAT_RECID number,
PAT_NAME varchar2(100),
constraint PAT_PK primary key(PAT_RECID)
);
create table MY_ORDER
(
ORD_RECID number,
ORD_CODE varchar2(15),
ORD_PATID number,
constraint ORD_PK primary key(ORD_RECID),
constraint ORD_PAT_FK foreign key(ORD_PATID) references MY_PATIENT(PAT_RECID),
constraint ORD_CODE_UNIQUE unique (ORD_CODE)
);
CREATE OR REPLACE PROCEDURE fetch_patient_orders(
patientId IN NUMBER,
patientOrders OUT SYS_REFCURSOR)
AS
BEGIN
OPEN patientOrders FOR
SELECT *
FROM MY_ORDER
WHERE ORD_PATID = patientId;
END;
The entity definition.
#NamedStoredProcedureQueries(
#NamedStoredProcedureQuery(
name = "fetch_patient_orders",
procedureName = "fetch_patient_orders",
resultClasses = Order.class,
parameters = {
#StoredProcedureParameter(
name = "patientId",
type = Long.class,
mode = ParameterMode.IN
),
#StoredProcedureParameter(
name = "patientOrders",
type = Class.class,
mode = ParameterMode.REF_CURSOR
)
}
)
)
#Entity
#Table(name = "MY_ORDER")
public class Order
{
#Id
#Column(name = "ORD_RECID")
private Long id;
#Column(name = "ORD_CODE")
private String code;
#ManyToOne
#JoinColumn(name = "ORD_PATID")
private Patient patient;
}
And usage:
List<Order> orders = session.createNamedStoredProcedureQuery("fetch_patient_orders")
.setParameter("patientId", 2L)
.getResultList();
It was tested with hibernate 5.4.12.Final, ojdbc8.jar, Oracle12cDialect.
See also the hibernate documentation.
The described above approach will work in a pure hibernate application, but not in spring boot app.
According to the spring boot documentation:
Connection to a Production Database
Production database connections can also be auto-configured by using a pooling DataSource. Spring Boot uses the following algorithm for choosing a specific implementation:
We prefer HikariCP for its performance and concurrency. If HikariCP is available, we always choose it.
Otherwise, if the Tomcat pooling DataSource is available, we use it.
If neither HikariCP nor the Tomcat pooling datasource are available and if Commons DBCP2 is available, we use it.
If you use the spring-boot-starter-jdbc or spring-boot-starter-data-jpa “starters”, you automatically get a dependency to HikariCP.
You can bypass that algorithm completely and specify the connection pool to use by setting the spring.datasource.type property.
So, spring boot uses HikariCP JDBC connection pool by default. And it looks like it has a problem with REF_CURSOR parameter registering:
o.h.r.j.i.ResourceRegistryStandardImpl : Registering statement [HikariProxyCallableStatement#770201936 wrapping oracle.jdbc.driver.OracleCallableStatementWrapper#528a6369]
o.h.type.descriptor.sql.BasicBinder : binding parameter [patientId] as [BIGINT] - [2]
o.h.s.i.AbstractServiceRegistryImpl : Initializing service [role=org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport]
o.h.engine.jdbc.spi.SqlExceptionHelper : Error registering REF_CURSOR parameter [patientOrders] [n/a]
When I use the oracle specific data source pool in the application.properties:
# com.zaxxer.hikari.HikariDataSource (default value)
spring.datasource.type=oracle.jdbc.pool.OracleDataSource
all work fine.

Hibernate doesn't return manually INSERTed rows

I'm having an issue with my jpa repository doesn't return rows that I've manually inserted into the database (Oracle) via good old SQL
Insert into SYSTEM.USER (ID,CREDENTIALS,ISADMIN) values (USERSEQ.nextval,'foo',1);
My Jpa Repository
#RepositoryRestResource
public interface UserRepository extends JpaRepository<User, Long> {}
User entity
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idgen")
#SequenceGenerator(initialValue = 1, allocationSize = 1, name = "idgen", sequenceName = "userseq")
private Long id;
#NotNull
private String credentials;
private boolean isAdmin;
}
The super weird thing is that entries that I've inserted via the REST interface works!
So if I create:
User A via REST API
User B via SQL statement
User C via REST API
The result of GET /api/users is A, C
After pulling out all my hair. I think I've narrowed it down to the Flashback feature Oracle has. As only A and C has entries in the Flashback. So Hibernate must do some magic behind the scene.
So my question is. How do I insert a row using SQL so it get a flashback entry also.
If the flashback thing isn't the problem. How do I make Hibernate return all the rows then?
while you are executing the SQL query in Oracle Sql Developer that time it is working own session. and JPA is working own session. i.e. JPA is not able to access the SQL query's records.
solution
Insert into SYSTEM.USER (ID,CREDENTIALS,ISADMIN) values (USERSEQ.nextval,'foo',1);
after that just fire the COMMIT command in Oracle Sql Developer.
it is working for me.

How to give dynamic value to #Table(name=p+"name") in spring JPA

name of the table should be fixed but in my scenario the last part of the table name is profile based so in local it is X but in dev it is Y and so on till Prod. Is there way to add dynamically the value to the table name.
The question tries to implement a bad practice. Don't do that.
Currently, Spring, Hibernate, and JPA does not support your configuration type.
You can use Hibernate interceptors to change the table in the generated SQL statements.
For your case you can define your table class like this:
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name=TableNameReplacer.PLACEHOLDER, schema="MySchema")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class ProfileData implements Serializable {
and define your Hibernate interceptor in a following way:
public class TableNameReplacer extends EmptyInterceptor {
public static final String TABLE_PLACEHOLDER = "{table_placeholder}";
#Override
public String onPrepareStatement(String sql) {
if (sql.contains(TABLE_PLACEHOLDER )) {
String replacement = "{your logic to fill proper table name}";
sql = sql.replace(TABLE_SUFFIX_PLACEHOLDER, replacement);
}
return super.onPrepareStatement(sql);
}
Using this approach you're free to modify generated SQL and replace the table name there as you wish.
I recommend to use good placeholder value which you're sure will not be a part of actual values being saved to the table (or you can only limit this to select statements if you only read the data).

How to test Dao with Dbunit for table which is not having hibernate entity

I have wrote a test class for Dao using DbUnit and dataset
Here is my class :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:config/appContext-test.xml" })
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DBUnitTestExecutionListener.class })
#DBUnitConfiguration(locations = { "testdata/mso_data.xml" })
public class TestMsoJobsDao{
#Resource
private MsoJobsDao msoJobsDao;
#Test
public void testSaveMsoDataIntoTempTable() throws Exception{
List<Object[]> msoHeadendList = new ArrayList<Object[]>();
Timestamp timestamp1 = Timestamp.valueOf("2015-07-01 08:49:50");
Object[] obj1 = {"TEST_MSO_SERVICE_ID_3","America/Detroit","SL","1",timestamp1,"1",timestamp1};
msoList.add(obj1);
msoJobsDao.saveMsoDataIntoTempTable(msoList);
}
}
and data set is :
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<mso_temp id="1" mso_service_id="TEST_MSO_SERVICE_ID_3"
timezone="America/Detroit" customer_group="RC" created_by="1"
created_date="2015-10-05 06:31:59" updated_by="1"
updated_date="2015-10-05 06:31:59"/>
</dataset>
When i am running my test case i am getting org.dbunit.dataset.NoSuchTableException: mso_temp
My problem is i don't need any entity as i am connecting to other database and saving the data from there to the temp table in our application database using PreparedStatement.If i create entity class the test case runs fine.
Is there any way through which DBUnit will consider the table for which entity class is not there.
dbUnit does not use entity classes, it directly uses JDBC. The dbUnit error is table does not exist, and dbUnit does not create tables. Your application / test setup creates the tables from the entity classes, so without the entity, the table does not exist.
For needed tables without entities, test setup must create these tables. For ease, you may want to just put the entity in the test classes folder. Another option is to run DDL that creates the table(s) before running all tests.

Resources