Using reserved keyword in JPA / Hibernate entity - spring

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>

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.

Getting aggregate data from table

I want to get aggregate data from a table using spring data.
#Query("SELECT COUNT(*) AS TOTAL_1, MAX(FIELD_1) AS MAX_1 FROM TABLE_NAME WHERE GROUP_ID = :groupId")
Mono<SummaryEntity> getSummary(#Param("groupId" Long groupId));
package com.refinitiv.eit.kv.label.enity.response;
import lombok.AllArgsConstructor;
import lombok.Data;
#Data
#AllArgsConstructor
public class SummaryResponse {
#Column("TOTAL_1")
private Double total_1;
#Column("MAX_1")
private Double max_1;
}
However I get this error : "Could not read property #org.springframework.data.annotation.Id() " ...
There should be no ID, only a single row with the summary data.
Any ideas on getting the summary data?
(the code is more complex but cleared up for this)
First of all, if you need your entity SummaryResponse to be managed by JPA and eventually persist it, you need to annotate it as #Entity and assign it either id or composite id (annotated with #Id).
If you just want to use that DTO for fetching the data, you can use a Spring's interface based projection for that:
public interface SummaryResponseProjection{
getTotal1();
getMax1();
}
and then use it for mapping the results of the query:
#Query("SELECT COUNT(*) AS TOTAL_1, MAX(FIELD_1) AS MAX_1 FROM TABLE_NAME WHERE GROUP_ID = :groupId")
Mono<SummaryResponseProjection> getSummary(#Param("groupId" Long groupId));
Found the reason:
This method was part of a repository defined as ReactiveCrudRepository<RawEntity, Long>, with RawEntity having the id defined.
Moving the method into a new repo defined as ReactiveCrudRepository<SummaryEntity, Void> solves the issue.
Thanks all!

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 {...}

Spring JPA Repository Sub-Query In Native Query

I am trying to run a native query on a repository method so that it returns the results with some counts. It was too complicated to do with JPQL, so I opted for a native query instead.
Repository
#RepositoryRestResource(collectionResourceRel="projects", path="projects")
interface ProjectRepository extends BaseRepository<Project, Long>, ProjectRepositoryCustom {
#Query(
value="SELECT p.id, p.user_id, p.title, p.description, p.created_on, p.version,(SELECT COUNT(0) FROM projectparts WHERE project_id = p.id) AS parts,(SELECT COUNT(0) FROM requests WHERE project_id = p.id) AS requests FROM projects AS p ORDER BY ?#{#pageable}",
countQuery="SELECT COUNT(0) FROM projects",
nativeQuery=true
)
Page<Project> findAll(Pageable pageable)
}
The entity has 2 properties annotated with #Transient so that the info is not persisted to the database. All the data comes back fine except the 2 transient properties which return null for the values. When I copy the query from the console and paste it in MySQL Workbench, the results are as expected and I see the counts that I need. Anyhow, not sure if there is anything else that needs to be done in order to get this native query to work as an annotation. I hard coded a value in the sub-query SELECT 55 FROM... just to see if it was a problem with the count and it still returned as null. I ran the query in Workbench and it works fine.
I've tried changing the transient property type from Integer, Long, BigInteger, long, int... and none of that made a difference. Since I'm using Groovy, I also tried def to let Groovy infer the type and that didn't work either.
I also tried running the project from the terminal instead and it still didn't work. I've tried it on a Mac and Linux and had no luck with displaying the results of the counts.
This will not work. You could use an SQLConstructorExpression however the returned instances would be unmanaged which is a major drawback.
An better option is to create a simple DB view which holds the pieces of summary info for the Project. You can them map the Project entity to both it's table and the associated summary view using the #SecondaryTable functionality of JPA.
https://en.wikibooks.org/wiki/Java_Persistence/Tables#Example_mapping_annotations_for_an_entity_with_multiple_tables
An added benefit is that you can sort and query on the summary values as for any other property.
Updated mapping:
#Entity
#Table(name = "projects")
#SecondaryTable(name = "projects_summary_vw")
public class Project{
//use Integer rather than int to avoid issue outlined here:
//http://stackoverflow.com/a/37160701/1356423
#Column(name = "parts", table = "projects_summary_vw",
insertable="false", updateable="false")
private Integer partsCount;
#Column(name = "requests", table = "requestsCount"
insertable="false", updateable="false")
private Integer requestsCount;
//other mappings as required
}
No Custom query required:
#RepositoryRestResource(collectionResourceRel="projects",
path="projects")
interface ProjectRepository extends BaseRepository<Project, Long>,
ProjectRepositoryCustom {
}
An alternative non-JPA compliant solution may be to use some vendor specific extension rather than a view. Hibernate for example has an #Formula annotation which could be used:
https://docs.jboss.org/hibernate/orm/5.1/javadocs/org/hibernate/annotations/Formula.html
#Entity
#Table(name = "projects")
public class Project{
#Formula("my count query as native sql")
private Integer partsCount;
#Formula("my count query as native sql")
private Integer requestsCount;
//other mappings as required
}

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

Resources