|
49 | 49 | import org.springframework.data.domain.Sort;
|
50 | 50 | import org.springframework.data.neo4j.core.Neo4jOperations;
|
51 | 51 | import org.springframework.data.neo4j.integration.shared.common.DoritoEatingPerson;
|
| 52 | +import org.springframework.data.neo4j.integration.shared.common.GH2621Domain; |
52 | 53 | import org.springframework.data.neo4j.test.Neo4jImperativeTestConfiguration;
|
53 | 54 | import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
|
54 | 55 | import org.springframework.data.neo4j.core.Neo4jTemplate;
|
|
79 | 80 | import org.springframework.data.repository.query.Param;
|
80 | 81 | import org.springframework.transaction.PlatformTransactionManager;
|
81 | 82 | import org.springframework.transaction.annotation.EnableTransactionManagement;
|
| 83 | +import org.springframework.transaction.support.TransactionTemplate; |
82 | 84 |
|
83 | 85 | /**
|
84 | 86 | * @author Gerrit Meier
|
@@ -459,6 +461,57 @@ public void projectionRespected(@Autowired Neo4jOperations neo4jOperations) {
|
459 | 461 | assertThat(saved).hasValueSatisfying(it -> assertThat(it.getFriends()).isEmpty());
|
460 | 462 | }
|
461 | 463 |
|
| 464 | + @Test // GH-2621 |
| 465 | + public void nestedProjectWithFluentOpsShouldWork(@Autowired TransactionTemplate transactionTemplate, @Autowired Neo4jTemplate neo4jTemplate) { |
| 466 | + |
| 467 | + GH2621Domain.FooProjection fooProjection = transactionTemplate.execute(tx -> { |
| 468 | + final GH2621Domain.BarBarProjection barBarProjection = new GH2621Domain.BarBarProjection("v1", "v2"); |
| 469 | + return neo4jTemplate.save(GH2621Domain.Foo.class).one(new GH2621Domain.FooProjection(barBarProjection)); |
| 470 | + }); |
| 471 | + |
| 472 | + assertThat(fooProjection.getBar()).isNotNull(); |
| 473 | + assertThat(fooProjection.getBar().getValue1()).isEqualTo("v1"); |
| 474 | + // There is no way to deduce from a `BarProjection` field the correlation from `BarBarProjection to `BarBar` |
| 475 | + // without throwing a dice and we are not going to try this |
| 476 | + assertThat(fooProjection.getBar()).isInstanceOf(GH2621Domain.BarProjection.class); |
| 477 | + |
| 478 | + // The result above is reflected in the graph |
| 479 | + try (Session session = driver.session(bookmarkCapture.createSessionConfig())) { |
| 480 | + Record result = session.run("MATCH (n:GH2621Bar) RETURN n").single(); |
| 481 | + assertThat(result.get("n").asNode().get("value1").asString()).isEqualTo("v1"); |
| 482 | + } |
| 483 | + } |
| 484 | + |
| 485 | + @Test // GH-2621 |
| 486 | + public void nestedProjectWithFluentOpsShouldWork2(@Autowired TransactionTemplate transactionTemplate, @Autowired Neo4jTemplate neo4jTemplate) { |
| 487 | + |
| 488 | + GH2621Domain.FooProjection fooProjection = transactionTemplate.execute(tx -> { |
| 489 | + GH2621Domain.Foo foo = new GH2621Domain.Foo(new GH2621Domain.BarBar("v1", "v2")); |
| 490 | + return neo4jTemplate.saveAs(foo, GH2621Domain.FooProjection.class); |
| 491 | + }); |
| 492 | + |
| 493 | + assertThat(fooProjection.getBar()).isNotNull(); |
| 494 | + assertThat(fooProjection.getBar().getValue1()).isEqualTo("v1"); |
| 495 | + // There is no way to deduce from a `BarProjection` field the correlation from `BarBarProjection to `BarBar` |
| 496 | + // without throwing a dice and we are not going to try this |
| 497 | + assertThat(fooProjection.getBar()).isInstanceOf(GH2621Domain.BarProjection.class); |
| 498 | + |
| 499 | + // This is a different here as the concrete dto was used during save ops, so the |
| 500 | + try (Session session = driver.session(bookmarkCapture.createSessionConfig())) { |
| 501 | + Record result = session.run("MATCH (n:GH2621Bar:GH2621BarBar) RETURN n").single(); |
| 502 | + org.neo4j.driver.types.Node node = result.get("n").asNode(); |
| 503 | + assertThat(node.get("value1").asString()).isEqualTo("v1"); |
| 504 | + // This is a limitation of the Spring Data Commons support for the DTO projections |
| 505 | + // when we reach org/springframework/data/neo4j/core/PropertyFilterSupport.java:141 we call |
| 506 | + // org.springframework.data.projection.ProjectionFactory.getProjectionInformation and we only |
| 507 | + // have the concrete type information at hand, in the domain example FooProjection#bar, which points |
| 508 | + // to BarProjection, without any clue that we do want a BarBarProjection being used during saving. |
| 509 | + // So with the example in the ticket, saving value2 (or anything on the BarBarProjection) won't |
| 510 | + // be possible |
| 511 | + assertThat(node.get("value2").isNull()).isTrue(); |
| 512 | + } |
| 513 | + } |
| 514 | + |
462 | 515 | private static void projectedEntities(PersonDepartmentQueryResult personAndDepartment) {
|
463 | 516 | assertThat(personAndDepartment.getPerson()).extracting(PersonEntity::getId).isEqualTo("p1");
|
464 | 517 | assertThat( personAndDepartment. getPerson()). extracting( PersonEntity:: getEmail). isEqualTo( "[email protected]");
|
@@ -621,5 +674,10 @@ public PlatformTransactionManager transactionManager(Driver driver, DatabaseSele
|
621 | 674 | public boolean isCypher5Compatible() {
|
622 | 675 | return neo4jConnectionSupport.isCypher5SyntaxCompatible();
|
623 | 676 | }
|
| 677 | + |
| 678 | + @Bean |
| 679 | + TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) { |
| 680 | + return new TransactionTemplate(transactionManager); |
| 681 | + } |
624 | 682 | }
|
625 | 683 | }
|
0 commit comments