From 68a15b0f5a36113fe44a97b9212bd497e75cc563 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 12 Mar 2018 09:43:55 +0100 Subject: [PATCH] #346 - Demonstrating the use of @Query with and without @Modifying. Added assertions to existing tests to verify they work as intended. Changed Model to a value object because we can. --- jdbc/basics/README.adoc | 4 +- .../aggregate/AggregateConfiguration.java | 33 ++++---- .../jdbc/basics/aggregate/LegoSet.java | 5 +- .../basics/aggregate/LegoSetRepository.java | 19 ++++- .../jdbc/basics/aggregate/Model.java | 8 +- .../jdbc/basics/aggregate/ModelReport.java | 29 +++++++ .../jdbc/basics/aggregate/AggregateTests.java | 77 +++++++++++++++++-- 7 files changed, 139 insertions(+), 36 deletions(-) create mode 100644 jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/ModelReport.java diff --git a/jdbc/basics/README.adoc b/jdbc/basics/README.adoc index e3ebb5a1..9d8f2e34 100644 --- a/jdbc/basics/README.adoc +++ b/jdbc/basics/README.adoc @@ -1,6 +1,5 @@ == Spring Data JDBC basics - === SimpleEntityTests This example demonstrate basic usage of JDBC based repositories. @@ -26,3 +25,6 @@ This is achieved by providing a custom `NamingStrategy` which maps both to the s * When the database returns a data type for query which Spring Data JDBC doesn't map out of the box a custom conversion can be registered using a `ConversionCustomizer` as demonstrated in `AggregateContext.conversionCustomizer()`. +* `LegoSetRepository` has methods that utilize `@Query` annotations. + +* Note that `Model` is a value class, i.e. it is immutable, and doesn't have an ID. \ No newline at end of file diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AggregateConfiguration.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AggregateConfiguration.java index a767b0f8..c319e30c 100644 --- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AggregateConfiguration.java +++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/AggregateConfiguration.java @@ -38,33 +38,32 @@ import org.springframework.lang.Nullable; @Configuration @EnableJdbcRepositories public class AggregateConfiguration { + final AtomicInteger id = new AtomicInteger(0); @Bean public ApplicationListener idSetting() { - final AtomicInteger id = new AtomicInteger(0); - return (ApplicationListener) event -> { - Object entity = event.getEntity(); - - if (entity instanceof LegoSet) { - - LegoSet legoSet = (LegoSet) entity; - - if (legoSet.getId() == 0) { - legoSet.setId(id.incrementAndGet()); - } - - Manual manual = legoSet.getManual(); - - if (manual != null) { - manual.setId((long) legoSet.getId()); - } + if (event.getEntity() instanceof LegoSet) { + setIds((LegoSet) event.getEntity()); } }; } + private void setIds(LegoSet legoSet) { + + if (legoSet.getId() == 0) { + legoSet.setId(id.incrementAndGet()); + } + + Manual manual = legoSet.getManual(); + + if (manual != null) { + manual.setId((long) legoSet.getId()); + } + } + @Bean public NamingStrategy namingStrategy() { diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSet.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSet.java index e2b88e84..9e81fe92 100644 --- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSet.java +++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSet.java @@ -77,10 +77,7 @@ public class LegoSet { public void addModel(String name, String description) { - Model model = new Model(); - model.name = name; - model.description = description; - + Model model = new Model(name, description); models.put(name, model); } } diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSetRepository.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSetRepository.java index 5c33c5d1..5ae6a47b 100644 --- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSetRepository.java +++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/LegoSetRepository.java @@ -15,11 +15,28 @@ */ package example.springdata.jdbc.basics.aggregate; +import org.springframework.data.jdbc.repository.query.Modifying; +import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; + +import java.util.List; /** * A repository for {@link LegoSet}. * * @author Jens Schauder */ -interface LegoSetRepository extends CrudRepository {} +interface LegoSetRepository extends CrudRepository { + + @Query("SELECT m.name modelName, m.description, l.name setName" + + " FROM model m" + + " JOIN LegoSet l" + + " ON m.legoset = l.id" + + " WHERE :age BETWEEN l.minAge and l.maxAge") + List reportModelForAge(@Param("age") int age); + + @Modifying + @Query("UPDATE model set name = lower(name) WHERE name <> lower(name)") + int lowerCaseMapKeys(); +} diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Model.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Model.java index 6eb1bbf8..343ee94d 100644 --- a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Model.java +++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/Model.java @@ -15,18 +15,16 @@ */ package example.springdata.jdbc.basics.aggregate; -import lombok.ToString; - -import org.springframework.data.annotation.Id; +import lombok.Value; /** * One of potentially multiple models that can be build from a single {@link LegoSet}. No getters or setters needed. * * @author Jens Schauder */ -@ToString +@Value public class Model { - @Id String name; + String name; String description; } diff --git a/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/ModelReport.java b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/ModelReport.java new file mode 100644 index 00000000..db71a9a6 --- /dev/null +++ b/jdbc/basics/src/main/java/example/springdata/jdbc/basics/aggregate/ModelReport.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 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 + * + * http://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.basics.aggregate; + +import lombok.Value; + +/** + * @author Jens Schauder + */ +@Value +public class ModelReport { + + private final String modelName; + private final String description; + private final String setName; +} diff --git a/jdbc/basics/src/test/java/example/springdata/jdbc/basics/aggregate/AggregateTests.java b/jdbc/basics/src/test/java/example/springdata/jdbc/basics/aggregate/AggregateTests.java index f70c795f..0f980ea8 100644 --- a/jdbc/basics/src/test/java/example/springdata/jdbc/basics/aggregate/AggregateTests.java +++ b/jdbc/basics/src/test/java/example/springdata/jdbc/basics/aggregate/AggregateTests.java @@ -15,15 +15,21 @@ */ package example.springdata.jdbc.basics.aggregate; +import static org.assertj.core.api.Assertions.*; + import example.springdata.jdbc.basics.Output; import java.time.Period; +import java.util.Arrays; +import java.util.List; +import org.assertj.core.groups.Tuple; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureJdbc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; /** @@ -34,6 +40,7 @@ import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest(classes = AggregateConfiguration.class) @AutoConfigureJdbc +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class AggregateTests { @Autowired LegoSetRepository repository; @@ -41,34 +48,88 @@ public class AggregateTests { @Test public void exerciseSomewhatComplexEntity() { - LegoSet smallCar = createLegoSet(); + LegoSet smallCar = createLegoSet("Small Car 01", 5, 12); smallCar.setManual(new Manual("Just put all the pieces together in the right order", "Jens Schauder")); smallCar.addModel("suv", "SUV with sliding doors."); smallCar.addModel("roadster", "Slick red roadster."); repository.save(smallCar); - Output.list(repository.findAll(), "Original LegoSet"); + Iterable legoSets = repository.findAll(); + Output.list(legoSets, "Original LegoSet"); + checkLegoSets(legoSets, "Just put all the pieces together in the right order", 2); smallCar.getManual().setText("Just make it so it looks like a car."); smallCar.addModel("pickup", "A pickup truck with some tools in the back."); repository.save(smallCar); - Output.list(repository.findAll(), "Updated"); + legoSets = repository.findAll(); + Output.list(legoSets, "Updated"); + checkLegoSets(legoSets, "Just make it so it looks like a car.", 3); smallCar.setManual(new Manual("One last attempt: Just build a car! Ok?", "Jens Schauder")); repository.save(smallCar); - Output.list(repository.findAll(), "Manual replaced"); + legoSets = repository.findAll(); + Output.list(legoSets, "Manual replaced"); + checkLegoSets(legoSets, "One last attempt: Just build a car! Ok?", 3); + } - private LegoSet createLegoSet() { + @Test + public void customQueries() { + + LegoSet smallCars = createLegoSet("Small Car - 01", 5, 10); + smallCars.setManual(new Manual("Just put all the pieces together in the right order", "Jens Schauder")); + + smallCars.addModel("SUV", "SUV with sliding doors."); + smallCars.addModel("roadster", "Slick red roadster."); + + LegoSet f1Racer = createLegoSet("F1 Racer", 6, 15); + f1Racer.setManual(new Manual("Build a helicopter or a plane", "M. Shoemaker")); + + f1Racer.addModel("F1 Ferrari 2018", "A very fast red car."); + + LegoSet constructionVehicles = createLegoSet("Construction Vehicles", 3, 6); + constructionVehicles.setManual( + new Manual("Build a Road Roler, a Mobile Crane, a Tracked Dumper, or a Backhoe Loader ", "Bob the Builder")); + + constructionVehicles.addModel("scoop", "A backhoe loader"); + constructionVehicles.addModel("Muck", "Muck is a continuous tracked dump truck with an added bulldozer blade"); + constructionVehicles.addModel("lofty", "A mobile crane"); + constructionVehicles.addModel("roley", + "A road roller that loves to make up songs and frequently spins his eyes when he is excited."); + + repository.saveAll(Arrays.asList(smallCars, f1Racer, constructionVehicles)); + + List report = repository.reportModelForAge(6); + Output.list(report, "Model Report"); + + assertThat(report).hasSize(7) + .allMatch(m -> m.getDescription() != null && m.getModelName() != null && m.getSetName() != null); + + int updated = repository.lowerCaseMapKeys(); + // SUV, F1 Ferrari 2018 and Muck get updated + assertThat(updated).isEqualTo(3); + + } + + private LegoSet createLegoSet(String name, int minimumAge, int maximumAge) { LegoSet smallCar = new LegoSet(); - smallCar.setName("Small Car 01"); - smallCar.setMinimumAge(Period.ofYears(5)); - smallCar.setMaximumAge(Period.ofYears(12)); + smallCar.setName(name); + smallCar.setMinimumAge(Period.ofYears(minimumAge)); + smallCar.setMaximumAge(Period.ofYears(maximumAge)); return smallCar; } + + private void checkLegoSets(Iterable legoSets, String manualText, int numberOfModels) { + + assertThat(legoSets) // + .extracting( // + ls -> ls.getManual().getText(), // + ls -> ls.getModels().size()) // + .containsExactly(new Tuple(manualText, numberOfModels)); + } }