Spring Data Neo4J中SignalDescription节点实体未创建问题求助
我正在用RestTemplate获取SignalDescription对象,想把它存入Neo4j,但运行测试后发现只生成了更新语句,没有CREATE,导致节点没创建出来。
我的测试类代码:
@RunWith(SpringRunner.class) @SpringBootTest public class SignalTest { @Autowired private SignalRepository signalRepository; private SignalClient signalClient; @Autowired private RestTemplate restTemplate; private String url; private SignalDescription signalDescription; @Before public void setUp() throws Exception { restTemplate = new RestTemplate(); url = "http://10.248.104.63:10001"; } @Test public void testSignal() { signalClient = new SignalClient(restTemplate, url); signalDescription = signalClient.getSignalDescription("203"); System.out.println(signalDescription.toString()); //Save SignalDescription in database signalRepository.save(signalDescription); } }
SignalClient代码:
@Service public class SignalClient { private RestTemplate restTemplate; private String url; @Autowired public SignalClient(RestTemplate restTemplate, @Value("${metasql.url}") String url) { this.restTemplate = restTemplate; this.url = url; } public SignalDescription getSignalDescription(String signalId) { return restTemplate.getForObject(url + "/v0/cache/signals/" + signalId, SignalDescription.class); } }
运行测试后,Neo4j的请求日志是更新语句:
Thread: 1, url: http://localhost:7474/db/data/transaction/10, request: {"statements":[{"statement":"UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`SignalDescription` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, row.type as type","parameters":{"rows":[{"type":"node","nodeId":203,"props":{"customerDesignation":"1.D30.03","name":"UNIT1.Ev_Grp30_E.Field_curcuit_breaker_open.Present","unitId":"0"}}]},"resultDataContents":["row"],"includeStats":false}]}
但另一个Product实体的示例会生成CREATE语句:
Thread: 1, url: http://localhost:7474/db/data/transaction/11, request: {"statements":[{"statement":"MATCH (n:`Product`) OPTIONAL MATCH (n)-[r0]-() DELETE r0, n","parameters":{},"resultDataContents":["row"],"includeStats":false}]} 2018-01-17 11:40:42.060 INFO 22039 --- [ main] o.n.o.drivers.http.request.HttpRequest : Thread: 1, url: http://localhost:7474/db/data/transaction/12, request: {"statements":[{"statement":"UNWIND {rows} as row CREATE (n:`Product`) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, row.type as type","parameters":{"rows":[{"nodeRef":-1,"type":"node","props":{"price":"100.0","imageUrl":"http://an-imageurl.com/image1.jpg","description":"ProductTest"}}]},"resultDataContents":["row"],"includeStats":false}]}
问题根源
从日志能明显看出来:Spring Data Neo4j(SDN)认为你要保存的SignalDescription是已存在的节点,所以执行了更新(MATCH ... SET)而非创建(CREATE)。
为什么会这样?因为SDN默认会把实体的@Id字段映射为Neo4j的内部节点ID(就是那个数字ID)。你从API获取的SignalDescription对象,它的@Id字段被设置成了203(也就是你传入的signalId),SDN就直接把这个值当成了Neo4j已存在节点的内部ID,所以去执行更新操作了——但实际上数据库里根本没有ID为203的SignalDescription节点。
解决办法
针对这个问题,有两种常见的修复思路:
1. 改用业务ID作为实体标识(推荐)
不要用Neo4j的内部ID,而是把你的业务ID(也就是signalId)作为实体的唯一标识,这样SDN会根据业务ID判断节点是否存在。
修改你的SignalDescription实体类:
@Node public class SignalDescription { // 用业务ID作为节点的唯一标识,映射到Neo4j的signalId属性 @Id @Property("signalId") private String signalId; // 其他属性... private String customerDesignation; private String name; private String unitId; }
这样当你保存signalId="203"的对象时,如果数据库里没有对应signalId的节点,SDN就会自动生成CREATE语句创建新节点;如果已经存在,才会执行更新。
2. 清空实体的内部ID再保存
如果你一定要用Neo4j的内部ID,那就在保存前把实体的ID字段设为null——因为当ID为null时,SDN会认为这是一个全新的节点,从而执行CREATE语句。
修改测试类的代码:
@Test public void testSignal() { signalClient = new SignalClient(restTemplate, url); signalDescription = signalClient.getSignalDescription("203"); // 清空内部ID,告诉SDN这是新节点 signalDescription.setId(null); System.out.println(signalDescription.toString()); signalRepository.save(signalDescription); }
注意:这里假设你的SignalDescription实体有一个对应Neo4j内部ID的id字段(类型一般是Long)。
补充说明
你提到的Product示例之所以会生成CREATE语句,是因为那个实体的ID要么是null,要么用了业务ID且数据库中不存在对应节点,所以SDN判断这是新节点,执行了创建操作。
内容的提问来源于stack exchange,提问作者marhg

