diff --git a/jdbc/pom.xml b/jdbc/pom.xml index 742457e16..81dcfbe55 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -22,6 +22,7 @@ immutables jmolecules jooq + singlequeryloading graalvm-native diff --git a/jdbc/singlequeryloading/pom.xml b/jdbc/singlequeryloading/pom.xml new file mode 100644 index 000000000..4c174a1d1 --- /dev/null +++ b/jdbc/singlequeryloading/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + singlequeryloading + + 21 + 21 + + + + org.springframework.data.examples + spring-data-jdbc-examples + 2.0.0.BUILD-SNAPSHOT + ../pom.xml + + + + Spring Data JDBC - Demonstration of Single Query Loading + Sample project demonstrating Single Query Loading with Spring Data JDBC + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + org.springframework + spring-test + + + org.testcontainers + postgresql + + + org.postgresql + postgresql + + + diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Cat.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Cat.java new file mode 100644 index 000000000..48e3e1f23 --- /dev/null +++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Cat.java @@ -0,0 +1,24 @@ +/* + * Copyright 2023 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.singlequeryloading; + +/** + * A simple entity for use in a collection of {@link PetOwner}. + * + * @author Jens Schauder + */ +record Cat(String name) { +} diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Config.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Config.java new file mode 100644 index 000000000..4bcf284fb --- /dev/null +++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Config.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 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.singlequeryloading; + +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; +import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration; +import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; +import org.springframework.data.relational.RelationalManagedTypes; +import org.springframework.data.relational.core.mapping.NamingStrategy; + +import java.util.Optional; + +/** + * Spring application context configuration that enables Single Query Loading. + * + * @author Jens Schauder + */ +@SpringBootConfiguration +@EnableJdbcRepositories +public class Config extends AbstractJdbcConfiguration { + + + @Override + public JdbcMappingContext jdbcMappingContext(Optional namingStrategy, JdbcCustomConversions customConversions, RelationalManagedTypes jdbcManagedTypes) { + + JdbcMappingContext jdbcMappingContext = super.jdbcMappingContext(namingStrategy, customConversions, jdbcManagedTypes); + jdbcMappingContext.setSingleQueryLoadingEnabled(true); + return jdbcMappingContext; + } +} diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Dog.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Dog.java new file mode 100644 index 000000000..450efe97e --- /dev/null +++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Dog.java @@ -0,0 +1,24 @@ +/* + * Copyright 2023 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.singlequeryloading; + +/** + * A simple entity for use in a collection of {@link PetOwner}. + * + * @author Jens Schauder + */ +record Dog(String name) { +} \ No newline at end of file diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Fish.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Fish.java new file mode 100644 index 000000000..a2f41f651 --- /dev/null +++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Fish.java @@ -0,0 +1,24 @@ +/* + * Copyright 2023 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.singlequeryloading; + +/** + * A simple entity for use in a collection of {@link PetOwner}. + * + * @author Jens Schauder + */ +record Fish(String name) { +} diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwner.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwner.java new file mode 100644 index 000000000..8eb9c1099 --- /dev/null +++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwner.java @@ -0,0 +1,61 @@ +/* + * Copyright 2023 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.singlequeryloading; + +import org.springframework.data.annotation.Id; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * An aggregate with mutliple collections. + * + * @author Jens Schauder + */ +class PetOwner { + + @Id + Long Id; + + String name; + + List dogs = new ArrayList<>(); + + List cats = new ArrayList<>(); + + List fish = new ArrayList<>(); + + public PetOwner(String name, List cats, List dogs, List fish) { + + this.name = name; + this.cats = cats; + this.dogs = dogs; + this.fish = fish; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PetOwner petOwner = (PetOwner) o; + return Objects.equals(Id, petOwner.Id) && Objects.equals(name, petOwner.name) && Objects.equals(dogs, petOwner.dogs) && Objects.equals(cats, petOwner.cats) && Objects.equals(fish, petOwner.fish); + } + + @Override + public int hashCode() { + return Objects.hash(Id, name, dogs, cats, fish); + } +} diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwnerRepository.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwnerRepository.java new file mode 100644 index 000000000..8fd2d4c6c --- /dev/null +++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwnerRepository.java @@ -0,0 +1,29 @@ +/* + * Copyright 2023 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.singlequeryloading; + +import org.springframework.data.repository.ListCrudRepository; + +import java.util.List; + + +/** + * Repository to access {@link PetOwner} instances. + * + * @author Jens Schauder + */ +interface PetOwnerRepository extends ListCrudRepository { + List findByName(String marry); +} diff --git a/jdbc/singlequeryloading/src/main/resources/application.properties b/jdbc/singlequeryloading/src/main/resources/application.properties new file mode 100644 index 000000000..d467e12d4 --- /dev/null +++ b/jdbc/singlequeryloading/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.datasource.url=jdbc:tc:postgresql:16.0:///test +logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG +spring.sql.init.mode=always \ No newline at end of file diff --git a/jdbc/singlequeryloading/src/main/resources/schema.sql b/jdbc/singlequeryloading/src/main/resources/schema.sql new file mode 100644 index 000000000..b398d1147 --- /dev/null +++ b/jdbc/singlequeryloading/src/main/resources/schema.sql @@ -0,0 +1,27 @@ +CREATE TABLE PET_OWNER +( + ID SERIAL PRIMARY KEY, + NAME VARCHAR(255) +); + +CREATE TABLE CAT +( + PET_OWNER INT, + PET_OWNER_KEY INT, + NAME VARCHAR(255) +); + +CREATE TABLE DOG +( + PET_OWNER INT, + PET_OWNER_KEY INT, + NAME VARCHAR(255) +); + +CREATE TABLE FISH +( + PET_OWNER INT, + PET_OWNER_KEY INT, + NAME VARCHAR(255) +); + diff --git a/jdbc/singlequeryloading/src/test/java/example/springdata/jdbc/singlequeryloading/SingleQueryLoadingApplicationTests.java b/jdbc/singlequeryloading/src/test/java/example/springdata/jdbc/singlequeryloading/SingleQueryLoadingApplicationTests.java new file mode 100644 index 000000000..73768f8fa --- /dev/null +++ b/jdbc/singlequeryloading/src/test/java/example/springdata/jdbc/singlequeryloading/SingleQueryLoadingApplicationTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2023 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.singlequeryloading; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.data.jdbc.core.JdbcAggregateTemplate; +import org.springframework.data.relational.core.query.Criteria; +import org.springframework.data.relational.core.query.CriteriaDefinition; +import org.springframework.data.relational.core.query.Query; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +/** + * Run tests demonstrating the use of Single Query Loading. You'll have to observe the executed queries. + * + * @author Jens Schauder + */ +@JdbcTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class SingleQueryLoadingApplicationTests { + + @Autowired + PetOwnerRepository petOwners; + @Autowired + JdbcAggregateTemplate template; + private PetOwner emil; + private PetOwner marry; + + @BeforeEach + void setup() { + petOwners.deleteAll(); + emil = petOwners.save(new PetOwner("Emil", + List.of( + new Cat("Edgar"), + new Cat("Einstein"), + new Cat("Elliot"), + new Cat("Elton"), + new Cat("Evan") + ), + List.of( + new Dog("Eric"), + new Dog("Eddie"), + new Dog("Eke"), + new Dog("Echo") + ), + List.of( + new Fish("Floaty") + ) + + )); + + marry = petOwners.save(new PetOwner("Marry", + List.of( + new Cat("Mars"), + new Cat("Maverick"), + new Cat("Max") + ), + List.of( + new Dog("Molly"), + new Dog("Murphy"), + new Dog("Madison"), + new Dog("Macie") + ), + List.of( + new Fish("Mahi Mahi"), + new Fish("Mr. Limpet") + ) + )); + } + + @Test + void loadById() { + + PetOwner emilReloaded = petOwners.findById(emil.Id).orElseThrow(); + + assertThat(emilReloaded).isEqualTo(emil); + + } + + @Test + void loadByName() { + + CriteriaDefinition criteria = Criteria.where("name").is("Marry"); + Query query = Query.query(criteria); + List marries = (List) template.findAll(query, PetOwner.class); + assertThat(marries).containsExactly(marry); + } + +}