The stored procedure call with cursors throws invalid column name exception - oracle

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.

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.

How to define Oracle Package Procedure in H2 for Testing

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

How to select InnoDB or XtraDB as storage engine in MariaDB in Spring Boot 2 JPA application

I am developing a new application using Spring Boot 2.0.0.M6 and Spring Data JPA. I am using MariaDB v10.
Below is my dev properties file.
spring.jpa.hibernate.ddl-auto=create-drop
spring.datasource.url=jdbc:mariadb://localhost:3306/testdb
spring.datasource.username=user
spring.datasource.password=
spring.jpa.show-sql=true
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
org.hibernate.dialect.Dialect=MariaDB53Dialect
spring.jooq.sql-dialect=MariaDB53Dialect
I get output:
Hibernate: create table hibernate_sequence (next_val bigint) engine=MyISAM
I am not able to change the storage engine. All the tables are being created using storage engine MyISAM.
I am able to create tables manually using other storage engines. But for some reason Spring or Hibernate falls back to MyISAM engine only.
With pure Hibernate-Java application, Hibernate uses InnoDB as default.
INFO: HHH000412: Hibernate Core {5.2.11.Final}
Hibernate: create table hibernate_sequence (next_val bigint) engine=InnoDB
Is there any way to override Database storage engine from the Spring Boot properties?
As described in Spring Boot's documentation, all properties prefixed with spring.jpa.properties are passed through to the underlying JPA provider (Hibernate in this case) with the prefix removed.
The Hibernate property to configure the dialect is hibernate.dialect and its value should be the fully qualified class name of the dialect that you want to use. In this case that's org.hibernate.dialect.MariaDB53Dialect.
Putting the above together, you could set the following property in your application.properties:
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDB53Dialect
With this in place your Spring Boot-based application uses the MariaDB dialect:
2017-11-09 14:18:17.557 INFO 69955 --- [ost-startStop-1] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MariaDB53Dialect
With Hibernate 5.2.12, if I run the MySQLStoredProcedureTest while setting the dialect to MariaDB:
#RequiresDialect(MariaDB53Dialect.class)
public class MySQLStoredProcedureTest
extends BaseEntityManagerFunctionalTestCase {
#Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Phone.class,
};
}
...
}
The Post entity is mapped as follows:
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
private String nickName;
private String address;
#Temporal(TemporalType.TIMESTAMP )
private Date createdOn;
#OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
#OrderColumn(name = "order_id")
private List<Phone> phones = new ArrayList<>();
#Version
private int version;
//Getters and setter omitted for brevity
}
And, when I run the test on MariaDB, Hibernate generates the following schema:
create table Person (
id bigint not null,
address varchar(255),
createdOn datetime(6),
name varchar(255),
nickName varchar(255),
version integer not null,
primary key (id)
) engine=InnoDB
That's because MariaDB53Dialect extends the MariaDBDialect which uses the InnoDBStorageEngine:
public class MariaDBDialect extends MySQL5Dialect {
public MariaDBDialect() {
super();
}
public boolean supportsRowValueConstructorSyntaxInInList() {
return true;
}
#Override
protected MySQLStorageEngine getDefaultMySQLStorageEngine() {
return InnoDBStorageEngine.INSTANCE;
}
}
So, it's impossible to get MyISAM with MariaDB53Dialect when generating the schema with hbm2ddl.
However, you should only be using hbm2ddl to generate the initial script. In a production environment, you should use a tool like FlywayDB.
We actually wrote this in the Hibernate User Guide:
Although the automatic schema generation is very useful for testing
and prototyping purposes, in a production environment, it’s much more
flexible to manage the schema using incremental migration scripts.
We changed this in Hibernate 5.2.8, so I suppose you are using an older version instead, otherwise, there is no explanation why you'd see MyISAM in your hbm2ddl auto-generated schema.
Check the dependencies using:
mvn dependency:tree
and make sure you are really using Hibernate 5.2.12.

H2 JdbcSQLException: "Table not found" with camelcase table & entity name

Using Spring Boot, with Spring Data JPA and H2 in-memory database (in PostgreSQL mode if it makes a difference).
I have a table & entity class named ContentBlock, yet H2 is complaining about missing CONTENT_BLOCK table, when I do a findAll() or findOne():
org.h2.jdbc.JdbcSQLException: Table "CONTENT_BLOCK" not found
I'm not sure if uppercase/camelcase makes a difference, but where does the underscore in CONTENT_BLOCK come from?
In my schema definition:
CREATE TABLE ContentBlock (
id BIGSERIAL PRIMARY KEY,
content TEXT
-- etc
);
And in the entity class:
#Entity
#Table(name = "ContentBlock")
public class ContentBlock {
// ...
}
(Of course I first tried without #Table annotation, as the class name exactly matches the table name.)
With my other tables/entities, with names like Asset, there are no problems, and I don't need to explicitly specify the table name on Java side:
#Entity
public class Asset {
// ...
}
In my setup, the H2 datasource is explicitly defined like this:
#Bean
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScripts("database/init.sql", "database/schema.sql", "database/test_data.sql")
.build();
}
(Contents of init.sql is SET MODE PostgreSQL;)
As workaround, I just renamed the ContentBlock table to Block in schema.sql, with #Table(name = "Block") in the Java class which I still call ContentBlock.
But this is weird, surely you can map a table with camelcase name to an entity somehow?
By default Spring Boot uses SpringNamingStrategy. It extends org.hibernate.cfg.ImprovedNamingStrategy from Hibernate 4. ImprovedNamingStrategy generates underscores in table names.
To map a table with camel case name to an entity you can use org.hibernate.cfg.EJB3NamingStrategy or implement your own.
An example of set a name strategy using properties
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy

Unable to see generated tables generated by hibernate schema export (H2 database)

I am trying to get a small app going using Spring Boot (v1.1.1.RELEASE), and H2 database. In the logging i see that the ddl is correctly generated but it i just cannot find the tables inside the H2 database.
I manually copied the ddl into a db visualizer and the sql is ok. I have no clue what i am missing here. When executing code the JPA persistence layer seems to store the data correctly as i get generated ID's back etc.. I was thinking that i made a mistake in the jdbc url, but they all point to the same file based H2 database. But this database just seems to hold no data.
The JPA object
#Entity
#Table(name = "rawdata", schema = "PUBLIC")
public class RawData {
#Id
#GeneratedValue
private Long id;
#Lob
#Column(name = "payload", nullable = false)
private String payload;
// getters and setters omitted
}
The JPARepository
#Repository
public interface RawDataRepository extends JpaRepository<RawData, Long> {
}
Application properties
spring.datasource.url=jdbc:h2:file:/home/username/dev-db
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
Logging info
org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export
Hibernate: drop table PUBLIC.rawdata if exists
Hibernate: create table PUBLIC.rawdata (id bigint generated by default as identity, payload clob not null, primary key (id))
org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete
Test code
#Autowired
private RawDataRepository repository;
repository.saveAndFlush(new RawData("test"));
System.out.println(repository.count());
So saving a JPA object actually seems to persist the object (the count increases etc) but the data and table structure do not appear in the database. I see that the modified date changes of the database when persisting an object but i seem unable to view the data with for example squirrel/dbvisualizer etc.. Any hints or tips?
The problem is that when the application is shutdown, Hibernate will drop the entire schema, because you have configured spring.jpa.hibernate.ddl-auto=create-drop.
If you change your configuration to spring.jpa.hibernate.ddl-auto=create the schema will not be dropped at the end of the session and you will be able to see the tables that where created along with any data you inserted

Resources