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