Skip to content

Consider JTS Geometry types as simple types #1711

Closed
@pete-setchell-kubra

Description

Bug Report

Creating a basic entity with a PostGIS geometry Point type does not function as expected with Spring Data R2DBC. A Point can be read from a fixture in the database using the native PostgisGeometryCodec, but cannot be inserted in a new entity.

Versions

  • Driver: 1.0.3.RELEASE (also functioned the same rolling back through 0.8.5.RELEASE)
  • Database: postgres (PostgreSQL) 14.10 (Homebrew)
  • Java: OpenJDK 64-Bit Server VM (build 21.0.1+12-29, mixed mode, sharing)
  • OS: MacOS

Current Behavior

Attempting to set up a minimal mapping of a point with JTS does not work as expected with kotlin and Spring Data R2DBC. Running through a debugger, the PostgisGeometryCodec seems to be dynamically loaded during connection handshake, but this is not done in time to stop the exception being thrown on first write.

Stack trace
org.springframework.dao.InvalidDataAccessApiUsageException: Nested entities are not supported

	at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.writePropertyInternal(MappingR2dbcConverter.java:251)
	at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.writeProperties(MappingR2dbcConverter.java:218)
	at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.writeInternal(MappingR2dbcConverter.java:189)
	at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.write(MappingR2dbcConverter.java:181)
	at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.write(MappingR2dbcConverter.java:61)
	at org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy.getOutboundRow(DefaultReactiveDataAccessStrategy.java:177)
	at org.springframework.data.r2dbc.core.R2dbcEntityTemplate.lambda$doInsert$6(R2dbcEntityTemplate.java:470)
	at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:153)
	at reactor.core.publisher.MonoFlatMap.subscribeOrReturn(MonoFlatMap.java:53)
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:63)
	at reactor.core.publisher.MonoUsingWhen.subscribe(MonoUsingWhen.java:87)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4512)
	at kotlinx.coroutines.reactor.MonoKt.awaitSingleOrNull(Mono.kt:47)
	at org.springframework.aop.framework.CoroutinesUtils.awaitSingleOrNull(CoroutinesUtils.java:42)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:268)
	at jdk.proxy2/jdk.proxy2.$Proxy69.save(Unknown Source)
	at com.example.r2dbchibernatepoc.repository.AssetRepositoryTest$testInsert$1.invokeSuspend(AssetRepositoryTest.kt:29)
	at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:46)
	at com.example.r2dbchibernatepoc.repository.AssetRepositoryTest$testInsert$1.invokeSuspend(AssetRepositoryTest.kt:29)```
</details>
 
#### Table schema

<!--- Provide the table schema. -->

<details>
<summary>Input Code</summary>

```sql
CREATE SEQUENCE asset_id_seq;
CREATE TABLE IF NOT EXISTS asset (
    id BIGINT PRIMARY KEY DEFAULT NEXTVAL('asset_id_seq'),
    geom GEOMETRY(Point, 4326)
);

Steps to reproduce

Input Code
import org.locationtech.jts.geom.Geometry

@Table("asset")
data class Asset constructor(
    @Id val id: Long?,
    @Column val geom: Geometry
)

interface AssetRepository : CoroutineCrudRepository<Asset, Long> {
    override suspend fun findById(id: Long): Asset?
}
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.PrecisionModel

@SpringBootTest
class AssetRepositoryTest @Autowired constructor(val userRepository: UserRepository, val assetRepository: AssetRepository) {

    @Test
    fun testInsert() {
        val longitude = 37.7434579544699
        val latitude = -122.44437070120628
        val factory4326 = GeometryFactory(PrecisionModel(PrecisionModel.FLOATING), 4326)
        val p: Geometry = factory4326.createPoint(Coordinate(longitude, latitude)) as Geometry

        runBlocking {
            val a = assetRepository.save(Asset(id=null, geom = p))
            assertNotNull(a.id)
            val aid = a.id!!
            val b = assetRepository.findById(aid)
            assertEquals(a, b)
            assertNotNull(b?.geom)
        }
    }

    @Test
    fun testRead() {
        runBlocking {
            val aid = 1L
            val a = assetRepository.findById(aid)
            assertNotNull(a?.geom)
        }
    }
}

A minimal reproducible version of this bug is available here: https://github.com/pete-setchell-kubra/r2dbcrepro

Expected behavior/code

  • testRead runs and loads an entity from a fixture, mapping the geometry correctly.
  • testInsert fails with the exception about Nested entity.

Possible Solution

This may be in issue in r2dbc-postgresql, I'll double file there and update this ticket with an issue number.

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions