Postgres UUID and Hibernate → no column found - java

I am having troubles with using Postgres UUID type, java.util.UUID and Hibernate.
Everything works fine with native queries, but when I try to use HQL it tells me it could not find id column: column huser0_.id does not exist.
Here's the code for the repository:
import com.xobotun.common.HUser;
import java.util.UUID;
#org.springframework.stereotype.Repository
public interface ReadOnlyUserRepository extends Repository<HUser, UUID> {
// #Query("select u from HUser u where u.id = :id")
#Query(value = "select * from \"user\" where id = :id", nativeQuery = true)
HUser getById(#Param("id") UUID id);
}
This way it prints the expected
HUser(id=fbd3c9e2-8fa4-11e9-bc42-526af7764f64, name=test, about=test, isPermabanned=false, created=2019-06-15T19:37:30.503, lastActive=2019-06-15T19:37:33.512)
But when comment out the native query and use HQL one, it suddenly stops working:
org.postgresql.util.PSQLException: ERROR: column huser0_.id does not exist
Has anyone encountered such an issue? Thanks.
Some more info to understand the question and to check if I have any typos. :)
Table DDL:
CREATE TABLE "user" (
id UUID NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
is_permabanned BOOLEAN DEFAULT FALSE NOT NULL,
created TIMESTAMP DEFAULT now() NOT NULL,
last_active TIMESTAMP,
about TEXT
);
Entity class:
package com.xobotun.common;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.UUID;
#Data
#Entity
#Table(name = "user")
public class HUser {
#Id
#Column(name = "id") //, columnDefinition = "uuid", updatable = false)
// #Type(type="pg-uuid")
private UUID id;
#Column(name = "name")
private String name;
#Column(name = "about")
private String about;
#Column(name = "is_permabanned")
private Boolean isPermabanned;
#Column(name = "created")
private LocalDateTime created;
#Column(name = "last_active")
private LocalDateTime lastActive;
}
As you can see, I experimented with various options on id field, but none of them worked with HQL query.
Java version is 11, PostgreSQL is also 11 and here are related dependencies:
dependencies {
compile 'org.springframework.data:spring-data-jpa:2.1.5.RELEASE'
compile 'javax.persistence:javax.persistence-api:2.2'
compile 'com.vladmihalcea:hibernate-types-52:2.4.4'
implementation 'org.hibernate:hibernate-core:5.4.3.Final'
implementation 'org.postgresql:postgresql:42.2.5.jre7'
}
Also, I tried solutions in these questions, but they were of no help: 1 2 3 4
UPD1:
Here's SQL generated by Hibernate on failing query:
select
huser0_.id as id1_0_,
huser0_.about as about2_0_,
huser0_.created as created3_0_,
huser0_.is_permabanned as is_perma4_0_,
huser0_.last_active as last_act5_0_,
huser0_.name as name6_0_
from
user huser0_
where
huser0_.id=?

Thanks to #JBNizet kind comment I found out the problem was not in weird UUID behaviours, but that Hibernate does not escape identifiers by default.
There are actually three easy solutions to the question:
Do not use reserved keywords, change table name to something else.
Manually escape table name (like #Table(name = "\"user\"") in HUser.java).
Add line hibernate.globally_quoted_identifiers=true to your config. I wonder why is it not true by default... See this for more details.

Related

Spring SQL: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "; expected "identifier", when using INSERT INTO

I am working on a Spring Web Application.
I am using mysql database, but for my unit tests, I want to run them in H2 database.
Test specific application properties:
#Specific spring boot configuration for tests
spring.main.banner-mode=off
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:skel;MODE=MYSQL
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.datasource.user=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create
spring.jpa.defer-datasource-initialization=true
endpoints.enabled=false
# enable test-profile
spring.profiles.active=test
As you can see, my database is in MODE=MYSQL, since my data.sql is in MySQL dialect.
But during initialization of of data.sql I get this error:
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement
"INSERT IGNORE INTO [*]user (username, password, first_name, last_name, enabled,
news_cycle, created_at, updated_at) VALUES('admin#example.com',
'$2a$10$BFNo8gUTorQMtikcFbYVEeAPyX5iCn5BpKglp.eJ2DrFs.bNeXgEu', 'Admin', 'Adminoso',
'TRUE', 'NEVER', '2016-01-01 00:00:00', '2016-01-01 00:00:00')"; expected
"identifier"; SQL statement:
INSERT IGNORE INTO user (username, password, first_name, last_name, enabled,
news_cycle, created_at, updated_at) VALUES('admin#example.com',
'$2a$10$BFNo8gUTorQMtikcFbYVEeAPyX5iCn5BpKglp.eJ2DrFs.bNeXgEu', 'Admin', 'Adminoso',
'TRUE', 'NEVER', '2016-01-01 00:00:00', '2016-01-01 00:00:00') [42001-212]
I suppose from this error you can see the SQL statement that is causing the problem, other statements in data.sql do not seem to cause issues. for example:
INSERT IGNORE INTO department (department_id, department_name, created_at,
updated_at) VALUES (1, 'Marketing', '2016-01-01 00:00:00', '2016-01-01 00:00:00');
My user entity:
import at.qe.skeleton.model.facility.Department;
import at.qe.skeleton.model.facility.Room;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Set;
/**
* Entity representing users.
*
* This class is part of the skeleton project provided for students of the
* courses "Software Architecture" and "Software Engineering" offered by the
* University of Innsbruck.
*/
#Getter
#Setter
#Entity
#EntityListeners(AuditingEntityListener.class)
public class User implements Comparable<User>{
#Id
#Column(name = "username", length = 255)
private String username;
#Column(name = "password",nullable = false,length = 255)
private String password;
#Column(name = "first_Name",nullable = false,length = 255)
private String firstName;
#Column(name = "last_Name",nullable = false,length = 255)
private String lastName;
#Column(name = "enabled", nullable = false)
private boolean enabled;
#Column(name = "news_cycle", nullable = false)
#Enumerated(EnumType.STRING)
private NewsletterCycle newsCycle;
#ElementCollection(targetClass = UserRole.class, fetch = FetchType.EAGER)
#CollectionTable(name = "user_roles")
#Enumerated(EnumType.STRING)
private Set<UserRole> roles;
#ManyToOne
#JoinColumn(name = "department_id")
private Department department;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "manages_department_id", referencedColumnName = "department_id")
private Department managingDepartment;
#ManyToOne
#JoinColumn(name = "assigned_room_id")
private Room assignedRoom;
#OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
private Set<Absence> absences;
#CreatedDate
#Column(name = "created_at", nullable = false)
private LocalDateTime created;
#LastModifiedDate
#Column(name = "updated_at", nullable = false)
private LocalDateTime updated;
#Override
public int compareTo(User o) {
return this.username.compareTo(o.getUsername());
}
#Override
public int hashCode() {
int hash = 7;
hash = 59 * hash + Objects.hashCode(this.getUsername());
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof User)) {
return false;
}
final User other = (User) obj;
return Objects.equals(this.username, other.getUsername());
}
#Override
public String toString() {
return "username: " + getUsername() + " name: " + getFirstName() + " " + getLastName();
}
}
What does this "identifier expected" SQL syntax error mean in this case, I can't figure it out.
I tried making all fields except for username in user nullable, and then tried the same statement only inserting with the username, suspecting that maybe some timestamp datatype was the problem, but that did not change a thing.
I hope somebody can help thank you!
You named your table user which is a reserved keyword in H2. It's also a reserved keyword in the ANSI SQL-99 standard and often in other SQL implementations (sometimes it is a non-reserved keyword, for example in MySQL).
You can use reserved keywords as table names in SQL if you delimit them. H2 supports standard identifier delimiters, which are double-quotes.
I don't know if there's an easy way to make Spring delimit the identifiers in SQL statements. I recall it's pretty wonky. You have to define the entity with built-in double-quotes around its name, like this:
#Entity
#Table(name = "\"user\"")
See https://www.chrouki.com/posts/escape-sql-reserved-keywords-jpa-hibernate/
It's easier if you can just avoid using reserved words for your table names (or other identifiers, including columns, procedures, views, indexes, partitions, etc.).
If I understand your question correctly, you are trying to
populate data.sql (mysql format) to an H2 database.
spring.datasource.url=jdbc:h2:mem:skel;MODE=MYSQL
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
I just doubt that H2 can have enough support on this.
My suggestion is to provide a data-h2.sql for your test. And it will be easy to maintain.

Implementing complex MAX function in Spring Boot/JPA query language

Spring Boot here using JPA/Hibernate and CrudRepository impls for managing persistence to my DB tables.
I have the following MySQL table:
CREATE TABLE IF NOT EXISTS price_scarcity_configs (
price_scarcity_config_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
price_scarcity_config_ref_id VARCHAR(36) NOT NULL,
price_scarcity_config_version BIGINT NOT NULL,
price_scarcity_config_updated_on DATETIME NOT NULL,
price_scarcity_config_fizz INTEGER NOT NULL,
CONSTRAINT pk_price_scarcity_configs PRIMARY KEY (price_scarcity_config_id),
CONSTRAINT uc_price_scarcity_configs_ref_id_and_version UNIQUE (price_scarcity_config_ref_id, price_scarcity_config_version)
);
These records will be versioned and different versions of the "same" record will all share the same price_scarcity_config_ref_id. Hence 2+ records can have the same price_scarcity_config_ref_id but will have two distinct different versions.
I'm also using the following JPA/Hibernate entity to model it:
// Uses Lombok annotations to generate getters/setters, etc.
#MappedSuperclass
#Data
#EqualsAndHashCode(callSuper=false)
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String refId;
}
#Entity
#Table(name = "price_scarcity_configs")
#AttributeOverrides({
#AttributeOverride(name = "id", column = #Column(name = "price_scarcity_config_id")),
#AttributeOverride(name = "refId", column = #Column(name = "price_scarcity_config_ref_id"))
})
#Data
#EqualsAndHashCode(callSuper=false)
public class PriceScarcityConfiguration extends BaseEntity {
#Column(name = "price_scarcity_config_version")
private Long version;
#Column(name = "price_scarcity_config_updated_on")
private Date updatedOn;
#Column(name = "price_scarcity_config_fizz")
private Integer fizz;
}
I am now trying to write the PriceScarcityConfigurationRepository and need a fairly sophisticated query. Given a refId, I need to find the record who matches that ref id and has the highest/max version number. The raw SQL query to perform this is:
select
*
from
price_scarcity_configs pcs
inner join
(
SELECT
price_scarcity_config_ref_id,
MAX(price_scarcity_config_version) as max_ver
FROM
price_scarcity_configs
group by
price_scarcity_config_ref_id
) t
on
t.price_scarcity_config_ref_id = pcs.price_scarcity_config_ref_id
and
t.max_ver = pcs.price_scarcity_config_version;
Given my repository and using JPA/Hibernate's built-in query language/annos, how do I implement this query?
public interface PriceScarcityConfigurationRepository extends CrudRepository<PriceScarcityConfiguration,Long> {
#Query("FROM PriceScarcityConfiguration WHERE ??? HOW TO IMPLEMENT THE ABOVE QUERY HERE ???")
PriceSheetConfiguration fetchLatestVersionByRefId(#Param("refId") String refId);
}
You could use the following query instead and use setMaxResults(1)
FROM PriceScarcityConfiguration p WHERE p.refId = :refId ORDER BY p.version DESC
Or simply use the Spring Data notation
List<PriceSheetConfiguration> findFirstByRefIdOrderByVersionDesc(String refId);

Spring-Data-Jpa: INNER JOIN with Subquery

I have the following problem. I want to execute this query in my spring boot project. I tried to do this with the query annotation in the JPA repository interface. But it says "unexpected SELECT" at the inner join. When I execute this query directly on my mySQL database, it will work.
Do anyone have a solution for this case?
This is my query:
SELECT t1.*
FROM az_manager t1
INNER JOIN
(
SELECT maID, MAX(datum) AS max_date
FROM az_manager
WHERE maID IN (7243, 1)
GROUP BY maID
) t2
ON t1.maID = t2.maID AND t1.datum = t2.max_date
WHERE
t1.maID IN (7243, 1);
This is my class:
#Entity
#Table(name = "az_manager")
#IdClass(TnsWorkingHoursManagerId.class)
#Getter
#Setter
public class TnsWorkingHoursManager extends TnsObject{
#Id
#Column(name = "datum")
private long date;
#Id
#Column(name = "maid")
private int employeeId;
#Column(name = "typid")
private int typeId;
#Column(name = "bemerkung")
private String comment;
#Column(name = "host")
private String host;
#Column(name = "modus")
private byte mode;
public TnsWorkingHoursManager() {
}
}
Here is my try with the JPA repository:
#Query(value = "SELECT azm1 FROM az_manager azm1 INNER JOIN (SELECT maID, MAX(datum) AS max_date FROM az_manager WHERE maID IN(:userIds) GROUP BY maID) azm2 ON azm1.maID = azm2.maID AND azm1.datum = azm2.max_date WHERE azm1.maID IN (:userIds)")
List<TnsWorkingHoursManager> getLastEntries(#Param("userIds") ArrayList<Integer> userIds);
At the second select it says "'SELECT' unexpected"
For anyone else that might stumble upon this question:
If you don't add the nativeQuery = true parameter to the #Query annotation in a Spring Repository, the query will be considered as written in JPQL.
From the JPQL docs:
Subqueries may be used in the WHERE or HAVING clause.
Based on the quote above, the JPQL (Java Persistence Query Language) does not support subqueries in the FROM clause and that is why OP had to make the query native in order for it to work.
I have found a solution.
I forgot to add ", nativeQuery = true" at the end of the line, but in the bracket. Now it works.

Hibernate Criteria select using embedded object (tuple)

In my case I have a SQL query which looks like:
select * from event_instance where (object_id, object_type) in
(<LIST OF TUPLES RETRIEVED FROM SUBQUERY>);
I want to map this on Hibernate Entities and I have a problem with this query. My mapping looks like that:
#Entity
#Table(name="event_instance")
public class AuditEvent {
<OTHER_FIELDS>
#Column( name = "object_type", nullable = false)
private String objectType;
#Column( name ="object_id" , nullable = false)
private Integer objectId;
}
and second entity:
#Entity
#Table(schema = "els" ,name = "acg_objects")
public class AcgObject implements Serializable{
#Id
#Column(name = "acg_id")
private String acgId;
#Id
#Column(name="object_type")
private String objectType;
#Id
#Column(name="object_id")
private Integer objectId;
<OTHER FIELDS>
}
I already run query for getting AcgObjects and for my DAO I'm getting List only thing I want to do is query a touple using criteria like:
crit.add(Restrictions.in("objectType,objectId",<List of tuples>);
Is it possible? I was trying to use #Embedded object but don't know how exactly construct a query for it. Please help
You can do that not in standard SQL nor using criteria; you have to split in two distinct restrictions or using a Session.SQLQuery() if you want to use specific RDBMS (look at SQL WHERE.. IN clause multiple columns for an explanation)

Hibernation annotations, specify column default value

I have a domain object and annotated as follows
#Entity
#Table(name = "REQUEST")
public class Request {
/**
* Unique id for this request
*/
#Id
#GeneratedValue
#Column(name = "EQ_ID")
private long requestId;
/**
*
*/
#Column(name = "EMAIL_ID")
private String emailId;
/**
*
*/
#Column(name = "REQUEST_DATE")
private Date requestDate;
/**
*Getters/setters omitted
*/
}
The column Request_date cannot be null and as per the DDL the default value is sysdate (oracle DB). How do I annotate this field so that if the requestDate property is null,hiberanate automatically inserts sysdate.? Currently it throws error when the field is null,which is very obvious as it cannot be null as per the DB constraints. How do I go about this?
One alternative is to mark this field as transient and the inserts work fine. But the negative aspect is that, I will not be able to retrieve the value (of request_date column).
This is a missing feature in hibernate annotations. Also there exist some workaround as Yok has posted. The problem is that the workaround is vendor dependent and might not work for all DB. In my case,Oracle, it isn't working and has been reported as a bug.
You can put the default value in a columnDefinition. An example would look like:
#Column(name = "REQUEST_DATE", nullable = false, columnDefinition = "date default sysdate")
Using #ColumnDefault (Work for DDL update).
hibernate.hbm2ddl.auto=update
import org.hibernate.annotations.ColumnDefault;
....
#ColumnDefault(value="'#'")
#Column(name = "TEMP_COLUMN", nullable = false)
public String getTempColumn() {
return tempColumn;
}
DDL Generate:
Alter Table YOUR_TABLE add TEMP_COLUMN varchar2(255) default '#' not null;
Assign a default value to the field:
private Date requestDate = new Date();
If you mark your entity with #DynamicInsert e.g.
#Entity
#DynamicInsert
#Table(name = "TABLE_NAME")
public class ClassName implements Serializable {
Hibernate will generate SQL without null values. Then the database will insert its own default value. This does have performance implications See Dynamic Insert.
Make the default in Oracle for the column SYSDATE:
ALTER TABLE APP MODIFY (REQUEST_DATE DEFAULT SYSDATE);
Then, from Hibernate's perspective it can be nullable.
Hibernate will save a NULL to the database. Oracle will convert that to SYSDATE. And everyone will be happy.
I resolved assigning a value to the variable like this private Integer active= 0;
#Entity
#Table(name="products")
public class ServiziTipologia {
#Id
#GeneratedValue
private Integer id;
private String product;
private String description;
private Integer active= 0;

Categories