Issue
I am trying to persist a simple class using Spring, with hibernate/JPA and a PostgreSQL database.
The ID
column of the table is a UUID, which I want to generate in code, not in the database.
This should be href="https://thorben-janssen.com/generate-uuids-primary-keys-hibernate/" rel="nofollow noreferrer">straightforward since hibernate and postgres have good support for UUIDs.
Each time I create a new instance and write it with save()
, I get the following error:
o.h.j.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"; SQL statement: INSERT INTO DOODAHS (fieldA, fieldB) VALUES $1, $2) ...
This error indicates that it's expecting the ID column to be auto-populated (with some default value) when a row is inserted.
The class looks like this:
@lombok.Data
@lombok.AllArgsConstructor
@org.springframework.data.relational.core.mapping.Table("doodahs")
public class Doodah {
@org.springframework.data.annotation.Id
@javax.persistence.GeneratedValue(generator = "UUID")
@org.hibernate.annotations.GenericGenerator(name="UUID", strategy = "uuid2")
@javax.persistence.Column(nullable = false, unique = true)
private UUID id;
//... other fields
Things I have tried:
- Annotate the field with
@javax.persistence.Id
(in addition to existing spring Id) - Annotate the field with
@org.hibernate.annotations.Type(type = "pg-uuid")
- Create the UUID myself - results in Spring complaining that it can't find the row with that id.
- Specify
strategy = "org.hibernate.id.UUIDGenerator"
- Annotate class with
@Entity
- Replace spring
@Id
annotation with@javax.persistence.Id
I've seen useful answers here, here and here but none have worked so far.
NB the persistence is being handled by a class which looks like this:
@org.springframework.stereotype.Repository
public interface DoodahRepository extends CrudRepository<Doodah, UUID> ;
The DDL for the table is like this:
CREATE TABLE DOODAHS(id UUID not null, fieldA VARCHAR(10), fieldB VARCHAR(10));
Update
Thanks to Sve Kamenska, with whose help I finally got it working eventually. I ditched the JPA approach - and note that we are using R2DBC, not JDBC, so the answer didn't work straight away. Several sources (here, here, here, here, here and here) indicate that there is no auto Id generation for R2DBC. So you have to add a callback Bean to set your Id
manually.
I updated the class as follows:
@Table("doodahs")
public class Doodah {
@org.springframework.data.annotation.Id
private UUID id;
I also added a Bean as follows:
@Bean
BeforeConvertCallback<Doodah> beforeConvertCallback() {
return (d, row, table) -> {
if (d.getId() == null){
d.id = UUID.randomUUID();
}
return Mono.just(d);
};
}
When a new object (with id = null
, and isNew = true
) is passed to the save()
method, the callback method is invoked, and it sets the id.
Initially I tried using BeforeSaveCallback
but it was being called too late in the process, resulting in the following exception:
JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"....
Solution
Update
There are, at least, 2 types of Spring Data: JPA
and JDBC
.
The issue happens because you are mixing the 2 of them.
So, in order to fix, there are 2 solutions.
Solution 1 - Use Spring Data JDBC only.
Pom.xml dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>
Generate ID.
Spring Data JDBC assumes that ID is generated on database level (like we already figured that out from log). If you try to save an entity with pre-defined id, Spring will assume that it is existing entity and will try to find it in the database and update. That is why you got this error in your attempt #3.
In order to generate UUID, you can:
Leave it to DB (it looks like Postgre allows to do it)
or Fill it in
BeforeSaveCallback
(more details here https://spring.io/blog/2021/09/09/spring-data-jdbc-how-to-use-custom-id-generation)@Bean BeforeSaveCallback<Doodah> beforeSaveCallback() { return (doodah, mutableAggregateChange) -> { if (doodah.id == null) { doodah.id = UUID.randomUUID(); } return doodah; }; }
Solution 2 - Use Spring Data JPA only
Pom.xml dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
Generate ID.
Here you can, actually, use the approach with the UUID auto-generation, like you wanted to do initially
Use
javax.persistence @Entity
annotation instead ofspringdata @Table
on the class-leveland Use
@javax.persistence.Id
and@javax.persistence.GeneratedValue
with all defaults on id-field.@javax.persistence.Id
@javax.persistence.GeneratedValue
private UUID id;
Other notes:
- Specification of generator and strategy is not required, since it will generate based on the type of the
id
field (UUID
in this case). - Specification of
Column(nullable = false, unique = true)
is not required either, since putting@Id
annotation already assumes these constraints.
Initial answer before update
The main question: how do you save the entity? As id-generation is handled by JPA provider, Hibernate in this case. It is done during save method of em
or repository
. In order to create entities and ids Hibernate is looking for javax.persistence
annotations, while you have Spring-specific, so I am wandering how do you save them.
And another question here: the error you provided INSERT INTO DOODAHS (fieldA, fieldB) VALUES $1, $2
shows that there is no id
field in the insert-query at all. Did you just simplified the error-message and removed ID from it? Or this is original error and your code does not even "see" field ID? In that case the issue in not related to the id-generation, but rather is related to the question why your code does not see this field.
Answered By - Sve Kamenska
Answer Checked By - Terry (JavaFixing Volunteer)