r2dbc / r2dbc-h2

R2DBC H2 Implementation
Apache License 2.0
200 stars 45 forks source link

Getting MappingException when reading TINYINT value from H2 database as boolean/Boolean #170

Closed abhinaba-chakraborty-by closed 4 years ago

abhinaba-chakraborty-by commented 4 years ago

Bug Report

I am using R2DBC H2 for integration testing my reactive application (which is based on Spring Webflux , Java). Getting no issue when saving PrepTaskEntity but when trying to read, getting the following error. On debugging through the classes, it seems that the tiny int value is being read as Bytes and BooleanCodec of H2 is unable to decode it.

Versions

Current Behavior

Stack trace ``` org.springframework.data.mapping.MappingException: Could not read property @org.springframework.data.relational.core.mapping.Column(value="is_active")private boolean com.example.demosvc.persistence.entities.PrepTaskEntity.isActive from result set! at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.readFrom(MappingR2dbcConverter.java:172) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): |_ checkpoint ⇢ Handler com.example.demosvc.controllers.PrepTaskController#getPrepTasksForGivenBU(String) [DispatcherHandler] |_ checkpoint ⇢ com.example.demosvc.filters.TenancyContextFilter [DefaultWebFilterChain] |_ checkpoint ⇢ HTTP GET "/sites/BU-002/prepTasks" [ExceptionHandlingWebHandler] Stack trace: at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.readFrom(MappingR2dbcConverter.java:172) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.read(MappingR2dbcConverter.java:133) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.read(MappingR2dbcConverter.java:116) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.EntityRowMapper.apply(EntityRowMapper.java:46) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.EntityRowMapper.apply(EntityRowMapper.java:29) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at io.r2dbc.h2.H2Result.lambda$map$0(H2Result.java:67) ~[r2dbc-h2-0.8.4.RELEASE.jar:0.8.4.RELEASE] at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:100) ~[reactor-core-3.3.8.RELEASE.jar:3.3.8.RELEASE] at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73) ~[reactor-core-3.3.8.RELEASE.jar:3.3.8.RELEASE] ... Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.Byte] to type [boolean] at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE] at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE] at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:174) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE] at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.getPotentiallyConvertedSimpleRead(MappingR2dbcConverter.java:263) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.readValue(MappingR2dbcConverter.java:187) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.readFrom(MappingR2dbcConverter.java:169) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.read(MappingR2dbcConverter.java:133) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.read(MappingR2dbcConverter.java:116) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.EntityRowMapper.apply(EntityRowMapper.java:46) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at org.springframework.data.r2dbc.convert.EntityRowMapper.apply(EntityRowMapper.java:29) ~[spring-data-r2dbc-1.1.2.RELEASE.jar:1.1.2.RELEASE] at io.r2dbc.h2.H2Result.lambda$map$0(H2Result.java:67) ~[r2dbc-h2-0.8.4.RELEASE.jar:0.8.4.RELEASE] ... ```

Table schema

Input Code ```sql CREATE TABLE `prep_task` ( `id` SERIAL, `external_id` VARCHAR(45) NOT NULL, `name` VARCHAR(45) NOT NULL, `description` VARCHAR(100) NULL, `is_active` TINYINT, PRIMARY KEY (`id`) ); ```

Steps to reproduce

Input Code ```java @Table("prep_task") public class PrepTaskEntity implements Persistable { @Id @Column("id") private Long id; @Column("external_id") private String externalId; @Column("name") private String name; @Column("description") private String description; @Column("is_active") private boolean isActive; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getExternalId() { return externalId; } public void setExternalId(String externalId) { this.externalId = externalId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isActive() { return isActive; } public void setActive(boolean active) { isActive = active; } @Override public boolean isNew() { return id == null; } } ```

Expected behavior/code

Should be able to read the is_active column value in the boolean isActive.

Additional context

H2 Connection Config (using Routing Connection Factory because of multi-tenancy)

@Configuration
@Profile(Application.Profiles.INTEGRATION_TEST)
public class H2ConnectionConfig extends AbstractR2dbcConfiguration {

  private final R2dbcConfigProperties r2dbcConfigProperties;

  @Autowired
  public H2ConnectionConfig(R2dbcConfigProperties r2dbcConfigProperties) {
    this.r2dbcConfigProperties = r2dbcConfigProperties;
  }

  @Override
  @Bean
  public ConnectionFactory connectionFactory() {
    RoutingConnectionFactory connectionFactory = new RoutingConnectionFactory();
    Map<String, ConnectionFactory> factories = new HashMap<>();
    H2ConnectionConfiguration.Builder configurationBuilder = H2ConnectionConfiguration.builder()
        .property(H2ConnectionOption.MODE, "MySQL")
        .property(H2ConnectionOption.DB_CLOSE_DELAY, "-1")
        .property(H2ConnectionOption.DB_CLOSE_ON_EXIT, "false");
    for (String tenant : r2dbcConfigProperties.getTenants()) {
      String databaseName = r2dbcConfigProperties.getDbPrefix() + tenant;
      factories.put(tenant, new H2ConnectionFactory(configurationBuilder.inMemory(databaseName).build()));
    }
    connectionFactory.setTargetConnectionFactories(factories);
    connectionFactory.setDefaultTargetConnectionFactory(factories.get("default"));
    return connectionFactory;
  }

}
mp911de commented 4 years ago

Can you please attach the cause of MappingException?

abhinaba-chakraborty-by commented 4 years ago

@mp911de You mean stacktrace? It's there.

mp911de commented 4 years ago

No, I mean the cause of MappingException.

abhinaba-chakraborty-by commented 4 years ago

@mp911de I just updated the description. Probably it's now a bit more clearer?

mp911de commented 4 years ago

I have difficulties to understand why it's not possible to attach the cause of MappingException. Instead the stack trace is truncated and showing mostly Spring Data stack elements. You're not required to debug the code nor investigate the underlying issue. Just providing the full error context is sufficient.

abhinaba-chakraborty-by commented 4 years ago

@mp911de My bad. Missed that in the stacktrace. Thought to omit the unnecessary ones (which had the trace from Reactor classes). Adding that.

mp911de commented 4 years ago

Thanks a lot, ConverterNotFoundException is what we were looking for.

That being said, there's nothing we can do on the driver level as the conversion is related to Spring Data. Please register a @ReadingConverter Converter<Byte, Boolean> that implements your conversion logic and register it with R2dbcCustomConversions. See https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#mapping.configuration for further reference.

Closing this ticket as that's not a driver issue.

abhinaba-chakraborty-by commented 4 years ago

Thanks @mp911de for the guide.