diff --git a/pom.xml b/pom.xml index e5a08ca..8d2846f 100644 --- a/pom.xml +++ b/pom.xml @@ -15,12 +15,12 @@ org.springframework.data.build spring-data-parent - 3.5.0-RC1 + 3.5.1 spring-data-jdbc - 3.5.0-RC1 + 3.5.1 4.21.1 reuseReports diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index 6a9ee47..9a8975e 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -45,7 +45,7 @@ org.springframework.data spring-data-jdbc - 3.5.0-RC1 + ${springdata.commons} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/DependencyTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/DependencyTests.java index b754581..d7d142b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/DependencyTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/DependencyTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc; import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.data.auditing.config.AuditingHandlerBeanDefinitionParser; @@ -34,6 +35,7 @@ * * @author Jens Schauder */ +@Disabled("Disabled because of JdbcArrayColumns and Dialect cycle to be resolved in 4.0") public class DependencyTests { @Test diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java index 4f047f8..e27c386 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java @@ -29,9 +29,10 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -60,6 +61,7 @@ import org.springframework.data.relational.core.mapping.MappedCollection; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.Table; +import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; import org.springframework.data.relational.core.query.Criteria; import org.springframework.data.relational.core.query.CriteriaDefinition; import org.springframework.data.relational.core.query.Query; @@ -236,7 +238,25 @@ void findAllByQuery() { Query query = Query.query(criteria); Iterable reloadedById = template.findAll(query, SimpleListParent.class); - assertThat(reloadedById).extracting(e -> e.id, e -> e.content.size()).containsExactly(tuple(two.id, 2)); + assertThat(reloadedById) // + .extracting(e -> e.id, e-> e.name, e -> e.content.size()) // + .containsExactly(tuple(two.id, two.name, 2)); + } + + @Test // GH-1803 + void findAllByQueryWithColumns() { + + template.save(SimpleListParent.of("one", "one_1")); + SimpleListParent two = template.save(SimpleListParent.of("two", "two_1", "two_2")); + template.save(SimpleListParent.of("three", "three_1", "three_2", "three_3")); + + CriteriaDefinition criteria = CriteriaDefinition.from(Criteria.where("id").is(two.id)); + Query query = Query.query(criteria).columns("id"); + Iterable reloadedById = template.findAll(query, SimpleListParent.class); + + assertThat(reloadedById) // + .extracting(e -> e.id, e-> e.name, e -> e.content.size()) // + .containsExactly(tuple(two.id, null, 2)); } @Test // GH-1601 @@ -1373,6 +1393,22 @@ void mapWithEnumKey() { assertThat(enumMapOwners).containsExactly(enumMapOwner); } + @Test // GH-2064 + void saveAllBeforeConvertCallback() { + + BeforeConvertCallbackForSaveBatch first = new BeforeConvertCallbackForSaveBatch("first"); + BeforeConvertCallbackForSaveBatch second = new BeforeConvertCallbackForSaveBatch("second"); + BeforeConvertCallbackForSaveBatch third = new BeforeConvertCallbackForSaveBatch("third"); + + template.saveAll(List.of(first, second, third)); + + List allEntriesInTable = template + .findAll(BeforeConvertCallbackForSaveBatch.class); + + assertThat(allEntriesInTable).hasSize(3).extracting(BeforeConvertCallbackForSaveBatch::getName) + .containsExactlyInAnyOrder("first", "second", "third"); + } + @Test // GH-1684 void oneToOneWithIdenticalIdColumnName() { @@ -2184,6 +2220,31 @@ public Short getVersion() { } } + @Table("BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH") + static class BeforeConvertCallbackForSaveBatch { + + @Id + private String id; + + private String name; + + public BeforeConvertCallbackForSaveBatch(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + } + @Table("VERSIONED_AGGREGATE") static class AggregateWithPrimitiveShortVersion extends VersionedAggregate { @@ -2214,12 +2275,14 @@ static class WithIdOnly { @Table static class WithInsertOnly { + @Id Long id; @InsertOnlyProperty String insertOnly; } @Table static class MultipleCollections { + @Id Long id; String name; List listElements = new ArrayList<>(); @@ -2271,9 +2334,17 @@ TestClass testClass() { } @Bean - JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context, + BeforeConvertCallback callback() { + return aggregate -> { + aggregate.setId(UUID.randomUUID().toString()); + return aggregate; + }; + } + + @Bean + JdbcAggregateOperations operations(ApplicationContext applicationContext, RelationalMappingContext context, DataAccessStrategy dataAccessStrategy, JdbcConverter converter) { - return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy); + return new JdbcAggregateTemplate(applicationContext, context, converter, dataAccessStrategy); } } @@ -2285,5 +2356,10 @@ static class JdbcAggregateTemplateIntegrationTests extends AbstractJdbcAggregate static class JdbcAggregateTemplateSingleQueryLoadingIntegrationTests extends AbstractJdbcAggregateTemplateIntegrationTests { + @Disabled + @Override + void findAllByQueryWithColumns() { + super.findAllByQueryWithColumns(); + } } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java new file mode 100644 index 0000000..e9a4a7e --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/ImmutableAggregateTemplateHsqlIntegrationTests.java @@ -0,0 +1,612 @@ +/* + * Copyright 2017-2025 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 org.springframework.data.jdbc.core; + +import static java.util.Collections.*; +import static org.assertj.core.api.Assertions.*; + +import java.util.Objects; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.convert.DataAccessStrategy; +import org.springframework.data.jdbc.core.convert.JdbcConverter; +import org.springframework.data.jdbc.testing.DatabaseType; +import org.springframework.data.jdbc.testing.EnabledOnDatabase; +import org.springframework.data.jdbc.testing.IntegrationTest; +import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; + +/** + * Integration tests for {@link JdbcAggregateTemplate} and it's handling of immutable entities. + * + * @author Jens Schauder + * @author Salim Achouche + * @author Chirag Tailor + */ +@IntegrationTest +@EnabledOnDatabase(DatabaseType.HSQL) +public class ImmutableAggregateTemplateHsqlIntegrationTests { + + @Autowired JdbcAggregateOperations template; + + @Test // DATAJDBC-241 + public void saveWithGeneratedIdCreatesNewInstance() { + + LegoSet legoSet = createLegoSet(createManual()); + + LegoSet saved = template.save(legoSet); + + SoftAssertions softly = new SoftAssertions(); + + softly.assertThat(legoSet).isNotSameAs(saved); + softly.assertThat(legoSet.getId()).isNull(); + + softly.assertThat(saved.getId()).isNotNull(); + softly.assertThat(saved.name).isNotNull(); + softly.assertThat(saved.manual).isNotNull(); + softly.assertThat(saved.manual.content).isNotNull(); + + softly.assertAll(); + } + + @Test // DATAJDBC-241 + public void saveAndLoadAnEntityWithReferencedEntityById() { + + LegoSet saved = template.save(createLegoSet(createManual())); + + assertThat(saved.manual.id).describedAs("id of stored manual").isNotNull(); + + LegoSet reloadedLegoSet = template.findById(saved.getId(), LegoSet.class); + + assertThat(reloadedLegoSet.manual).isNotNull(); + + SoftAssertions softly = new SoftAssertions(); + + softly.assertThat(reloadedLegoSet.manual.getId()) // + .isEqualTo(saved.getManual().getId()) // + .isNotNull(); + softly.assertThat(reloadedLegoSet.manual.getContent()).isEqualTo(saved.getManual().getContent()); + + softly.assertAll(); + } + + @Test // DATAJDBC-291 + public void saveAndLoadAnEntityWithTwoReferencedEntitiesById() { + + LegoSet saved = template.save(createLegoSet(createManual(), new Author(null, "Alfred E. Neumann"))); + + assertThat(saved.manual.id).describedAs("id of stored manual").isNotNull(); + assertThat(saved.author.id).describedAs("id of stored author").isNotNull(); + + LegoSet reloadedLegoSet = template.findById(saved.getId(), LegoSet.class); + + assertThat(reloadedLegoSet.manual).isNotNull(); + + SoftAssertions softly = new SoftAssertions(); + + softly.assertThat(reloadedLegoSet.manual.getId()) // + .isEqualTo(saved.getManual().getId()) // + .isNotNull(); + softly.assertThat(reloadedLegoSet.manual.getContent()).isEqualTo(saved.getManual().getContent()); + softly.assertThat(reloadedLegoSet.author.getName()).isEqualTo(saved.getAuthor().getName()); + + softly.assertAll(); + } + + @Test // DATAJDBC-241 + public void saveAndLoadManyEntitiesWithReferencedEntity() { + + LegoSet legoSet = createLegoSet(createManual()); + + LegoSet savedLegoSet = template.save(legoSet); + + Iterable reloadedLegoSets = template.findAll(LegoSet.class); + + assertThat(reloadedLegoSets).hasSize(1).extracting("id", "manual.id", "manual.content") + .contains(tuple(savedLegoSet.getId(), savedLegoSet.getManual().getId(), savedLegoSet.getManual().getContent())); + } + + @Test // DATAJDBC-241 + public void saveAndLoadManyEntitiesByIdWithReferencedEntity() { + + LegoSet saved = template.save(createLegoSet(createManual())); + + Iterable reloadedLegoSets = template.findAllById(singletonList(saved.getId()), LegoSet.class); + + assertThat(reloadedLegoSets).hasSize(1).extracting("id", "manual.id", "manual.content") + .contains(tuple(saved.getId(), saved.getManual().getId(), saved.getManual().getContent())); + } + + @Test // DATAJDBC-241 + public void saveAndLoadAnEntityWithReferencedNullEntity() { + + LegoSet saved = template.save(createLegoSet(null)); + + LegoSet reloadedLegoSet = template.findById(saved.getId(), LegoSet.class); + + assertThat(reloadedLegoSet.manual).isNull(); + } + + @Test // DATAJDBC-241 + public void saveAndDeleteAnEntityWithReferencedEntity() { + + LegoSet legoSet = createLegoSet(createManual()); + + LegoSet saved = template.save(legoSet); + + template.delete(saved); + + SoftAssertions softly = new SoftAssertions(); + + softly.assertThat(template.findAll(LegoSet.class)).isEmpty(); + softly.assertThat(template.findAll(Manual.class)).isEmpty(); + + softly.assertAll(); + } + + @Test // DATAJDBC-241 + public void saveAndDeleteAllWithReferencedEntity() { + + template.save(createLegoSet(createManual())); + + template.deleteAll(LegoSet.class); + + SoftAssertions softly = new SoftAssertions(); + + assertThat(template.findAll(LegoSet.class)).isEmpty(); + assertThat(template.findAll(Manual.class)).isEmpty(); + + softly.assertAll(); + } + + @Test // DATAJDBC-241 + public void updateReferencedEntityFromNull() { + + LegoSet saved = template.save(createLegoSet(null)); + + LegoSet changedLegoSet = new LegoSet(saved.id, saved.name, new Manual(23L, "Some content"), null); + + template.save(changedLegoSet); + + LegoSet reloadedLegoSet = template.findById(saved.getId(), LegoSet.class); + + assertThat(reloadedLegoSet.manual.content).isEqualTo("Some content"); + } + + @Test // DATAJDBC-241 + public void updateReferencedEntityToNull() { + + LegoSet saved = template.save(createLegoSet(null)); + + LegoSet changedLegoSet = new LegoSet(saved.id, saved.name, null, null); + + template.save(changedLegoSet); + + LegoSet reloadedLegoSet = template.findById(saved.getId(), LegoSet.class); + + SoftAssertions softly = new SoftAssertions(); + + softly.assertThat(reloadedLegoSet.manual).isNull(); + softly.assertThat(template.findAll(Manual.class)).describedAs("Manuals failed to delete").isEmpty(); + + softly.assertAll(); + } + + @Test // DATAJDBC-241 + public void replaceReferencedEntity() { + + LegoSet saved = template.save(createLegoSet(null)); + + LegoSet changedLegoSet = new LegoSet(saved.id, saved.name, new Manual(null, "other content"), null); + + template.save(changedLegoSet); + + LegoSet reloadedLegoSet = template.findById(saved.getId(), LegoSet.class); + + SoftAssertions softly = new SoftAssertions(); + + softly.assertThat(reloadedLegoSet.manual.content).isEqualTo("other content"); + softly.assertThat(template.findAll(Manual.class)).describedAs("There should be only one manual").hasSize(1); + + softly.assertAll(); + } + + @Test // GH-1201 + void replaceReferencedEntity_saveResult() { + + Root root = new Root(null, "originalRoot", new NonRoot(null, "originalNonRoot")); + Root originalSavedRoot = template.save(root); + + assertThat(originalSavedRoot.id).isNotNull(); + assertThat(originalSavedRoot.name).isEqualTo("originalRoot"); + assertThat(originalSavedRoot.reference.id).isNotNull(); + assertThat(originalSavedRoot.reference.name).isEqualTo("originalNonRoot"); + + Root updatedRoot = new Root(originalSavedRoot.id, "updatedRoot", new NonRoot(null, "updatedNonRoot")); + Root updatedSavedRoot = template.save(updatedRoot); + + assertThat(updatedSavedRoot.id).isNotNull(); + assertThat(updatedSavedRoot.name).isEqualTo("updatedRoot"); + assertThat(updatedSavedRoot.reference.id).isNotNull().isNotEqualTo(originalSavedRoot.reference.id); + assertThat(updatedSavedRoot.reference.name).isEqualTo("updatedNonRoot"); + } + + @Test // DATAJDBC-241 + public void changeReferencedEntity() { + + LegoSet saved = template.save(createLegoSet(createManual())); + + LegoSet changedLegoSet = saved.withManual(saved.manual.withContent("new content")); + + template.save(changedLegoSet); + + LegoSet reloadedLegoSet = template.findById(saved.getId(), LegoSet.class); + + Manual manual = reloadedLegoSet.manual; + assertThat(manual).isNotNull(); + assertThat(manual.content).isEqualTo("new content"); + } + + @Test // DATAJDBC-545 + public void setIdViaConstructor() { + + WithCopyConstructor entity = new WithCopyConstructor(null, "Alfred"); + + WithCopyConstructor saved = template.save(entity); + + assertThat(saved).isNotEqualTo(entity); + assertThat(saved.id).isNotNull(); + } + + private static LegoSet createLegoSet(Manual manual) { + + return new LegoSet(null, "Star Destroyer", manual, null); + } + + private static LegoSet createLegoSet(Manual manual, Author author) { + + return new LegoSet(null, "Star Destroyer", manual, author); + } + + private static Manual createManual() { + return new Manual(null, + "Accelerates to 99% of light speed. Destroys almost everything. See https://what-if.xkcd.com/1/"); + } + + static final class LegoSet { + + @Id private final Long id; + private final String name; + private final Manual manual; + private final Author author; + + public LegoSet(Long id, String name, Manual manual, Author author) { + this.id = id; + this.name = name; + this.manual = manual; + this.author = author; + } + + public Long getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public Manual getManual() { + return this.manual; + } + + public Author getAuthor() { + return this.author; + } + + public boolean equals(final Object o) { + if (o == this) + return true; + if (!(o instanceof final LegoSet other)) + return false; + final Object this$id = this.getId(); + final Object other$id = other.getId(); + if (!Objects.equals(this$id, other$id)) + return false; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + if (!Objects.equals(this$name, other$name)) + return false; + final Object this$manual = this.getManual(); + final Object other$manual = other.getManual(); + if (!Objects.equals(this$manual, other$manual)) + return false; + final Object this$author = this.getAuthor(); + final Object other$author = other.getAuthor(); + return Objects.equals(this$author, other$author); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.getId(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + final Object $manual = this.getManual(); + result = result * PRIME + ($manual == null ? 43 : $manual.hashCode()); + final Object $author = this.getAuthor(); + result = result * PRIME + ($author == null ? 43 : $author.hashCode()); + return result; + } + + public String toString() { + return "ImmutableAggregateTemplateHsqlIntegrationTests.LegoSet(id=" + this.getId() + ", name=" + this.getName() + + ", manual=" + this.getManual() + ", author=" + this.getAuthor() + ")"; + } + + public LegoSet withId(Long id) { + return this.id == id ? this : new LegoSet(id, this.name, this.manual, this.author); + } + + public LegoSet withName(String name) { + return this.name == name ? this : new LegoSet(this.id, name, this.manual, this.author); + } + + public LegoSet withManual(Manual manual) { + return this.manual == manual ? this : new LegoSet(this.id, this.name, manual, this.author); + } + + public LegoSet withAuthor(Author author) { + return this.author == author ? this : new LegoSet(this.id, this.name, this.manual, author); + } + } + + static final class Manual { + + @Id private final Long id; + private final String content; + + public Manual(Long id, String content) { + this.id = id; + this.content = content; + } + + public Long getId() { + return this.id; + } + + public String getContent() { + return this.content; + } + + public boolean equals(final Object o) { + if (o == this) + return true; + if (!(o instanceof final Manual other)) + return false; + final Object this$id = this.getId(); + final Object other$id = other.getId(); + if (!Objects.equals(this$id, other$id)) + return false; + final Object this$content = this.getContent(); + final Object other$content = other.getContent(); + return Objects.equals(this$content, other$content); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.getId(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $content = this.getContent(); + result = result * PRIME + ($content == null ? 43 : $content.hashCode()); + return result; + } + + public String toString() { + return "ImmutableAggregateTemplateHsqlIntegrationTests.Manual(id=" + this.getId() + ", content=" + + this.getContent() + ")"; + } + + public Manual withId(Long id) { + return this.id == id ? this : new Manual(id, this.content); + } + + public Manual withContent(String content) { + return this.content == content ? this : new Manual(this.id, content); + } + } + + static final class Author { + + @Id private final Long id; + private final String name; + + public Author(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public boolean equals(final Object o) { + if (o == this) + return true; + if (!(o instanceof final Author other)) + return false; + final Object this$id = this.getId(); + final Object other$id = other.getId(); + if (!Objects.equals(this$id, other$id)) + return false; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + return Objects.equals(this$name, other$name); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.getId(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + return result; + } + + public String toString() { + return "ImmutableAggregateTemplateHsqlIntegrationTests.Author(id=" + this.getId() + ", name=" + this.getName() + + ")"; + } + + public Author withId(Long id) { + return this.id == id ? this : new Author(id, this.name); + } + + public Author withName(String name) { + return this.name == name ? this : new Author(this.id, name); + } + } + + static class Root { + @Id private Long id; + private String name; + private NonRoot reference; + + public Root(Long id, String name, NonRoot reference) { + this.id = id; + this.name = name; + this.reference = reference; + } + + public Long getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public NonRoot getReference() { + return this.reference; + } + + public void setId(Long id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setReference(NonRoot reference) { + this.reference = reference; + } + } + + static final class NonRoot { + @Id private final Long id; + private final String name; + + public NonRoot(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public boolean equals(final Object o) { + if (o == this) + return true; + if (!(o instanceof final NonRoot other)) + return false; + final Object this$id = this.getId(); + final Object other$id = other.getId(); + if (!Objects.equals(this$id, other$id)) + return false; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + return Objects.equals(this$name, other$name); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.getId(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + return result; + } + + public String toString() { + return "ImmutableAggregateTemplateHsqlIntegrationTests.NonRoot(id=" + this.getId() + ", name=" + this.getName() + + ")"; + } + + public NonRoot withId(Long id) { + return this.id == id ? this : new NonRoot(id, this.name); + } + + public NonRoot withName(String name) { + return this.name == name ? this : new NonRoot(this.id, name); + } + } + + static class WithCopyConstructor { + @Id private final Long id; + private final String name; + + WithCopyConstructor(Long id, String name) { + this.id = id; + this.name = name; + } + } + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Bean + Class testClass() { + return ImmutableAggregateTemplateHsqlIntegrationTests.class; + } + + @Bean + JdbcAggregateOperations operations(ApplicationEventPublisher publisher, RelationalMappingContext context, + DataAccessStrategy dataAccessStrategy, JdbcConverter converter) { + return new JdbcAggregateTemplate(publisher, context, converter, dataAccessStrategy); + } + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java index 9f3bd72..0f06834 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategyUnitTests.java @@ -22,10 +22,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.dialect.JdbcHsqlDbDialect; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.relational.core.conversion.IdValueSource; import org.springframework.data.relational.core.dialect.Dialect; -import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.jdbc.core.JdbcOperations; @@ -58,7 +58,7 @@ class DefaultDataAccessStrategyUnitTests { void before() { DelegatingDataAccessStrategy relationResolver = new DelegatingDataAccessStrategy(); - Dialect dialect = HsqlDbDialect.INSTANCE; + Dialect dialect = JdbcHsqlDbDialect.INSTANCE; converter = new MappingJdbcConverter(context, relationResolver, new JdbcCustomConversions(), new DefaultJdbcTypeFactory(jdbcOperations)); accessStrategy = new DataAccessStrategyFactory( // diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/IdGeneratingEntityCallbackTest.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/IdGeneratingEntityCallbackTest.java index 240d47f..0de18dd 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/IdGeneratingEntityCallbackTest.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/IdGeneratingEntityCallbackTest.java @@ -27,12 +27,11 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; - import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.core.dialect.JdbcMySqlDialect; +import org.springframework.data.jdbc.core.dialect.JdbcPostgresDialect; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.relational.core.conversion.MutableAggregateChange; -import org.springframework.data.relational.core.dialect.MySqlDialect; -import org.springframework.data.relational.core.dialect.PostgresDialect; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.Sequence; import org.springframework.data.relational.core.mapping.Table; @@ -56,7 +55,8 @@ class IdGeneratingEntityCallbackTest { void setUp() { relationalMappingContext = new RelationalMappingContext(); - relationalMappingContext.setSimpleTypeHolder(new SimpleTypeHolder(PostgresDialect.INSTANCE.simpleTypes(), true)); + relationalMappingContext + .setSimpleTypeHolder(new SimpleTypeHolder(JdbcPostgresDialect.INSTANCE.simpleTypes(), true)); } @Test // GH-1923 @@ -65,7 +65,7 @@ void sequenceGenerationIsNotSupported() { NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class); IdGeneratingEntityCallback subject = new IdGeneratingEntityCallback(relationalMappingContext, - MySqlDialect.INSTANCE, operations); + JdbcMySqlDialect.INSTANCE, operations); EntityWithSequence processed = (EntityWithSequence) subject.onBeforeSave(new EntityWithSequence(), MutableAggregateChange.forSave(new EntityWithSequence())); @@ -77,7 +77,7 @@ void sequenceGenerationIsNotSupported() { void entityIsNotMarkedWithTargetSequence() { IdGeneratingEntityCallback subject = new IdGeneratingEntityCallback(relationalMappingContext, - MySqlDialect.INSTANCE, operations); + JdbcMySqlDialect.INSTANCE, operations); NoSequenceEntity processed = (NoSequenceEntity) subject.onBeforeSave(new NoSequenceEntity(), MutableAggregateChange.forSave(new NoSequenceEntity())); @@ -93,7 +93,7 @@ void entityIdIsPopulatedFromSequence() { .thenReturn(generatedId); IdGeneratingEntityCallback subject = new IdGeneratingEntityCallback(relationalMappingContext, - PostgresDialect.INSTANCE, operations); + JdbcPostgresDialect.INSTANCE, operations); EntityWithSequence processed = (EntityWithSequence) subject.onBeforeSave(new EntityWithSequence(), MutableAggregateChange.forSave(new EntityWithSequence())); @@ -109,7 +109,7 @@ void appliesIntegerConversion() { .thenReturn(generatedId); IdGeneratingEntityCallback subject = new IdGeneratingEntityCallback(relationalMappingContext, - PostgresDialect.INSTANCE, operations); + JdbcPostgresDialect.INSTANCE, operations); EntityWithIntSequence processed = (EntityWithIntSequence) subject.onBeforeSave(new EntityWithIntSequence(), MutableAggregateChange.forSave(new EntityWithIntSequence())); @@ -125,7 +125,7 @@ void assignsUuidValues() { .thenReturn(generatedId); IdGeneratingEntityCallback subject = new IdGeneratingEntityCallback(relationalMappingContext, - PostgresDialect.INSTANCE, operations); + JdbcPostgresDialect.INSTANCE, operations); EntityWithUuidSequence processed = (EntityWithUuidSequence) subject.onBeforeSave(new EntityWithUuidSequence(), MutableAggregateChange.forSave(new EntityWithUuidSequence())); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/NonQuotingDialect.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/NonQuotingDialect.java index edf150a..fda4f5d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/NonQuotingDialect.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/NonQuotingDialect.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jdbc.core.convert; +import org.springframework.data.jdbc.core.dialect.JdbcHsqlDbDialect; import org.springframework.data.relational.core.dialect.AbstractDialect; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.HsqlDbDialect; @@ -38,12 +39,12 @@ private NonQuotingDialect() {} @Override public LimitClause limit() { - return HsqlDbDialect.INSTANCE.limit(); + return JdbcHsqlDbDialect.INSTANCE.limit(); } @Override public LockClause lock() { - return HsqlDbDialect.INSTANCE.lock(); + return JdbcHsqlDbDialect.INSTANCE.lock(); } @Override diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java index d7ad163..a67da73 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/QueryMapperUnitTests.java @@ -46,6 +46,7 @@ * * @author Mark Paluch * @author Jens Schauder + * @author Mikhail Fedorov */ public class QueryMapperUnitTests { @@ -121,7 +122,7 @@ public void shouldMapNestedGroup() { Condition condition = map(criteria); assertThat(condition).hasToString( - "(person.\"NAME\" = ?[:name]) AND (person.\"NAME\" = ?[:name1] OR person.age < ?[:age] OR (person.\"NAME\" != ?[:name2] AND person.age > ?[:age1]))"); + "(person.\"NAME\" = ?[:name]) AND (person.\"NAME\" = ?[:name1] OR person.age < ?[:age] OR (person.\"NAME\" != ?[:name3] AND person.age > ?[:age4]))"); } @Test // DATAJDBC-318 diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java index d095b27..62e9524 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java @@ -15,17 +15,24 @@ */ package org.springframework.data.jdbc.core.convert; -import static java.util.Collections.*; -import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.SoftAssertions.*; -import static org.springframework.data.relational.core.mapping.ForeignKeyNaming.*; -import static org.springframework.data.relational.core.sql.SqlIdentifier.*; +import static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.entry; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.data.relational.core.mapping.ForeignKeyNaming.APPLY_RENAMING; +import static org.springframework.data.relational.core.mapping.ForeignKeyNaming.IGNORE_RENAMING; +import static org.springframework.data.relational.core.sql.SqlIdentifier.EMPTY; +import static org.springframework.data.relational.core.sql.SqlIdentifier.quoted; +import static org.springframework.data.relational.core.sql.SqlIdentifier.unquoted; import java.util.Map; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + import org.springframework.data.annotation.Id; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.annotation.Version; @@ -33,13 +40,13 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils; +import org.springframework.data.jdbc.core.dialect.JdbcPostgresDialect; +import org.springframework.data.jdbc.core.dialect.JdbcSqlServerDialect; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.dialect.AnsiDialect; import org.springframework.data.relational.core.dialect.Dialect; -import org.springframework.data.relational.core.dialect.PostgresDialect; -import org.springframework.data.relational.core.dialect.SqlServerDialect; import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.DefaultNamingStrategy; @@ -274,7 +281,7 @@ void findAllSortedByMultipleFields() { @Test // GH-821 void findAllSortedWithNullHandling_resolvesNullHandlingWhenDialectSupportsIt() { - SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class, PostgresDialect.INSTANCE); + SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class, JdbcPostgresDialect.INSTANCE); String sql = sqlGenerator .getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST))); @@ -285,7 +292,7 @@ void findAllSortedWithNullHandling_resolvesNullHandlingWhenDialectSupportsIt() { @Test // GH-821 void findAllSortedWithNullHandling_ignoresNullHandlingWhenDialectDoesNotSupportIt() { - SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class, SqlServerDialect.INSTANCE); + SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class, JdbcSqlServerDialect.INSTANCE); String sql = sqlGenerator .getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST))); @@ -345,12 +352,13 @@ void findAllPagedAndSorted() { @Test // GH-1919 void selectByQuery() { - Query query = Query.query(Criteria.where("id").is(23L)); + Query query = Query.query(Criteria.where("id").is(23L)).columns(new String[0]); String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource()); assertThat(sql).contains( // "SELECT", // + "dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name", // "FROM dummy_entity", // "LEFT OUTER JOIN referenced_entity ref ON ref.dummy_entity = dummy_entity.id1", // "LEFT OUTER JOIN second_level_referenced_entity ref_further ON ref_further.referenced_entity = ref.x_l1id", // @@ -358,6 +366,45 @@ void selectByQuery() { ); } + @Test // GH-1803 + void selectByQueryWithColumnLimit() { + + Query query = Query.empty().columns("id", "alpha", "beta", "gamma"); + + String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource()); + + assertThat(sql).contains( // + "SELECT dummy_entity.id1 AS id1, dummy_entity.alpha, dummy_entity.beta, dummy_entity.gamma", // + "FROM dummy_entity" // + ); + } + + @Test // GH-1803 + void selectingSetContentSelectsAllColumns() { + + Query query = Query.empty().columns("elements.content"); + + String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource()); + + assertThat(sql).contains( // + "SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name"// + ); + } + + @Test // GH-1803 + void selectByQueryWithMappedColumnPathsRendersCorrectSelection() { + + Query query = Query.empty().columns("ref.content"); + + String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource()); + + assertThat(sql).contains( // + "SELECT", // + "ref.x_content AS ref_x_content", // + "FROM dummy_entity", // + "LEFT OUTER JOIN referenced_entity ref ON ref.dummy_entity = dummy_entity.id1"); + } + @Test // GH-1919 void selectBySortedQuery() { @@ -375,7 +422,8 @@ void selectBySortedQuery() { "ORDER BY dummy_entity.id1 ASC" // ); assertThat(sql).containsOnlyOnce("LEFT OUTER JOIN referenced_entity ref ON ref.dummy_entity = dummy_entity.id1"); - assertThat(sql).containsOnlyOnce("LEFT OUTER JOIN second_level_referenced_entity ref_further ON ref_further.referenced_entity = ref.x_l1id"); + assertThat(sql).containsOnlyOnce( + "LEFT OUTER JOIN second_level_referenced_entity ref_further ON ref_further.referenced_entity = ref.x_l1id"); } @Test // DATAJDBC-131, DATAJDBC-111 @@ -512,7 +560,7 @@ void updateWithVersion() { @Test // DATAJDBC-264 void getInsertForEmptyColumnListPostgres() { - SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class, PostgresDialect.INSTANCE); + SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class, JdbcPostgresDialect.INSTANCE); String insert = sqlGenerator.getInsert(emptySet()); @@ -522,7 +570,7 @@ void getInsertForEmptyColumnListPostgres() { @Test // GH-777 void gerInsertForEmptyColumnListMsSqlServer() { - SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class, SqlServerDialect.INSTANCE); + SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class, JdbcSqlServerDialect.INSTANCE); String insert = sqlGenerator.getInsert(emptySet()); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index ea3e548..0767c2e 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -37,6 +37,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.data.convert.CustomConversions; import org.springframework.data.jdbc.core.convert.*; +import org.springframework.data.jdbc.core.dialect.JdbcArrayColumns; import org.springframework.data.jdbc.core.dialect.JdbcDialect; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes; @@ -169,7 +170,8 @@ JdbcConverter relationalConverter(RelationalMappingContext mappingContext, @Lazy CustomConversions conversions, @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, Dialect dialect) { - JdbcArrayColumns arrayColumns = dialect instanceof JdbcDialect ? + org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns = dialect instanceof JdbcDialect + ? ((JdbcDialect) dialect).getArraySupport() : JdbcArrayColumns.DefaultSupport.INSTANCE; diff --git a/spring-data-jdbc/src/test/resources/META-INF/spring.factories b/spring-data-jdbc/src/test/resources/META-INF/spring.factories new file mode 100644 index 0000000..9631bc2 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.test.context.TestExecutionListener=org.springframework.data.jdbc.testing.LicenseListener,org.springframework.data.jdbc.testing.DatabaseTypeCondition diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-gaussdb.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-gaussdb.sql index d4760f6..e00060d 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-gaussdb.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-gaussdb.sql @@ -53,6 +53,8 @@ DROP TABLE THIRD; DROP TABLE SEC; DROP TABLE FIRST; +DROP TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH"; + CREATE TABLE LEGO_SET ( "id1" SERIAL PRIMARY KEY, @@ -473,4 +475,10 @@ CREATE TABLE THIRD SEC BIGINT NOT NULL, NAME VARCHAR(20) NOT NULL, FOREIGN KEY (SEC) REFERENCES SEC (ID) -); \ No newline at end of file +); + +CREATE TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH" +( + ID VARCHAR PRIMARY KEY, + NAME VARCHAR +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql index 36f2089..5c4a8c0 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql @@ -52,6 +52,8 @@ DROP TABLE THIRD; DROP TABLE SEC; DROP TABLE FIRST; +DROP TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH"; + CREATE TABLE LEGO_SET ( "id1" SERIAL PRIMARY KEY, @@ -470,4 +472,10 @@ CREATE TABLE THIRD SEC BIGINT NOT NULL, NAME VARCHAR(20) NOT NULL, FOREIGN KEY (SEC) REFERENCES SEC (ID) -); \ No newline at end of file +); + +CREATE TABLE "BEFORE_CONVERT_CALLBACK_FOR_SAVE_BATCH" +( + ID VARCHAR PRIMARY KEY, + NAME VARCHAR +); diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index 80eb35f..95f7623 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -63,7 +63,7 @@ org.springframework.data spring-data-r2dbc - 3.5.0-RC1 + ${springdata.commons} diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 1c63ef2..5b35b51 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -28,7 +28,7 @@ org.springframework.data spring-data-relational - 3.5.0-RC1 + ${springdata.commons}