diff --git a/jdbc/howto/README.adoc b/jdbc/howto/README.adoc
index d7afa08b..0be53829 100644
--- a/jdbc/howto/README.adoc
+++ b/jdbc/howto/README.adoc
@@ -2,4 +2,12 @@
=== ID Generation
-Demonstrates the various ways how the user might control generation of IDs when the default of database generated IDs is not sufficient.
\ No newline at end of file
+Demonstrates the various ways how the user might control generation of IDs when the default of database generated IDs is not sufficient.
+
+=== Bidirectional aggregate internal relationships.
+
+Demonstrates how to maintain a bidirectional relationship within a single aggregate.
+
+=== Bidirectional relationships between aggregates.
+
+Demonstrates how to maintain a bidirectional relationship between aggregates.
\ No newline at end of file
diff --git a/jdbc/howto/bidirectionalexternal/README.adoc b/jdbc/howto/bidirectionalexternal/README.adoc
new file mode 100644
index 00000000..96de5745
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/README.adoc
@@ -0,0 +1,6 @@
+== Spring Data JDBC How To Bidirectional External Relationship
+
+An external relationship is one that links one aggregate from another one.
+Spring Data JDBC models such a relationship by referencing the aggregate by id, possibly wrapped in an `AggregateReference`.
+
+For a bidirectional navigation it turns out you just need an additional query method.
\ No newline at end of file
diff --git a/jdbc/howto/bidirectionalexternal/pom.xml b/jdbc/howto/bidirectionalexternal/pom.xml
new file mode 100644
index 00000000..f2a2d16d
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-how-to
+ 2.0.0.BUILD-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+
+ spring-data-jdbc-how-to-bidirectional-external
+
+ Spring Data JDBC - How to model bidirectional relationships between aggregates
+ Sample project for Spring Data JDBC demonstrating how to model bidirectional relationships between aggregates.
+ It serves as a source code repository for a How To article on the Spring Blog
+ https://projects.spring.io/spring-data-jdbc
+ 2021
+
\ No newline at end of file
diff --git a/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplication.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplication.java
new file mode 100644
index 00000000..12c3eea2
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplication.java
@@ -0,0 +1,13 @@
+package example.springdata.jdbc.howto.bidirectionalexternal;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+class BidirectionalApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BidirectionalApplication.class, args);
+ }
+
+}
diff --git a/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Minion.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Minion.java
new file mode 100644
index 00000000..1e2411fc
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Minion.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.springdata.jdbc.howto.bidirectionalexternal;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.jdbc.core.mapping.AggregateReference;
+
+class Minion {
+ @Id
+ Long id;
+ String name;
+ AggregateReference evilMaster;
+
+ Minion(String name, AggregateReference evilMaster) {
+ this.name = name;
+ this.evilMaster = evilMaster;
+ }
+}
diff --git a/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/MinionRepository.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/MinionRepository.java
new file mode 100644
index 00000000..d80307ef
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/MinionRepository.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.springdata.jdbc.howto.bidirectionalexternal;
+
+import java.util.Collection;
+
+import org.springframework.data.jdbc.repository.query.Query;
+import org.springframework.data.repository.CrudRepository;
+
+interface MinionRepository extends CrudRepository {
+
+ @Query("SELECT * FROM MINION WHERE EVIL_MASTER = :id")
+ Collection findByEvilMaster(Long id);
+}
diff --git a/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Person.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Person.java
new file mode 100644
index 00000000..cd064024
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/Person.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.springdata.jdbc.howto.bidirectionalexternal;
+
+import org.springframework.data.annotation.Id;
+
+class Person {
+ @Id
+ Long id;
+ String name;
+
+ Person(String name) {
+ this.name = name;
+ }
+}
diff --git a/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/PersonRepository.java b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/PersonRepository.java
new file mode 100644
index 00000000..6a6f0c98
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/java/example/springdata/jdbc/howto/bidirectionalexternal/PersonRepository.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.springdata.jdbc.howto.bidirectionalexternal;
+
+import org.springframework.data.repository.CrudRepository;
+
+interface PersonRepository extends CrudRepository {
+
+}
diff --git a/jdbc/howto/bidirectionalexternal/src/main/resources/application.properties b/jdbc/howto/bidirectionalexternal/src/main/resources/application.properties
new file mode 100644
index 00000000..3a7895a8
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/resources/application.properties
@@ -0,0 +1 @@
+logging.level.org.springframework.jdbc.core.JdbcTemplate=debug
diff --git a/jdbc/howto/bidirectionalexternal/src/main/resources/schema.sql b/jdbc/howto/bidirectionalexternal/src/main/resources/schema.sql
new file mode 100644
index 00000000..379fed64
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/main/resources/schema.sql
@@ -0,0 +1,13 @@
+CREATE TABLE PERSON
+(
+ ID IDENTITY PRIMARY KEY,
+ NAME VARCHAR(255)
+);
+
+CREATE TABLE MINION
+(
+ ID IDENTITY PRIMARY KEY,
+ NAME VARCHAR(255),
+ EVIL_MASTER BIGINT,
+ CONSTRAINT FK_MINION_PERSON FOREIGN KEY (EVIL_MASTER) REFERENCES PERSON
+);
diff --git a/jdbc/howto/bidirectionalexternal/src/test/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplicationTests.java b/jdbc/howto/bidirectionalexternal/src/test/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplicationTests.java
new file mode 100644
index 00000000..6a1a4067
--- /dev/null
+++ b/jdbc/howto/bidirectionalexternal/src/test/java/example/springdata/jdbc/howto/bidirectionalexternal/BidirectionalApplicationTests.java
@@ -0,0 +1,44 @@
+package example.springdata.jdbc.howto.bidirectionalexternal;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.Collection;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
+import org.springframework.data.jdbc.core.mapping.AggregateReference;
+
+@SpringBootTest
+class BidirectionalApplicationTests {
+
+ @Autowired
+ MinionRepository minions;
+
+ @Autowired
+ PersonRepository persons;
+
+ @Autowired
+ JdbcAggregateTemplate template;
+
+ @Test
+ void bidi() {
+
+
+ AggregateReference scarletReference = AggregateReference.to(persons.save(new Person("Scarlet")).id);
+
+ Minion bob = minions.save(new Minion("Bob", scarletReference));
+ Minion kevin = minions.save(new Minion("Kevin", scarletReference));
+ Minion stuart = minions.save(new Minion("Stuart", scarletReference));
+
+ AggregateReference gruReference = AggregateReference.to(persons.save(new Person("Gru")).id);
+ Minion tim = minions.save(new Minion("Tim", gruReference));
+
+ Collection minionsOfScarlet = minions.findByEvilMaster(scarletReference.getId());
+
+ assertThat(minionsOfScarlet).extracting(m -> m.name).containsExactlyInAnyOrder("Bob", "Kevin", "Stuart");
+ }
+
+
+}
diff --git a/jdbc/howto/bidirectionalinternal/README.adoc b/jdbc/howto/bidirectionalinternal/README.adoc
new file mode 100644
index 00000000..9c33b54f
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/README.adoc
@@ -0,0 +1,6 @@
+== Spring Data JDBC How To Bidirectional Internal Relationship
+
+An internal relationship is one that links entities within an aggregate.
+Spring Data JDBC doesn't have special mechanics to maintain such a relationship.
+Instead you may choose to maintain the reverse relationship while creating the aggregate.
+This takes care of the construction during initial creation or modification of the aggregate, and also of the construction when loading an aggregate from the database.
\ No newline at end of file
diff --git a/jdbc/howto/bidirectionalinternal/pom.xml b/jdbc/howto/bidirectionalinternal/pom.xml
new file mode 100644
index 00000000..246b2ed1
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-how-to
+ 2.0.0.BUILD-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+
+ spring-data-jdbc-how-to-bidirectional-internal
+
+ Spring Data JDBC - How to model aggregate internal bidirectional relationships
+ Sample project for Spring Data JDBC demonstrating how to model aggregate internal bidirectional relationships.
+ It serves as a source code repository for a How To article on the Spring Blog
+ https://projects.spring.io/spring-data-jdbc
+ 2021
+
\ No newline at end of file
diff --git a/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplication.java b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplication.java
new file mode 100644
index 00000000..a53130f0
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplication.java
@@ -0,0 +1,13 @@
+package example.springdata.jdbc.howto.bidirectionalinternal;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+class BiDiInternalApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BiDiInternalApplication.class, args);
+ }
+
+}
diff --git a/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Minion.java b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Minion.java
new file mode 100644
index 00000000..7e3aca72
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Minion.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.springdata.jdbc.howto.bidirectionalinternal;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.PersistenceConstructor;
+
+class Minion {
+ @Id
+ Long id;
+ String name;
+ final Set toys = new HashSet<>();
+
+ Minion(String name) {
+ this.name = name;
+ }
+
+ @PersistenceConstructor
+ private Minion(Long id, String name, Collection toys) {
+
+ this.id = id;
+ this.name = name;
+ toys.forEach(this::addToy);
+ }
+
+ public void addToy(Toy toy) {
+ toys.add(toy);
+ toy.minion = this;
+ }
+
+ public void showYourToys() {
+ toys.forEach(Toy::sayHello);
+ }
+}
diff --git a/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/MinionRepository.java b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/MinionRepository.java
new file mode 100644
index 00000000..4c4fb5ab
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/MinionRepository.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.springdata.jdbc.howto.bidirectionalinternal;
+
+import org.springframework.data.repository.CrudRepository;
+
+public interface MinionRepository extends CrudRepository {
+}
diff --git a/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Toy.java b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Toy.java
new file mode 100644
index 00000000..dc55c1f8
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/java/example/springdata/jdbc/howto/bidirectionalinternal/Toy.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package example.springdata.jdbc.howto.bidirectionalinternal;
+
+import org.springframework.data.annotation.Transient;
+
+class Toy {
+ String name;
+
+ @Transient // org.SPRINGframework.DATA...
+ Minion minion;
+
+ Toy(String name) {
+ this.name = name;
+ }
+
+ public void sayHello() {
+ System.out.println("I'm " + name + " and I'm a toy of " + minion.name);
+ }
+}
diff --git a/jdbc/howto/bidirectionalinternal/src/main/resources/application.properties b/jdbc/howto/bidirectionalinternal/src/main/resources/application.properties
new file mode 100644
index 00000000..3a7895a8
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/resources/application.properties
@@ -0,0 +1 @@
+logging.level.org.springframework.jdbc.core.JdbcTemplate=debug
diff --git a/jdbc/howto/bidirectionalinternal/src/main/resources/schema.sql b/jdbc/howto/bidirectionalinternal/src/main/resources/schema.sql
new file mode 100644
index 00000000..7f67813e
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/main/resources/schema.sql
@@ -0,0 +1,11 @@
+CREATE TABLE MINION
+(
+ ID IDENTITY PRIMARY KEY,
+ NAME VARCHAR(255),
+);
+
+CREATE TABLE TOY
+(
+ MINION BIGINT NOT NULL,
+ NAME VARCHAR(255)
+);
diff --git a/jdbc/howto/bidirectionalinternal/src/test/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplicationTests.java b/jdbc/howto/bidirectionalinternal/src/test/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplicationTests.java
new file mode 100644
index 00000000..1a460086
--- /dev/null
+++ b/jdbc/howto/bidirectionalinternal/src/test/java/example/springdata/jdbc/howto/bidirectionalinternal/BiDiInternalApplicationTests.java
@@ -0,0 +1,26 @@
+package example.springdata.jdbc.howto.bidirectionalinternal;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class BiDiInternalApplicationTests {
+
+ @Autowired
+ MinionRepository minions;
+
+ @Test
+ void biDi() {
+
+ Minion bob = new Minion("Bob");
+ bob.addToy(new Toy("Dolphin"));
+ bob.addToy(new Toy("Tiger Duck"));
+
+ minions.save(bob);
+
+ Minion reloaded = minions.findById(bob.id).get();
+
+ reloaded.showYourToys();
+ }
+}
diff --git a/jdbc/howto/pom.xml b/jdbc/howto/pom.xml
index d9cc9ee4..53dd42ec 100644
--- a/jdbc/howto/pom.xml
+++ b/jdbc/howto/pom.xml
@@ -19,6 +19,8 @@
2021
+ bidirectionalexternal
+ bidirectionalinternal
idgeneration