Skip to content

Allow the use of composite properties as ids. #2618

@michael-simons

Description

@michael-simons

It would be nice to allow composite properties as @Id properties:

@Node
public class Thing {

	@Id
	@CompositeProperty(converter = CustomId.Converter.class)
	private final CustomId customId;

	@Version
	private Long version;

	private String name;

	public Thing(CustomId customId, String name) {
		this.customId = customId;
		this.name = name;
	}

	public CustomId getCustomId() {
		return customId;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Long getVersion() {
		return version;
	}
}

with CustomId being defined as

package com.example.compositeids;

import java.util.HashMap;
import java.util.Map;

import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
import org.springframework.data.neo4j.core.convert.Neo4jConversionService;
import org.springframework.data.neo4j.core.convert.Neo4jPersistentPropertyToMapConverter;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

public record CustomId(String value1, Integer value2) {

	static class Converter implements Neo4jPersistentPropertyToMapConverter<String, CustomId> {

		@NonNull
		@Override
		public Map<String, Value> decompose(@Nullable CustomId property, Neo4jConversionService conversionService) {

			final HashMap<String, Value> decomposed = new HashMap<>();
			if (property == null) {
				decomposed.put("value1", Values.NULL);
				decomposed.put("value2", Values.NULL);
			} else {
				decomposed.put("value1", Values.value(property.value1));
				decomposed.put("value2", Values.value(property.value2));
			}
			return decomposed;
		}

		@Override
		public CustomId compose(Map<String, Value> source, Neo4jConversionService conversionService) {
			return source.isEmpty() ?
				null :
				new CustomId(source.get("value1").asString(), source.get("value2").asInt());
		}
	}
}

this does not work yet but it only needs a fix in rendering the id attributes target.
Not sure whether to treat as as a bug or enhancement, so labelled it as both.

Test case for reference:

package com.example.compositeids;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.neo4j.driver.Driver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@Testcontainers(disabledWithoutDocker = true)
public class ThingRepositoryIT {

	@SuppressWarnings("resource")
	private static final Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:4.4")
		.withReuse(true);

	@DynamicPropertySource
	static void prepareNeo4j(DynamicPropertyRegistry registry) {

		neo4j.start();
		registry.add("spring.neo4j.authentication.username", () -> "neo4j");
		registry.add("spring.neo4j.authentication.password", neo4j::getAdminPassword);
		registry.add("spring.neo4j.uri", neo4j::getBoltUrl);
	}

	@Test
	void compositeIdsShouldWork(@Autowired Driver driver, @Autowired ThingRepository repository) {

		var thing = new Thing(new CustomId("a,", 1), "first entity");
		var saved = repository.save(thing);
		assertThat(saved.getVersion()).isGreaterThanOrEqualTo(0);

		saved.setName("foobar");
		saved = repository.save(saved);
		assertThat(saved.getVersion()).isGreaterThan(0);
		assertThat(saved.getName()).isEqualTo("foobar");
	}
}

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions