Dealing with Postgres specific Json/Enum type and Notifier/Listener with R2dbc
Tags: Postgres, Spring Data R2dbc, Spring Data, Spring, R2dbc

Enum type

Postgres database supports custom types, you can create your enum type and limit the inserting values in a set of predefined items.

DO $$ BEGIN

CREATE TYPE post_status AS ENUM( 'DRAFT', 'PENDING_MODERATION', 'PUBLISHED');

EXCEPTION

WHEN duplicate_object THEN null;

END $$;

The scripts will try to create a custom type post_status and only accept one of DRAFT , PENDING_MODERATION , PUBLISHED .

The ConnectionFactoryInitializer read the scripts line by line terminated by ";" which caused exceptions when the application starts up. I move this scripts into the init.sql of Postgres docker service defined in docker-compose.yml , it will be initialized when the docker is starting.

Define a post_status enum data type in the table script.

CREATE TABLE IF NOT EXISTS posts (

// other fields

status post_status default 'DRAFT',

PRIMARY KEY (id)

);

Create a Java Enum class and define a stauts field in the Post entity.

public class Post {

//...

@Column("status")

private Status status;

} enum Status {

DRAFT, PENDING_MODERATION, PUBLISHED;

}

To encode and decode the enum data type automatically, register a Postgres specific EnumCodec in the connection configuration which is shipped with the official Postgres R2dc driver.

PostgresqlConnectionConfiguration.builder()

...

.codecRegistrar(EnumCodec.builder().withEnum("post_status", Post.Status.class).build())

.build()

Then you can use DatabaseClient to read and write data of Java enum type like this.

// read from rowset directly.

row.get("status", Post.Status.class)



// bind java Enum type to parameter placeholder directly

.bind("status", p.getStatus())

When using Spring Data R2dbc, it registered a collection of converters in its MappingContext in the AbstractR2dbcConfiguration . It will detect the data type and Java type and apply the correct converters when reading and writing data.

Ideally when it fails, it will return to use the built-in solution from the baked drivers, but I got exceptions when reading data from databases. Follow the official Spring Data R2dbc reference document, you can create your custom converters to resolve this issue.

Create s @ReadingConverter to convert the data type to Java type.

@ReadingConverter

public class PostReadingConverter implements Converter<Row, Post> { @Override

public Post convert(Row row) {

return Post.builder()

.id(row.get("id", UUID.class))

.title(row.get("title", String.class))

.content(row.get("content", String.class))

.status(row.get("status", Post.Status.class))

.metadata(row.get("metadata", Json.class))

.createdAt(row.get("created_at", LocalDateTime.class))

.updatedAt(row.get("updated_at", LocalDateTime.class))

.version(row.get("version", Long.class))

.build();

}

}

Creating a @WritingConverter to handle the conversion of Java Enum type to enum data type. Spring Data R2dbc has a EnumWriteSupport to simplify the work.

@WritingConverter

public class PostStatusWritingConverter extends EnumWriteSupport<Post.Status> {

}

Register them in the R2dbc configuration.

public class DatabaseConfig extends AbstractR2dbcConfiguration { //... @Override

protected List<Object> getCustomConverters() {

return List.of(new PostReadingConverter(), new PostStatusWritingConverter());

}

}

Spring Data R2dbc can convert Java Enum type to a textual data type and reverse automatically. You can just need to declare the enum column as a textual type, eg.

status VARCHAR(255) default 'DRAFT',

No need custom converters in this case.

The disadvantage of this usage is that it will loss the constraints of the database schema itself, you are responsible for ensuring the correctness of the data being inserted in the tables.