-
Notifications
You must be signed in to change notification settings - Fork 616
The attribute 'id' is not mapped to a Graph property in ReactiveNeo4jTemplate #2676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This is my helper class import org.neo4j.configuration.GraphDatabaseSettings
import org.neo4j.configuration.GraphDatabaseSettings.DEFAULT_DATABASE_NAME
import org.neo4j.dbms.api.DatabaseManagementService
import org.neo4j.dbms.api.DatabaseManagementServiceBuilder
import org.neo4j.driver.AuthTokens
import org.neo4j.driver.Driver
import org.neo4j.driver.GraphDatabase
import org.neo4j.graphdb.GraphDatabaseService
import org.springframework.data.neo4j.core.ReactiveNeo4jClient
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext
import java.io.File
import java.time.Duration
import kotlin.io.path.createTempDirectory
class Neo4jTestHelper : ResourceTestHelper {
lateinit var reactiveNeo4jTemplate: ReactiveNeo4jTemplate
private lateinit var driver: Driver
private lateinit var reactiveNeo4jClient: ReactiveNeo4jClient
private val databaseDirectory = createTempDirectory("neo4j-test-")
private val managementService: DatabaseManagementService =
DatabaseManagementServiceBuilder(databaseDirectory)
.setConfig(GraphDatabaseSettings.transaction_timeout, Duration.ofSeconds(60))
.setConfig(GraphDatabaseSettings.preallocate_logical_logs, true).build()
override fun setUp() {
driver =
GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "password"));
reactiveNeo4jClient = ReactiveNeo4jClient.create(driver)
val graphDb: GraphDatabaseService = managementService.database(DEFAULT_DATABASE_NAME)
reactiveNeo4jTemplate =
ReactiveNeo4jTemplate(
reactiveNeo4jClient, Neo4jMappingContext.builder()
.build()
)
if (graphDb.isAvailable) {
graphDb.executeTransactionally("MATCH (n) DETACH DELETE n")
}
}
override fun tearDown() {
managementService.shutdown()
// delete the database directory and all its contents recursively (if it exists) after the test
deleteRecursively(databaseDirectory.toFile())
}
private fun deleteRecursively(file: File) {
if (file.isDirectory) {
val files = file.listFiles()
if (files != null) {
for (child in files) {
deleteRecursively(child)
}
}
}
if (!file.delete()) {
throw RuntimeException("Failed to delete file: $file")
}
}
} |
Please remove your |
I attempted to use both IDs, the default from Spring Data and the one from Spring Data Neo4j. Unfortunately, it still didn't work. I tried using import com.astrum.data.annotation.GeneratedValue
import com.astrum.ulid.ULID
//import org.springframework.data.annotation.Id
import org.springframework.data.neo4j.core.schema.Id
abstract class ULIDEntity : Entity<ULID>() {
@Id
@GeneratedValue
override var id: ULID = ULID.randomULID()
} import com.astrum.data.annotation.GeneratedValue
import com.astrum.ulid.ULID
import org.springframework.data.annotation.Id
//import org.springframework.data.neo4j.core.schema.Id
abstract class ULIDEntity : Entity<ULID>() {
@Id
@GeneratedValue
override var id: ULID = ULID.randomULID()
} @michael-simons I'm not sure if the problem is with creating the template when passing a predefined context created by the builder Neo4jMappingContext.builder().build() override fun setUp() {
driver =
GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "password"));
reactiveNeo4jClient = ReactiveNeo4jClient.create(driver)
val graphDb: GraphDatabaseService = managementService.database(DEFAULT_DATABASE_NAME)
reactiveNeo4jTemplate =
ReactiveNeo4jTemplate(
reactiveNeo4jClient, Neo4jMappingContext.builder()
.build()
)
if (graphDb.isAvailable) {
graphDb.executeTransactionally("MATCH (n) DETACH DELETE n")
}
} |
The error is the same
|
Oh wow, I didn't notice you are not using any test slices or AbstractReactiveNeo4jConfig.java in your tests… Would you please try out if you initialise it with your entity classes? import org.springframework.data.neo4j.config.Neo4jEntityScanner;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
public class Foo {
void f() throws ClassNotFoundException {
var ctx = Neo4jMappingContext.builder().build();
ctx.setInitialEntitySet(Neo4jEntityScanner.get().scan("com.astrum.data.entity"));
}
} Also, I assume you have a proper converter for Thank you. |
Also, what does We have a broad variety of tests for generated values and custom generators and custom ID generated types, for both Java and Kotlin. Maybe that support is already good enough instead of running your own thing on top of what is provided? |
I have implemented the converters for ULID as shown in this class. package com.astrum.data.converter
import com.astrum.data.annotation.ConverterScope
import com.astrum.ulid.ULID
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.data.convert.WritingConverter
import org.springframework.stereotype.Component
@Component
@WritingConverter
@ConverterScope(ConverterScope.Type.NEO4J)
class BinaryToULIDConverter : GenericConverter{
override fun getConvertibleTypes(): MutableSet<GenericConverter.ConvertiblePair>? {
return mutableSetOf(GenericConverter.ConvertiblePair(ByteArray::class.java, ULID::class.java))
}
override fun convert(
source: Any?,
sourceType: TypeDescriptor,
targetType: TypeDescriptor
): Any? {
return ULID.fromBytes(source as ByteArray)
}
} package com.astrum.data.converter
import com.astrum.data.annotation.ConverterScope
import com.astrum.ulid.ULID
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.data.convert.WritingConverter
import org.springframework.stereotype.Component
@Component
@WritingConverter
@ConverterScope(ConverterScope.Type.NEO4J)
class ULIDToBinaryConverter : GenericConverter{
override fun getConvertibleTypes(): MutableSet<GenericConverter.ConvertiblePair>? {
return mutableSetOf(GenericConverter.ConvertiblePair(ULID::class.java, ByteArray::class.java))
}
override fun convert(
source: Any?,
sourceType: TypeDescriptor,
targetType: TypeDescriptor
): Any? {
return (source as ULID).toBytes()
}
} I have the configuration class where the Bean for conversions is created. package com.astrum.data.configuration
import com.astrum.data.annotation.ConverterScope
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.data.convert.ReadingConverter
import org.springframework.data.convert.WritingConverter
import org.springframework.data.neo4j.core.convert.Neo4jConversions
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories
@Configuration
@EnableReactiveNeo4jRepositories("com.astrum")
class Neo4jConfiguration (
private val applicationContext: ApplicationContext
){
@Bean
fun neo4jConversions(): Neo4jConversions {
val converters = applicationContext.getBeansOfType(GenericConverter::class.java)
.values
.filter { it.javaClass.annotations.any { annotation -> annotation is WritingConverter || annotation is ReadingConverter } }
.filter {
val scope = it.javaClass.annotations.filterIsInstance<ConverterScope>()
scope.isEmpty() || scope.any { converterScope -> converterScope.type == ConverterScope.Type.NEO4J }
}
return Neo4jConversions(converters)
}
} Add the lines you mentioned to support entity scanning. override fun setUp() {
driver =
GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "password"));
reactiveNeo4jClient = ReactiveNeo4jClient.create(driver)
val graphDb: GraphDatabaseService = managementService.database(DEFAULT_DATABASE_NAME)
val ctx = Neo4jMappingContext.builder().build()
ctx.setInitialEntitySet(Neo4jEntityScanner.get().scan("com.astrum.data.entity"))
reactiveNeo4jTemplate =
ReactiveNeo4jTemplate(reactiveNeo4jClient, ctx)
if (graphDb.isAvailable) {
graphDb.executeTransactionally("MATCH (n) DETACH DELETE n")
}
} This is the complete class. package com.astrum.data.test
import org.neo4j.configuration.GraphDatabaseSettings
import org.neo4j.configuration.GraphDatabaseSettings.DEFAULT_DATABASE_NAME
import org.neo4j.dbms.api.DatabaseManagementService
import org.neo4j.dbms.api.DatabaseManagementServiceBuilder
import org.neo4j.driver.AuthTokens
import org.neo4j.driver.Driver
import org.neo4j.driver.GraphDatabase
import org.neo4j.graphdb.GraphDatabaseService
import org.springframework.data.neo4j.config.Neo4jEntityScanner
import org.springframework.data.neo4j.core.ReactiveNeo4jClient
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext
import java.io.File
import java.time.Duration
import kotlin.io.path.createTempDirectory
class Neo4jTestHelper : ResourceTestHelper {
lateinit var reactiveNeo4jTemplate: ReactiveNeo4jTemplate
private lateinit var driver: Driver
private lateinit var reactiveNeo4jClient: ReactiveNeo4jClient
private val databaseDirectory = createTempDirectory("neo4j-test-")
private val managementService: DatabaseManagementService =
DatabaseManagementServiceBuilder(databaseDirectory)
.setConfig(GraphDatabaseSettings.transaction_timeout, Duration.ofSeconds(60))
.setConfig(GraphDatabaseSettings.preallocate_logical_logs, true).build()
override fun setUp() {
driver =
GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "password"));
reactiveNeo4jClient = ReactiveNeo4jClient.create(driver)
val graphDb: GraphDatabaseService = managementService.database(DEFAULT_DATABASE_NAME)
val ctx = Neo4jMappingContext.builder().build()
ctx.setInitialEntitySet(Neo4jEntityScanner.get().scan("com.astrum.data.entity"))
reactiveNeo4jTemplate =
ReactiveNeo4jTemplate(reactiveNeo4jClient, ctx)
if (graphDb.isAvailable) {
graphDb.executeTransactionally("MATCH (n) DETACH DELETE n")
}
}
override fun tearDown() {
managementService.shutdown()
// delete the database directory and all its contents recursively (if it exists) after the test
deleteRecursively(databaseDirectory.toFile())
}
private fun deleteRecursively(file: File) {
if (file.isDirectory) {
val files = file.listFiles()
if (files != null) {
for (child in files) {
deleteRecursively(child)
}
}
}
if (!file.delete()) {
throw RuntimeException("Failed to delete file: $file")
}
}
} Unfortunately, I'm still having the same problem and the same exception.
|
@michael-simons , I still have the same problem I told you about. Now I suspect that the issue may be the ULID conversion, which causes it to not be recognized as an ID. I have given you access to my private project so that you can help me find the problem. |
Please provide the issue in isolation, I don't have the cycles to make my way through a whole application. As far as I can tell, this is a generated application which doesn't help spotting the the issue. We have explicit support for custom id types: |
This won't be picked up by SDN… And you already indicate that by your custom scope annotation… package com.astrum.data.converter
import com.astrum.data.annotation.ConverterScope
import com.astrum.ulid.ULID
import org.springframework.core.convert.converter.Converter
import org.springframework.data.convert.ReadingConverter
import org.springframework.stereotype.Component
@Component
@ReadingConverter
@ConverterScope(ConverterScope.Type.R2DBC)
class BytesToULIDConverter : Converter<ByteArray, ULID> {
override fun convert(source: ByteArray): ULID {
return ULID.fromBytes(source)
}
} I guess. this should work: package com.astrum.data.configuration
import com.astrum.ulid.ULID
import org.neo4j.driver.Driver
import org.neo4j.driver.Value
import org.neo4j.driver.Values
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider
import org.springframework.data.neo4j.core.convert.Neo4jConversions
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories
import org.springframework.data.neo4j.repository.config.ReactiveNeo4jRepositoryConfigurationExtension
import org.springframework.transaction.ReactiveTransactionManager
@Configuration
@EnableReactiveNeo4jRepositories("com.astrum")
class DatabaseConfiguration {
@Bean(ReactiveNeo4jRepositoryConfigurationExtension.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)
fun transactionManager(
driver: Driver,
databaseNameProvider: ReactiveDatabaseSelectionProvider
): ReactiveTransactionManager {
return ReactiveNeo4jTransactionManager(driver, databaseNameProvider)
}
@Bean
fun neo4jConversions(): Neo4jConversions? {
val f = object: GenericConverter {
override fun getConvertibleTypes(): Set<GenericConverter.ConvertiblePair>? {
return setOf(
GenericConverter.ConvertiblePair(
ULID::class.java,
Value::class.java
),
GenericConverter.ConvertiblePair(
Value::class.java,
ULID::class.java
)
)
}
override fun convert(source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any? {
if (source == null) {
return null
}
return if (ULID::class.java.isAssignableFrom(sourceType.getType())
) {
Values.value((source as ULID).toBytes())
} else {
ULID.fromBytes((source as Value).asByteArray())
}
}
}
return Neo4jConversions(setOf(f))
}
} |
The problem was that the configuration where the converters are set was not being loaded in the tests, and since I created a ReativeNeo4jTemplate in that test, the converters were not loaded. Now, my problem is that the render is null and I'm not understanding very well how to create that render. I have doubts about how to construct the render that the ReativeNeo4jTemplate uses. override fun setUp() {
driver =
GraphDatabase.driver("bolt://localhost:7687", AuthTokens.basic("neo4j", "password"))
reactiveNeo4jClient = ReactiveNeo4jClient.create(driver)
val graphDb: GraphDatabaseService = managementService.database(DEFAULT_DATABASE_NAME)
val ctx = Neo4jMappingContext.builder()
.withNeo4jConversions(Neo4jConversions(setOf(ULIDToValueConverter())))
.build()
ctx.setInitialEntitySet(Neo4jEntityScanner.get().scan("com.astrum.data.entity"))
reactiveNeo4jTemplate =
ReactiveNeo4jTemplate(reactiveNeo4jClient, ctx)
if (graphDb.isAvailable) {
graphDb.executeTransactionally("MATCH (n) DETACH DELETE n")
}
}
|
Oh you are using the But I consider the original issue invalid. |
I will add a proper default value to both templates, but this is not an endorsement for the way you are running the tests. Until I added the fix, this is a workaround for your test, but again, not really recommended: GenericApplicationContext ctx = new GenericApplicationContext();
ctx.refresh();
reactiveNeo4jTemplate.setBeanFactory(ctx); |
Hello everyone, I'm having a problem with my domain layer. I have an entity that is shared between Postgres and Neo4j. These are my models.
The problem is with the Neo4j part when I try to save a Person type entity. In the following class I use in a test it fails with the following error.
The text was updated successfully, but these errors were encountered: