The application I am working on is connecting to 2 postgresql databases at the same time.
The application is trying to perform an insert in a table with 3 columns on one of the two databases. The columns of the table are a string, a json and a timestamp.
When the application tries to perform the insert, it is getting the following error:
org.springframework.dao.InvalidDataAccessApiUsageException: Nested entities are not supported
at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.writePropertyInternal(MappingR2dbcConverter.java:413)
at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.writeProperties(MappingR2dbcConverter.java:380)
at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.writeInternal(MappingR2dbcConverter.java:358)
at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.write(MappingR2dbcConverter.java:350)
at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.write(MappingR2dbcConverter.java:64)
at org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy.getOutboundRow(DefaultReactiveDataAccessStrategy.java:200)
at org.springframework.data.r2dbc.core.R2dbcEntityTemplate.lambda$doInsert$9(R2dbcEntityTemplate.java:548)
at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:152)
at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:57)
at reactor.core.publisher.MonoUsingWhen.subscribe(MonoUsingWhen.java:87)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:157)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:210)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815)
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:249)
at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:387)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:151)
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:295)
at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1815)
at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:159)
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:102)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:259)
at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onComplete(ScopePassingSpanSubscriber.java:102)
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:401)
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:420)
at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:474)
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:685)
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:829)
In the stack it says that "nested entities are not supported" but I think that the issue here is that the framework is not using the converters I have defined, in fact the entity doesn't have any relations with other entities. Following are the details of the entity the application fails to insert:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("my_tab")
public class MyTab implements Persistable<String> {
@Id
@Column("identifier")
private String identifier;
@Column("data")
private MyData data;
@Column("created")
private LocalDateTime created;
@Transient
@Override
public String getId() {
return identifier;
}
}
MyData is basically a PoJo with other fields.
In order to allow the application to connect to two databases I have followed what is on the guide:
https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#r2dbc.multiple-databases
Following are the connection factory objects I have defined:
@Configuration
@EnableR2dbcRepositories(basePackages = {"com.my.package.entity.persistence", "com.my.package.repository.myTab"}, entityOperationsRef =
"myTabR2dbcEntityOperations")
public class FirstDatabaseConfig {
private static final Logger LOG = LoggerFactory.getLogger(FirstDatabaseConfig.class);
@Autowired
private MyDataToJsonConverter myDataWriteConverter;
@Autowired
private JsonToMyDataConverter myDataReadConverter;
/*
Used to configure custom object converters for the database.
*/
@Bean
public R2dbcCustomConversions r2dbcCustomConversions() {
Collection<?> converters = Arrays.asList(myDataWriteConverter, myDataReadConverter);
R2dbcCustomConversions conversions = R2dbcCustomConversions.of(PostgresDialect.INSTANCE,converters);
return conversions;
}
@Bean(name = "firstDatabaseConnectionFactory")
public ConnectionFactory connectionFactory() {
// URL, password, username are taken from an application.properties file
ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.parse(url);
ConnectionFactoryOptions.Builder builder = ConnectionFactoryOptions.builder().from(connectionFactoryOptions);
if (!StringUtils.isEmpty(username)) {
builder = builder.option(ConnectionFactoryOptions.USER, username);
}
if (!StringUtils.isEmpty(password)) {
builder = builder.option(ConnectionFactoryOptions.PASSWORD, password);
}
return ConnectionFactories.get(builder.build());
}
@Bean
public R2dbcEntityOperations myTabR2dbcEntityOperations(@Qualifier("firstDatabaseConnectionFactory") ConnectionFactory connectionFactory) {
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
return new R2dbcEntityTemplate(databaseClient, PostgresDialect.INSTANCE);
}
}
The other configuration class is similar to the one above but it doesn't use any converters as the other table in the other database has only simple data types.
Moreover, the guide https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#mapping.configuration suggests how to use custom converters but it doesn't work either in my case. Also, it says to extend AbstractR2dbcConfiguration, which shouldn't be used if you are connecting to multiple databases, as underlined at the link on the same guide I posted before https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#r2dbc.multiple-databases.
Finally the details of the r2dbc version I am using (gradle):
implementation group: "io.r2dbc", name: "r2dbc-postgresql", version: "0.8.8.RELEASE"
implementation "org.springframework.boot:spring-boot-starter-data-r2dbc"
The strange thing is that it was working before I have introduced the connection to the other database.
If someone might be able to help, that would be really appreciated.