Skip to content

Can not map Postgres bytea to ByteBuffer and Blob #1408

@hantsy

Description

@hantsy

In the readme of https://github.com/pgjdbc/r2dbc-postgresql, a Postgres bytea can be mapped to byte[], ByteBuffer and R2dbc Blob.

But I tried to create a simple Spring Boot 3.x to experience it, only the byte[] works.

@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(value = "posts")
class Post {

    @Id
    @Column("id")
    private UUID id;

    @Column("title")
    private String title;

    @Column("content")
    private String content;

    @Column("attachment")
    private ByteBuffer attachment;

    @Column("cover_image")
    private byte[] coverImage;

    @Column("cover_image_thumbnail")
    private Blob coverImageThumbnail;

}

And schema sql script is:

CREATE TABLE IF NOT EXISTS posts (
    id UUID DEFAULT uuid_generate_v4(),
    title VARCHAR(255),
    content VARCHAR(255),
    attachment bytea,
    cover_image bytea,
    cover_image_thumbnail bytea,
    PRIMARY KEY (id)
);

In my tests I tried to use these types to read and write a bytea.

@DataR2dbcTest()
@Testcontainers
@Slf4j
public class PostRepositoryTest {

    @Container
    static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer<>("postgres:12")
            .withCopyFileToContainer(MountableFile.forClasspathResource("init.sql"), "/docker-entrypoint-initdb.d/init.sql");

    @DynamicPropertySource
    static void registerDynamicProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.r2dbc.url", () -> "r2dbc:postgresql://"
                + postgreSQLContainer.getHost() + ":" + postgreSQLContainer.getFirstMappedPort()
                + "/" + postgreSQLContainer.getDatabaseName());
        registry.add("spring.r2dbc.username", () -> postgreSQLContainer.getUsername());
        registry.add("spring.r2dbc.password", () -> postgreSQLContainer.getPassword());
    }

    @Autowired
    R2dbcEntityTemplate template;

    @Autowired
    PostRepository posts;

    @BeforeEach
    public void setup() {

    }

    @Test
    public void testDatabaseClientExisted() {
        assertNotNull(template);
    }

    @Test
    public void testPostRepositoryExisted() {
        assertNotNull(posts);
    }

    @Test
    public void testByteBuffer() {
        String s = "testByteBuffer";
        var post = Post.builder().title("r2dbc").attachment(ByteBuffer.wrap(s.getBytes())).build();
        posts.save(post)
                .as(StepVerifier::create)
                .consumeNextWith(saved -> {
                            assertThat(saved.getTitle()).isEqualTo("r2dbc");
                            var attachment = new String(saved.getAttachment().array());
                            assertThat(attachment).isEqualTo(s);
                        }
                )
                .verifyComplete();
    }

    @Test
    public void testByteArray() {
        String s = "testByteArray";
        var post = Post.builder().title("r2dbc").coverImage(s.getBytes()).build();
        posts.save(post)
                .as(StepVerifier::create)
                .consumeNextWith(saved -> {
                            assertThat(saved.getTitle()).isEqualTo("r2dbc");
                            var attachment = new String(saved.getCoverImage());
                            assertThat(attachment).isEqualTo(s);
                        }
                )
                .verifyComplete();
    }

    @Test
    public void testBlob() {
        String s = "testBlob";
        var post = Post.builder().title("r2dbc").coverImageThumbnail(Blob.from(Mono.just(ByteBuffer.wrap(s.getBytes())))).build();
        posts.save(post)
                .as(StepVerifier::create)
                .consumeNextWith(saved -> {
                            assertThat(saved.getTitle()).isEqualTo("r2dbc");
                            var latch = new CountDownLatch(1);
                            Mono.from(saved.getCoverImageThumbnail().stream())
                                    .map(it -> new String(it.array()))
                                    .subscribe(it -> {
                                        assertThat(it).isEqualTo(s);
                                        latch.countDown();
                                    });

                            try {
                                latch.await(1000, TimeUnit.MILLISECONDS);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                )
                .verifyComplete();
    }

}

Only the byte[] case works, other two cases(ByteBuffer and Blob) throw exception like this.

java.lang.AssertionError: expectation "consumeNextWith" failed (expected: onNext(); actual: onError(org.springframework.dao.InvalidDataAccessApiUsageException: Nested entities are not supported))
...

Suppressed: org.springframework.dao.InvalidDataAccessApiUsageException: Nested entities are not supported
		at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.writePropertyInternal(MappingR2dbcConverter.java:409)
		at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.writeProperties(MappingR2dbcConverter.java:376)
		at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.writeInternal(MappingR2dbcConverter.java:347)
		at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.write(MappingR2dbcConverter.java:339)
		at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.write(MappingR2dbcConverter.java:66)
		at org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy.getOutboundRow(DefaultReactiveDataAccessStrategy.java:174)
		at org.springframework.data.r2dbc.core.R2dbcEntityTemplate.lambda$doInsert$7(R2dbcEntityTemplate.java:479)
		at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:152)
		... 79 more

The Sample project is here, https://github.com/hantsy/spring-r2dbc-sample/tree/master/boot-filepart

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions