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

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.

Related

Using reserved keyword in JPA / Hibernate entity

Lets assume that I have to use reserved keyword even if it's incorrect. I'm being forced to rename table to new name, which is reserved keyword.
Now I saw every recommended way, how to escape the name, to fail:
A) Using hibernate config property:
hibernate.globally_quoted_identifiers=true
having entity annotated just like:
#Entity
#Table(name = "LIMIT", schema = "something")
does work, but sadly also escapes schema, meaning it actually does not work, as statements like:
select * from "something"."LIMIT"
won't work.
B) Annotating entity as
#Entity(name = "LIMIT")
#Table(name = "\"LIMIT\"", schema = "something")
or
#Entity
#Table(name = "[LIMIT]", schema = "something")
is unable to see existing table. With it I'll get exception as:
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [something.limit]
If I turn of hibernate start-up validation, I'll get exceptions with existing queries, like:
QuerySyntaxException: LimitEntity is not mapped
C) annotating entity just:
#Entity
#Table(name = "LIMIT", schema = "something")
will work just OK, but hibernate fails to auto-generate schema out of it in tests
What is the correct escaping of reserved keywords in JPA/hibernate? We need everything to work, not just something, meaning running app, autogenerating table for tests, ...
version:
<artifactId>hibernate-core</artifactId>
<version>5.2.17.Final</version>

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

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.

Fully qualify Oracle table name in Hibernate

My Setup:
I am using Hibernate 5
with the Oracle JDBC driver oracle.jdbc.driver.OracleDriver and dialect org.hibernate.dialect.Oracle9iDialect.
My modelclasses look like this:
#Entity
#Table(name = "MyModel1", catalog = "DB1")
public class MyModel1 {...}
#Entity
#Table(name = "MyModel2", catalog = "DB2")
public class MyModel2 {...}
My Problem:
I have multiple modelclasses that are spread about different DBs on the same DBserver.
I have to use a single DB-connection to query all of my modelclasses so I connect to DB1 for querying.
Everything would be fine, if Hibernate would generate SQL queries like
select * from DB1.MyModel1;
but it does not. For some reason it makes its queries without the catalog ie.
select * from MyModel1;
which is fine for MyModel1 because I connect to DB1 but I need a fully qualified query for MyModel2 otherwise it throws an exception, because the table of MyModel2 cannot be found in DB1.
Do you know any way to trick Hibernate or JPA or the dialect into building queries with fully qualified tablenames?
With Oracle you have to use the schema attribute:
#Entity
#Table(name = "MyModel1", schema= "DB1")
public class MyModel1 {...}
#Entity
#Table(name = "MyModel2", schema= "DB2")
public class MyModel2 {...}

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