Adds examples how to model bidirectional relationships.

Original pull request #629
This commit is contained in:
Jens Schauder
2021-09-17 10:43:59 +02:00
parent de08dcf9c0
commit 387ca8c6c3
21 changed files with 400 additions and 1 deletions

View File

@@ -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.
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.

View File

@@ -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.

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-jdbc-how-to</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-jdbc-how-to-bidirectional-external</artifactId>
<name>Spring Data JDBC - How to model bidirectional relationships between aggregates</name>
<description>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</description>
<url>https://projects.spring.io/spring-data-jdbc</url>
<inceptionYear>2021</inceptionYear>
</project>

View File

@@ -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);
}
}

View File

@@ -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<Person, Long> evilMaster;
Minion(String name, AggregateReference<Person, Long> evilMaster) {
this.name = name;
this.evilMaster = evilMaster;
}
}

View File

@@ -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<Minion, Long> {
@Query("SELECT * FROM MINION WHERE EVIL_MASTER = :id")
Collection<Minion> findByEvilMaster(Long id);
}

View File

@@ -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;
}
}

View File

@@ -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<Person, Long> {
}

View File

@@ -0,0 +1 @@
logging.level.org.springframework.jdbc.core.JdbcTemplate=debug

View File

@@ -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
);

View File

@@ -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<Person, Long> 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<Person, Long> gruReference = AggregateReference.to(persons.save(new Person("Gru")).id);
Minion tim = minions.save(new Minion("Tim", gruReference));
Collection<Minion> minionsOfScarlet = minions.findByEvilMaster(scarletReference.getId());
assertThat(minionsOfScarlet).extracting(m -> m.name).containsExactlyInAnyOrder("Bob", "Kevin", "Stuart");
}
}

View File

@@ -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.

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-jdbc-how-to</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-jdbc-how-to-bidirectional-internal</artifactId>
<name>Spring Data JDBC - How to model aggregate internal bidirectional relationships</name>
<description>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</description>
<url>https://projects.spring.io/spring-data-jdbc</url>
<inceptionYear>2021</inceptionYear>
</project>

View File

@@ -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);
}
}

View File

@@ -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<Toy> toys = new HashSet<>();
Minion(String name) {
this.name = name;
}
@PersistenceConstructor
private Minion(Long id, String name, Collection<Toy> 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);
}
}

View File

@@ -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<Minion, Long> {
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1 @@
logging.level.org.springframework.jdbc.core.JdbcTemplate=debug

View File

@@ -0,0 +1,11 @@
CREATE TABLE MINION
(
ID IDENTITY PRIMARY KEY,
NAME VARCHAR(255),
);
CREATE TABLE TOY
(
MINION BIGINT NOT NULL,
NAME VARCHAR(255)
);

View File

@@ -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();
}
}

View File

@@ -19,6 +19,8 @@
<inceptionYear>2021</inceptionYear>
<modules>
<module>bidirectionalexternal</module>
<module>bidirectionalinternal</module>
<module>idgeneration</module>
</modules>