'Spring Data R2DBC PostgreSQL not saving new record with UUID @Id

I have a simple entity, consisting of two UUIDs:

@Table("library")
public class LibraryDao {
    @Id
    private UUID id;
    @NonNull
    private UUID ownerId;
}

I have a corresponding table in PostgreSQL:

CREATE TABLE IF NOT EXISTS library (id UUID PRIMARY KEY, owner_id UUID NOT NULL);

I am using the correct R2DBC drivers (io.r2dbc:r2dbc-postgresql and org.postgresql:postgresql).

To this point, everything works. My applpication runs. But…

Because PostgreSQL does not – at least according to the documentation – have an auto-generating function for UUIDs, I set the id when creating a new LibraryDao instance.

However, when I call the save method in my Repository, I get an exception: Failed to update table [library]. Row with Id [0ed4d7c0-871a-4473-8997-4c9c1ec67a00] does not exist.

It appears that save is being interpretted as update, without a fallback to insert if it doesn't exist.

How am I supposed to insert a new record into my database?



Solution 1:[1]

The UUID can be auto-generated, if you generate it by yourself Hibernate sees the entity with an id and try to update it.

To auto-generate the uuid just use the following annotations on your fields:

@GeneratedValue(generator = "UUID")
@GenericGenerator(
    name = "UUID",
    strategy = "org.hibernate.id.UUIDGenerator",
)

source: https://thorben-janssen.com/generate-uuids-primary-keys-hibernate/

Solution 2:[2]

Since R2DBC doesn't have (yet) auto-generation of UUIDs, and tools used inside Spring have their own mechanisms which prevent sending nulls to DB (so triggers to do the job can't be used) - maybe the optimal solution here is to generate UUIDs with Java (UUID.randomUUID();) and put them in Entities before sending the Entities to DB.

EDIT - concrete solution:

A minor problem with your solution is that the DB installation must be modyfied.

After realizing described problem with insert, I quit trying to do it using the repository save method and switched to "manual" solution using R2DBC DatabaseClient:

dbClient.sql("insert into product_price(id, product_id, price) values(:id, :product_id, :price)")
      .bind("id", UUID.randomUUID())
      .bind("product_id", 1)
      .bind("price", 10.0)
      .fetch()
      .one()
      .subscribe();

And here is the DatabaseClient, configured from ConnectionFactory and enabled to accept named parameters:

@Bean
public DatabaseClient dbClient(ConnectionFactory connectionFactory) {
    return DatabaseClient.builder()
            .connectionFactory(connectionFactory)
            .namedParameters(true)
            .build();
}

The ConnectionFactory is also a easily customisable Bean I have exposed in a config class that extends AbstractR2dbcConfiguration.


And hopefully, save() in reactive repository will be improved to behave like its counterparts in other spring-data repositories.

Solution 3:[3]

However, besides my answer about doing inserts using R2DBC DatabaseClient, there IS a way to do it using reactive repository's save method - by using Persistable interface, which is pretty straight-forward.

Its isNew method can be implemented using entity's version attribut, or by adding a new dedicated attribut (something like boolean isAlreadyPersisted) that could be set explicitly before invoking save(). And maybe there are also some other ways to utilize Persistable that I'm not aware of.

Solution 4:[4]

Because you've provided the @Id. The library needs to figure out, whether the row is new or whether it should exist.

Already answered by Mark Paluch to this question on spring-data-r2dbc issues board. See #275 for further reference. And also you can find useful information from #49.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 jashinhidan
Solution 2
Solution 3
Solution 4 doniadhamov