RelationshipEntity 的更新重置了 Neo4j 中各种其他丰富关系的值
Update of RelationshipEntity resets values of various other rich relationships in Neo4j
1。问题总结
当我更新单个现有 Neo4j RelationshipEntity
的属性时,相同类型的各种其他丰富关系的实际值突然被它们旧的、以前的值状态所取代。 似乎 事务被回滚了。我的期望是只更新被检查的 RelationshipEntity
的属性,其他所有关系都保持不变。
2。初始情况
- 运行 Neo4j 3.4.7,Spring Boot v2.0.5.RELEASE,Spring v5.0.9.RELEASE
- 没有明确使用事务
- 简化图架构:
3。目标-实际比较
3.1。时间顺序
根据图形模式的简化用例可以总结如下:
- 识别所有
ClassC
个节点
- 为他们找到各种相关的
ClassB
(来自 ClassA
)
- 为每个已识别的
ClassB
创建一个 ClassD
,包括与 ClassB
的关系和丰富的 CDMapping
与 ClassC
) 的关系
3.2 预期/积极结果
第一个 运行 所描述的完整块工作正常。 ClassC
和 ClassD
之间的各种 RelationshipEntitys
与属性 "Default Value" 被设置,最后一个丰富的关系接收预期值 "Special Value"。
3.3 问题详解
当涉及到第二个块循环时——正是通过在ClassC
和ClassD
之间保存第一个新的RelationshipEntity
——前一个运行的属性是出乎意料的设置回 "Default Value",替换原来的 "Special Value"。
3.4 Neo4j 生成查询
此时由Neo4j执行以下查询,由相关cdMappingDAO.save(cdMapping);
触发:
UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId CREATE (startNode)-[rel:`MAPS_TO`]->(endNode) SET rel += row.props RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=91, relRef=-45, endNodeId=115, props={attribute=Default Value}}]}
UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId MERGE (startNode)-[rel:`CONTAINS`]->(endNode) RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=88, relRef=-49, endNodeId=115, props={}}, {startNodeId=92, relRef=-51, endNodeId=91, props={}}, {startNodeId=88, relRef=-54, endNodeId=93, props={}}, {startNodeId=89, relRef=-56, endNodeId=94, props={}}, {startNodeId=92, relRef=-57, endNodeId=90, props={}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassA`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=92, props={name=Class A}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassB`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=88, props={name=Class B1}}, {nodeId=89, props={name=Class B2}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassD`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=115, props={}}, {nodeId=93, props={}}, {nodeId=94, props={}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassC`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=90, props={name=Class C1}}, {nodeId=91, props={name=Class C2}}]}
UNWIND {rows} AS row MATCH ()-[r]-() WHERE ID(r) = row.relId SET r += row.props RETURN ID(r) as ref, ID(r) as id, {type} as type with params {rows=[{relId=104, props={attribute=Default Value}}, {relId=106, props={attribute=Default Value}}], type=rel}
4。待解决挑战
能否请您给我一个建议,为什么以及通过什么组件重置属性值?我如何确保仅更新正在检查的 RelationshipEntity
?非常感谢您为我指明了正确的方向!
5。代码片段
5.1 GraphHandler
@Component
public class GraphHandler implements CommandLineRunner {
private ClassADAO classADAO;
private ClassBDAO classBDAO;
private ClassCDAO classCDAO;
private ClassDDAO classDDAO;
private CDMappingDAO cdMappingDAO;
private SessionFactory sessionFactory;
@Autowired
public GraphHandler(ClassADAO classADAO, ClassBDAO classBDAO, ClassCDAO classCDAO, ClassDDAO classDDAO, CDMappingDAO cdMappingDAO, SessionFactory sessionFactory) {
this.classADAO = classADAO;
this.classBDAO = classBDAO;
this.classCDAO = classCDAO;
this.classDDAO = classDDAO;
this.cdMappingDAO = cdMappingDAO;
this.sessionFactory = sessionFactory;
}
public void run(String... args) {
createInitialModel();
runUseCase();
}
private void createInitialModel() {
ClassA classA = new ClassA("Class A");
ClassB classB1 = new ClassB("Class B1");
ClassB classB2 = new ClassB("Class B2");
ClassC classC1 = new ClassC("Class C1");
ClassC classC2 = new ClassC("Class C2");
classA.getClassBs().addAll(Arrays.asList(classB1, classB2));
classA.getClassCs().addAll(Arrays.asList(classC1, classC2));
classADAO.save(classA);
classBDAO.save(classB1);
classBDAO.save(classB2);
classCDAO.save(classC1);
classCDAO.save(classC2);
}
private void runUseCase() {
Iterable<ClassC> classCs = classCDAO.findAll();
for (ClassC classC : classCs) {
ClassD rememberedClassD = null;
List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
for (ClassB classB : classBs) {
ClassD classD = new ClassD();
classD.setClassB(classB);
classB.getClassDs().add(classD);
classDDAO.save(classD);
rememberedClassD = classD;
CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
cdMappingDAO.save(cdMapping); // <-- here the problem occurs
}
// choosing the last created relationship (ClassC-ClassD) and mark it
CDMapping relationship = cdMappingDAO.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
relationship.setAttribute("Special Value");
cdMappingDAO.save(relationship);
}
}
}
5.2 CDMapping
@RelationshipEntity(type = "MAPS_TO")
public class CDMapping {
@Id
@GeneratedValue
private Long id;
@StartNode
private ClassC classC;
@EndNode
private ClassD classD;
private String attribute;
public CDMapping(ClassC classC, ClassD classD, String attribute) {
this.classC = classC;
this.classD = classD;
this.attribute = attribute;
classC.getCdMappings().add(this);
classD.getCdMappings().add(this);
}
// default constructor, getter and setter here
}
5.3A 级
@NodeEntity
public class ClassA extends Entity {
private String name;
@Relationship(type = "CONTAINS")
private List<ClassC> classCs = new ArrayList<>();
@Relationship(type = "MAPS_TO")
private List<ClassB> classBs = new ArrayList<>();
// default constructor, getter and setter here
}
5.4 B 级
@NodeEntity
public class ClassB extends Entity {
private String name;
@Relationship(type = "MAPS_TO", direction = Relationship.INCOMING)
private List<ClassA> classAs = new ArrayList<>();
@Relationship(type = "CONTAINS")
private List<ClassD> classDs = new ArrayList<>();
// default constructor, getter and setter here
}
5.5 级 C
@NodeEntity
public class ClassC extends Entity {
private String name;
@Relationship(type = "CONTAINS", direction = Relationship.INCOMING)
private ClassA classA;
@Relationship(type = "MAPS_TO")
private List<CDMapping> cdMappings = new ArrayList<>();
// default constructor, getter and setter here
}
5.6 D 级
@NodeEntity
public class ClassD extends Entity {
@Relationship(type = "CONTAINS", direction = Relationship.INCOMING)
private ClassB classB;
@Relationship(type = "MAPS_TO", direction = Relationship.INCOMING)
private List<CDMapping> cdMappings = new ArrayList<>();
// default constructor, getter and setter here
}
更新
5.7 CDMappingDAO
@Repository
public interface CDMappingDAO extends Neo4jRepository<CDMapping, Long> {
@Query("MATCH (classC:ClassC)-[relationship:MAPS_TO]-(classD:ClassD) WHERE id(classC)={classCId} AND id(classD)={classDId} RETURN classC, relationship, classD;")
CDMapping getRelationshipByClassCAndClassD(@Param("classCId") Long classCId, @Param("classDId") Long classDId);
}
5.8 ClassADAO / ClassCDAO / ClassDDAO
@Repository
public interface ClassADAO extends Neo4jRepository<ClassA, Long> {
}
除了第一个 Neo4jRepository
类型外,ClassCDAO
和 ClassDDAO
完全相同。
5.9 ClassBDAO
@Repository
public interface ClassBDAO extends Neo4jRepository<ClassB, Long> {
@Query("MATCH (classC:ClassC)<-[:CONTAINS]-(:ClassA)-[:MAPS_TO]->(classB:ClassB) WHERE id(classC)={classCId} RETURN classB;")
List<ClassB> findClassBSelection(@Param("classCId") Long classCId);
}
目前似乎 OGM 中的某处存在错误,但我已经找到了解决您的问题的两个解决方法。
解决方法一让您的代码几乎保持原样:
更改 runUseCase
一个在你的 GraphHandler
中以保存深度为 0 的 CDMapping
:
private void runUseCase() {
Iterable<ClassC> classCs = classCDAO.findAll();
for (ClassC classC : classCs) {
ClassD rememberedClassD = null;
List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
for (ClassB classB : classBs) {
ClassD classD = new ClassD();
classD.setClassB(classB);
classB.getClassDs().add(classD);
classDDAO.save(classD);
rememberedClassD = classD;
CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
cdMappingDAO.save(cdMapping, 0); // <-- here the problem occurs
}
// choosing the last created relationship (ClassC-ClassD) and mark it
CDMapping relationship = cdMappingDAO.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
relationship.setAttribute("Special Value");
cdMappingDAO.save(relationship, 0);
}
}
这使得 "special" 个完好无损。
我宁愿建议进行以下更改,添加一些更正确的事务边界。
引入一项服务 class,例如 SomeGraphBasedService
。由于 Springs 声明式事务的工作方式,专用 class 是必要的。 createInitialModel
和 runUseCase
这两种方法现在都跨越一个事务,所有 DAO 方法都参与其中。请特别注意评论,我只在 createInitialModel
:
中保存最顶层的父 class
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class SomeGraphBasedService {
private final ClassADAO classADAO;
private final ClassBDAO classBDAO;
private final ClassCDAO classCDAO;
private final ClassDDAO classDDAO;
private final CDMappingDAO cdMappingDAO;
public SomeGraphBasedService(ClassADAO classADAO, ClassBDAO classBDAO,
ClassCDAO classCDAO, ClassDDAO classDDAO, CDMappingDAO cdMappingDAO) {
this.classADAO = classADAO;
this.classBDAO = classBDAO;
this.classCDAO = classCDAO;
this.classDDAO = classDDAO;
this.cdMappingDAO = cdMappingDAO;
}
@Transactional
public void createInitialModel() {
ClassA classA = new ClassA("Class A");
ClassB classB1 = new ClassB("Class B1");
ClassB classB2 = new ClassB("Class B2");
ClassC classC1 = new ClassC("Class C1");
ClassC classC2 = new ClassC("Class C2");
classA.getClassBs().addAll(Arrays.asList(classB1, classB2));
classB1.getClassAs().add(classA);
classB2.getClassAs().add(classA);
classA.getClassCs().addAll(Arrays.asList(classC1, classC2));
classADAO.save(classA);
// No need to save them one by one, the releationships
// take care of that while saving the parent class at the top
/*
classBDAO.save(classB1);
classBDAO.save(classB2);
classCDAO.save(classC1);
classCDAO.save(classC2);
*/
}
@Transactional
public void runUseCase() {
Iterable<ClassC> classCs = classCDAO.findAll();
for (ClassC classC : classCs) {
ClassD rememberedClassD = null;
List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
for (ClassB classB : classBs) {
ClassD classD = new ClassD();
classD.setClassB(classB);
classB.getClassDs().add(classD);
classDDAO.save(classD);
rememberedClassD = classD;
CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
cdMappingDAO.save(cdMapping); // <-- here the problem occurs
}
// choosing the last created relationship (ClassC-ClassD) and mark it
CDMapping relationship = cdMappingDAO
.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
relationship.setAttribute("Special Value");
cdMappingDAO.save(relationship);
}
Iterable<CDMapping> f = cdMappingDAO.findAll();
for (CDMapping ff : f) {
System.out.println(ff);
}
}
}
然后你的命令行运行器变成这样:
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class GraphHandler implements CommandLineRunner {
private final SomeGraphBasedService someGraphBasedService;
public GraphHandler(SomeGraphBasedService someGraphBasedService) {
this.someGraphBasedService = someGraphBasedService;
}
public void run(String... args) {
this.someGraphBasedService.createInitialModel();
this.someGraphBasedService.runUseCase();
}
}
虽然第一个显然是一种解决方法,但另一个解决方案是我在现实世界场景中更喜欢的解决方案。
无论如何,现在两者的输出都符合预期:
CDMapping{id=21, classC=ClassC{name='Class C1'}, classD=ClassD{classB=ClassB{name='Class B1'}}, attribute='Default Value'}
CDMapping{id=23, classC=ClassC{name='Class C1'}, classD=ClassD{classB=ClassB{name='Class B2'}}, attribute='Special Value'}
CDMapping{id=25, classC=ClassC{name='Class C2'}, classD=ClassD{classB=ClassB{name='Class B1'}}, attribute='Default Value'}
CDMapping{id=27, classC=ClassC{name='Class C2'}, classD=ClassD{classB=ClassB{name='Class B2'}}, attribute='Special Value'}
1。问题总结
当我更新单个现有 Neo4j RelationshipEntity
的属性时,相同类型的各种其他丰富关系的实际值突然被它们旧的、以前的值状态所取代。 似乎 事务被回滚了。我的期望是只更新被检查的 RelationshipEntity
的属性,其他所有关系都保持不变。
2。初始情况
- 运行 Neo4j 3.4.7,Spring Boot v2.0.5.RELEASE,Spring v5.0.9.RELEASE
- 没有明确使用事务
- 简化图架构:
3。目标-实际比较
3.1。时间顺序
根据图形模式的简化用例可以总结如下:
- 识别所有
ClassC
个节点 - 为他们找到各种相关的
ClassB
(来自ClassA
) - 为每个已识别的
ClassB
创建一个ClassD
,包括与ClassB
的关系和丰富的CDMapping
与ClassC
) 的关系
3.2 预期/积极结果
第一个 运行 所描述的完整块工作正常。 ClassC
和 ClassD
之间的各种 RelationshipEntitys
与属性 "Default Value" 被设置,最后一个丰富的关系接收预期值 "Special Value"。
3.3 问题详解
当涉及到第二个块循环时——正是通过在ClassC
和ClassD
之间保存第一个新的RelationshipEntity
——前一个运行的属性是出乎意料的设置回 "Default Value",替换原来的 "Special Value"。
3.4 Neo4j 生成查询
此时由Neo4j执行以下查询,由相关cdMappingDAO.save(cdMapping);
触发:
UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId CREATE (startNode)-[rel:`MAPS_TO`]->(endNode) SET rel += row.props RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=91, relRef=-45, endNodeId=115, props={attribute=Default Value}}]}
UNWIND {rows} as row MATCH (startNode) WHERE ID(startNode) = row.startNodeId WITH row,startNode MATCH (endNode) WHERE ID(endNode) = row.endNodeId MERGE (startNode)-[rel:`CONTAINS`]->(endNode) RETURN row.relRef as ref, ID(rel) as id, {type} as type with params {type=rel, rows=[{startNodeId=88, relRef=-49, endNodeId=115, props={}}, {startNodeId=92, relRef=-51, endNodeId=91, props={}}, {startNodeId=88, relRef=-54, endNodeId=93, props={}}, {startNodeId=89, relRef=-56, endNodeId=94, props={}}, {startNodeId=92, relRef=-57, endNodeId=90, props={}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassA`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=92, props={name=Class A}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassB`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=88, props={name=Class B1}}, {nodeId=89, props={name=Class B2}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassD`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=115, props={}}, {nodeId=93, props={}}, {nodeId=94, props={}}]}
UNWIND {rows} as row MATCH (n) WHERE ID(n)=row.nodeId SET n:`ClassC`:`Entity` SET n += row.props RETURN row.nodeId as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeId=90, props={name=Class C1}}, {nodeId=91, props={name=Class C2}}]}
UNWIND {rows} AS row MATCH ()-[r]-() WHERE ID(r) = row.relId SET r += row.props RETURN ID(r) as ref, ID(r) as id, {type} as type with params {rows=[{relId=104, props={attribute=Default Value}}, {relId=106, props={attribute=Default Value}}], type=rel}
4。待解决挑战
能否请您给我一个建议,为什么以及通过什么组件重置属性值?我如何确保仅更新正在检查的 RelationshipEntity
?非常感谢您为我指明了正确的方向!
5。代码片段
5.1 GraphHandler
@Component
public class GraphHandler implements CommandLineRunner {
private ClassADAO classADAO;
private ClassBDAO classBDAO;
private ClassCDAO classCDAO;
private ClassDDAO classDDAO;
private CDMappingDAO cdMappingDAO;
private SessionFactory sessionFactory;
@Autowired
public GraphHandler(ClassADAO classADAO, ClassBDAO classBDAO, ClassCDAO classCDAO, ClassDDAO classDDAO, CDMappingDAO cdMappingDAO, SessionFactory sessionFactory) {
this.classADAO = classADAO;
this.classBDAO = classBDAO;
this.classCDAO = classCDAO;
this.classDDAO = classDDAO;
this.cdMappingDAO = cdMappingDAO;
this.sessionFactory = sessionFactory;
}
public void run(String... args) {
createInitialModel();
runUseCase();
}
private void createInitialModel() {
ClassA classA = new ClassA("Class A");
ClassB classB1 = new ClassB("Class B1");
ClassB classB2 = new ClassB("Class B2");
ClassC classC1 = new ClassC("Class C1");
ClassC classC2 = new ClassC("Class C2");
classA.getClassBs().addAll(Arrays.asList(classB1, classB2));
classA.getClassCs().addAll(Arrays.asList(classC1, classC2));
classADAO.save(classA);
classBDAO.save(classB1);
classBDAO.save(classB2);
classCDAO.save(classC1);
classCDAO.save(classC2);
}
private void runUseCase() {
Iterable<ClassC> classCs = classCDAO.findAll();
for (ClassC classC : classCs) {
ClassD rememberedClassD = null;
List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
for (ClassB classB : classBs) {
ClassD classD = new ClassD();
classD.setClassB(classB);
classB.getClassDs().add(classD);
classDDAO.save(classD);
rememberedClassD = classD;
CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
cdMappingDAO.save(cdMapping); // <-- here the problem occurs
}
// choosing the last created relationship (ClassC-ClassD) and mark it
CDMapping relationship = cdMappingDAO.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
relationship.setAttribute("Special Value");
cdMappingDAO.save(relationship);
}
}
}
5.2 CDMapping
@RelationshipEntity(type = "MAPS_TO")
public class CDMapping {
@Id
@GeneratedValue
private Long id;
@StartNode
private ClassC classC;
@EndNode
private ClassD classD;
private String attribute;
public CDMapping(ClassC classC, ClassD classD, String attribute) {
this.classC = classC;
this.classD = classD;
this.attribute = attribute;
classC.getCdMappings().add(this);
classD.getCdMappings().add(this);
}
// default constructor, getter and setter here
}
5.3A 级
@NodeEntity
public class ClassA extends Entity {
private String name;
@Relationship(type = "CONTAINS")
private List<ClassC> classCs = new ArrayList<>();
@Relationship(type = "MAPS_TO")
private List<ClassB> classBs = new ArrayList<>();
// default constructor, getter and setter here
}
5.4 B 级
@NodeEntity
public class ClassB extends Entity {
private String name;
@Relationship(type = "MAPS_TO", direction = Relationship.INCOMING)
private List<ClassA> classAs = new ArrayList<>();
@Relationship(type = "CONTAINS")
private List<ClassD> classDs = new ArrayList<>();
// default constructor, getter and setter here
}
5.5 级 C
@NodeEntity
public class ClassC extends Entity {
private String name;
@Relationship(type = "CONTAINS", direction = Relationship.INCOMING)
private ClassA classA;
@Relationship(type = "MAPS_TO")
private List<CDMapping> cdMappings = new ArrayList<>();
// default constructor, getter and setter here
}
5.6 D 级
@NodeEntity
public class ClassD extends Entity {
@Relationship(type = "CONTAINS", direction = Relationship.INCOMING)
private ClassB classB;
@Relationship(type = "MAPS_TO", direction = Relationship.INCOMING)
private List<CDMapping> cdMappings = new ArrayList<>();
// default constructor, getter and setter here
}
更新
5.7 CDMappingDAO
@Repository
public interface CDMappingDAO extends Neo4jRepository<CDMapping, Long> {
@Query("MATCH (classC:ClassC)-[relationship:MAPS_TO]-(classD:ClassD) WHERE id(classC)={classCId} AND id(classD)={classDId} RETURN classC, relationship, classD;")
CDMapping getRelationshipByClassCAndClassD(@Param("classCId") Long classCId, @Param("classDId") Long classDId);
}
5.8 ClassADAO / ClassCDAO / ClassDDAO
@Repository
public interface ClassADAO extends Neo4jRepository<ClassA, Long> {
}
除了第一个 Neo4jRepository
类型外,ClassCDAO
和 ClassDDAO
完全相同。
5.9 ClassBDAO
@Repository
public interface ClassBDAO extends Neo4jRepository<ClassB, Long> {
@Query("MATCH (classC:ClassC)<-[:CONTAINS]-(:ClassA)-[:MAPS_TO]->(classB:ClassB) WHERE id(classC)={classCId} RETURN classB;")
List<ClassB> findClassBSelection(@Param("classCId") Long classCId);
}
目前似乎 OGM 中的某处存在错误,但我已经找到了解决您的问题的两个解决方法。
解决方法一让您的代码几乎保持原样:
更改 runUseCase
一个在你的 GraphHandler
中以保存深度为 0 的 CDMapping
:
private void runUseCase() {
Iterable<ClassC> classCs = classCDAO.findAll();
for (ClassC classC : classCs) {
ClassD rememberedClassD = null;
List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
for (ClassB classB : classBs) {
ClassD classD = new ClassD();
classD.setClassB(classB);
classB.getClassDs().add(classD);
classDDAO.save(classD);
rememberedClassD = classD;
CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
cdMappingDAO.save(cdMapping, 0); // <-- here the problem occurs
}
// choosing the last created relationship (ClassC-ClassD) and mark it
CDMapping relationship = cdMappingDAO.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
relationship.setAttribute("Special Value");
cdMappingDAO.save(relationship, 0);
}
}
这使得 "special" 个完好无损。
我宁愿建议进行以下更改,添加一些更正确的事务边界。
引入一项服务 class,例如 SomeGraphBasedService
。由于 Springs 声明式事务的工作方式,专用 class 是必要的。 createInitialModel
和 runUseCase
这两种方法现在都跨越一个事务,所有 DAO 方法都参与其中。请特别注意评论,我只在 createInitialModel
:
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class SomeGraphBasedService {
private final ClassADAO classADAO;
private final ClassBDAO classBDAO;
private final ClassCDAO classCDAO;
private final ClassDDAO classDDAO;
private final CDMappingDAO cdMappingDAO;
public SomeGraphBasedService(ClassADAO classADAO, ClassBDAO classBDAO,
ClassCDAO classCDAO, ClassDDAO classDDAO, CDMappingDAO cdMappingDAO) {
this.classADAO = classADAO;
this.classBDAO = classBDAO;
this.classCDAO = classCDAO;
this.classDDAO = classDDAO;
this.cdMappingDAO = cdMappingDAO;
}
@Transactional
public void createInitialModel() {
ClassA classA = new ClassA("Class A");
ClassB classB1 = new ClassB("Class B1");
ClassB classB2 = new ClassB("Class B2");
ClassC classC1 = new ClassC("Class C1");
ClassC classC2 = new ClassC("Class C2");
classA.getClassBs().addAll(Arrays.asList(classB1, classB2));
classB1.getClassAs().add(classA);
classB2.getClassAs().add(classA);
classA.getClassCs().addAll(Arrays.asList(classC1, classC2));
classADAO.save(classA);
// No need to save them one by one, the releationships
// take care of that while saving the parent class at the top
/*
classBDAO.save(classB1);
classBDAO.save(classB2);
classCDAO.save(classC1);
classCDAO.save(classC2);
*/
}
@Transactional
public void runUseCase() {
Iterable<ClassC> classCs = classCDAO.findAll();
for (ClassC classC : classCs) {
ClassD rememberedClassD = null;
List<ClassB> classBs = classBDAO.findClassBSelection(classC.getId());
for (ClassB classB : classBs) {
ClassD classD = new ClassD();
classD.setClassB(classB);
classB.getClassDs().add(classD);
classDDAO.save(classD);
rememberedClassD = classD;
CDMapping cdMapping = new CDMapping(classC, classD, "Default Value");
cdMappingDAO.save(cdMapping); // <-- here the problem occurs
}
// choosing the last created relationship (ClassC-ClassD) and mark it
CDMapping relationship = cdMappingDAO
.getRelationshipByClassCAndClassD(classC.getId(), rememberedClassD.getId());
relationship.setAttribute("Special Value");
cdMappingDAO.save(relationship);
}
Iterable<CDMapping> f = cdMappingDAO.findAll();
for (CDMapping ff : f) {
System.out.println(ff);
}
}
}
然后你的命令行运行器变成这样:
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class GraphHandler implements CommandLineRunner {
private final SomeGraphBasedService someGraphBasedService;
public GraphHandler(SomeGraphBasedService someGraphBasedService) {
this.someGraphBasedService = someGraphBasedService;
}
public void run(String... args) {
this.someGraphBasedService.createInitialModel();
this.someGraphBasedService.runUseCase();
}
}
虽然第一个显然是一种解决方法,但另一个解决方案是我在现实世界场景中更喜欢的解决方案。
无论如何,现在两者的输出都符合预期:
CDMapping{id=21, classC=ClassC{name='Class C1'}, classD=ClassD{classB=ClassB{name='Class B1'}}, attribute='Default Value'}
CDMapping{id=23, classC=ClassC{name='Class C1'}, classD=ClassD{classB=ClassB{name='Class B2'}}, attribute='Special Value'}
CDMapping{id=25, classC=ClassC{name='Class C2'}, classD=ClassD{classB=ClassB{name='Class B1'}}, attribute='Default Value'}
CDMapping{id=27, classC=ClassC{name='Class C2'}, classD=ClassD{classB=ClassB{name='Class B2'}}, attribute='Special Value'}