From 4fd31c6174c998fd68b9860e57950034e2c5def1 Mon Sep 17 00:00:00 2001 From: Greg Turnquist Date: Mon, 7 Aug 2017 15:18:34 -0500 Subject: [PATCH 1/2] DATAJDBC-107 - Prepare branch --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4ffa5540e7..fbd666d089 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jdbc - 1.0.0.BUILD-SNAPSHOT + 1.0.0.DATAJDBC-107-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. From 469fb10800470a7056b4cf81a5288e4d681e1749 Mon Sep 17 00:00:00 2001 From: Greg Turnquist Date: Mon, 7 Aug 2017 17:10:18 -0500 Subject: [PATCH 2/2] DATAJDBC-107 - Implement naming strategy Created NamingStrategy and a default implementation that replicates the current solution. Several unit tests illustrates how to override the default and plugin a custom solution including a ThreadLocal, contextual one that could be user-based if, for example, Spring Security's SecurityContextHolder was used. --- .../model/BasicJdbcPersistentProperty.java | 2 +- .../mapping/model/DefaultNamingStrategy.java | 53 +++++ .../mapping/model/JdbcMappingContext.java | 10 +- .../model/JdbcPersistentEntityImpl.java | 8 +- .../jdbc/mapping/model/NamingStrategy.java | 33 +++ .../support/JdbcRepositoryFactory.java | 13 +- .../support/JdbcRepositoryFactoryBean.java | 17 +- .../JdbcEntityTemplateIntegrationTests.java | 3 +- ...orContextBasedNamingStrategyUnitTests.java | 214 ++++++++++++++++++ ...GeneratorFixedNamingStrategyUnitTests.java | 201 ++++++++++++++++ .../data/jdbc/core/SqlGeneratorUnitTests.java | 30 ++- .../JdbcEntityDeleteWriterUnitTests.java | 3 +- .../conversion/JdbcEntityWriterUnitTests.java | 3 +- .../BasicJdbcPersistentPropertyUnitTests.java | 6 +- ...epositoryIdGenerationIntegrationTests.java | 22 +- .../SimpleJdbcRepositoryEventsUnitTests.java | 3 +- .../data/jdbc/testing/TestConfiguration.java | 3 +- 17 files changed, 595 insertions(+), 29 deletions(-) create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/model/DefaultNamingStrategy.java create mode 100644 src/main/java/org/springframework/data/jdbc/mapping/model/NamingStrategy.java create mode 100644 src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java create mode 100644 src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java index 015103c461..63868dd27f 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java @@ -74,7 +74,7 @@ protected Association createAssociation() { * @see org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty#getColumnName() */ public String getColumnName() { - return getName(); + return this.context.getNamingStrategy().getColumnName(this); } /** diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/DefaultNamingStrategy.java b/src/main/java/org/springframework/data/jdbc/mapping/model/DefaultNamingStrategy.java new file mode 100644 index 0000000000..5bad582468 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/DefaultNamingStrategy.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.mapping.model; + +/** + * Basic implementation of {@link NamingStrategy} with no schema, table based on {@link Class} and + * column name based on {@link JdbcPersistentProperty}. + * + * NOTE: Can also be used as an adapter. Create an anonymous subclass and override any settings to implement + * a different strategy on the fly. + * + * @author Greg Turnquist + */ +public class DefaultNamingStrategy implements NamingStrategy { + + /** + * No schema at all! + */ + @Override + public String getSchema() { + return ""; + } + + /** + * Look up the {@link Class}'s simple name. + */ + @Override + public String getTableName(Class type) { + return type.getSimpleName(); + } + + + /** + * Look up the {@link JdbcPersistentProperty}'s name. + */ + @Override + public String getColumnName(JdbcPersistentProperty property) { + return property.getName(); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java index d2c074307b..59cc8b0f8e 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcMappingContext.java @@ -17,6 +17,8 @@ import static java.util.Arrays.*; +import lombok.Getter; + import java.math.BigDecimal; import java.math.BigInteger; import java.time.temporal.Temporal; @@ -46,7 +48,11 @@ public class JdbcMappingContext extends AbstractMappingContext referencedEntities(Class rootType, PropertyPath pat */ @Override protected JdbcPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - return new JdbcPersistentEntityImpl<>(typeInformation); + return new JdbcPersistentEntityImpl<>(typeInformation, this.namingStrategy); } /* diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java index 53342792f2..66a6b62206 100644 --- a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java +++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java @@ -29,6 +29,7 @@ class JdbcPersistentEntityImpl extends BasicPersistentEntity implements JdbcPersistentEntity { + private final NamingStrategy namingStrategy; private final @Getter String tableName; /** @@ -36,11 +37,12 @@ class JdbcPersistentEntityImpl extends BasicPersistentEntity information) { + JdbcPersistentEntityImpl(TypeInformation information, NamingStrategy namingStrategy) { super(information); - tableName = getType().getSimpleName(); + this.namingStrategy = namingStrategy; + this.tableName = this.namingStrategy.getQualifiedTableName(getType()); } /* @@ -49,7 +51,7 @@ class JdbcPersistentEntityImpl extends BasicPersistentEntity type); + + String getColumnName(JdbcPersistentProperty property); + + default String getQualifiedTableName(Class type) { + return this.getSchema() + (this.getSchema().equals("") ? "" : ".") + this.getTableName(type); + } + +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index b2b7003fe0..e4f19ae7ae 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -15,14 +15,13 @@ */ package org.springframework.data.jdbc.repository.support; -import lombok.RequiredArgsConstructor; - import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.core.JdbcEntityTemplate; import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation; +import org.springframework.data.jdbc.mapping.model.NamingStrategy; import org.springframework.data.jdbc.repository.SimpleJdbcRepository; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.RepositoryInformation; @@ -34,13 +33,19 @@ * @author Jens Schauder * @since 2.0 */ -@RequiredArgsConstructor public class JdbcRepositoryFactory extends RepositoryFactorySupport { - private final JdbcMappingContext context = new JdbcMappingContext(); + private final JdbcMappingContext context; private final NamedParameterJdbcOperations jdbcOperations; private final ApplicationEventPublisher publisher; + public JdbcRepositoryFactory(NamedParameterJdbcOperations namedParameterJdbcOperations, ApplicationEventPublisher publisher, NamingStrategy namingStrategy) { + + this.jdbcOperations = namedParameterJdbcOperations; + this.publisher = publisher; + this.context = new JdbcMappingContext(namingStrategy); + } + @SuppressWarnings("unchecked") @Override public EntityInformation getEntityInformation(Class aClass) { diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java index 0e8dc4e6eb..54bed46a6a 100644 --- a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -23,6 +23,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; +import org.springframework.data.jdbc.mapping.model.NamingStrategy; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; @@ -45,9 +47,12 @@ public class JdbcRepositoryFactoryBean, S, ID extend "No unique NamedParameterJdbcOperation could be found, " // + "nor JdbcOperations or DataSource to construct one from."; + private static final String NO_NAMING_STRATEGY_ERROR_MESSAGE = "No unique NamingStrategy could be found."; + private static final String NAMED_PARAMETER_JDBC_OPERATIONS_BEAN_NAME = "namedParameterJdbcTemplate"; private static final String JDBC_OPERATIONS_BEAN_NAME = "jdbcTemplate"; private static final String DATA_SOURCE_BEAN_NAME = "dataSource"; + private static final String NAMING_STRATEGY_BEAN_NAME = "namingStrategy"; private final ApplicationEventPublisher applicationEventPublisher; private final ApplicationContext context; @@ -62,7 +67,7 @@ public class JdbcRepositoryFactoryBean, S, ID extend @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { - return new JdbcRepositoryFactory(findOrCreateJdbcOperations(), applicationEventPublisher); + return new JdbcRepositoryFactory(findOrCreateJdbcOperations(), applicationEventPublisher, findOrCreateNamingStrategy()); } private NamedParameterJdbcOperations findOrCreateJdbcOperations() { @@ -75,6 +80,12 @@ private NamedParameterJdbcOperations findOrCreateJdbcOperations() { .orElseThrow(() -> new IllegalStateException(NO_NAMED_PARAMETER_JDBC_OPERATION_ERROR_MESSAGE)); } + private NamingStrategy findOrCreateNamingStrategy() { + + return getNamingStrategy() + .orElse(new DefaultNamingStrategy()); + } + private Optional getNamedParameterJdbcOperations() { return getBean(NamedParameterJdbcOperations.class, NAMED_PARAMETER_JDBC_OPERATIONS_BEAN_NAME); } @@ -87,6 +98,10 @@ private Optional getDataSource() { return getBean(DataSource.class, DATA_SOURCE_BEAN_NAME); } + private Optional getNamingStrategy() { + return getBean(NamingStrategy.class, NAMING_STRATEGY_BEAN_NAME); + } + private Optional getBean(Class type, String name) { Map beansOfType = context.getBeansOfType(type); diff --git a/src/test/java/org/springframework/data/jdbc/core/JdbcEntityTemplateIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/core/JdbcEntityTemplateIntegrationTests.java index 8b0b657fc3..3eb0b81a14 100644 --- a/src/test/java/org/springframework/data/jdbc/core/JdbcEntityTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/JdbcEntityTemplateIntegrationTests.java @@ -30,6 +30,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -253,7 +254,7 @@ Class testClass() { JdbcEntityOperations operations(ApplicationEventPublisher publisher, NamedParameterJdbcOperations namedParameterJdbcOperations) { - return new JdbcEntityTemplate(publisher, namedParameterJdbcOperations, new JdbcMappingContext()); + return new JdbcEntityTemplate(publisher, namedParameterJdbcOperations, new JdbcMappingContext(new DefaultNamingStrategy())); } } } diff --git a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java new file mode 100644 index 0000000000..95a6771db9 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorContextBasedNamingStrategyUnitTests.java @@ -0,0 +1,214 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.core; + +import static org.assertj.core.api.Assertions.*; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; +import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.jdbc.mapping.model.NamingStrategy; +import org.springframework.data.mapping.PropertyPath; + +/** + * Unit tests to verify a contextual {@link NamingStrategy} implementation that customizes using a user-centric {@link ThreadLocal}. + * + * NOTE: Due to the need to verify SQL generation and {@link SqlGenerator}'s package-private status suggests + * this unit test exist in this package, not {@literal org.springframework.data.jdbc.mappings.model}. + * + * @author Greg Turnquist + */ +public class SqlGeneratorContextBasedNamingStrategyUnitTests { + + private final ThreadLocal userHandler = new ThreadLocal<>(); + + /** + * Use a {@link DefaultNamingStrategy}, but override the schema with a {@link ThreadLocal}-based setting. + */ + private final NamingStrategy contextualNamingStrategy = new DefaultNamingStrategy() { + @Override + public String getSchema() { + return userHandler.get(); + } + }; + + @Test // DATAJDBC-107 + public void findOne() { + + testAgainstMultipleUsers(user -> { + + SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); + + String sql = sqlGenerator.getFindOne(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) // + .startsWith("SELECT") // + .contains(user + ".DummyEntity.id AS id,") // + .contains(user + ".DummyEntity.name AS name,") // + .contains("ref.l1id AS ref_l1id") // + .contains("ref.content AS ref_content") // + .contains("FROM " + user + ".DummyEntity"); + softAssertions.assertAll(); + }); + } + + @Test // DATAJDBC-107 + public void cascadingDeleteFirstLevel() { + + testAgainstMultipleUsers(user -> { + + SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); + + String sql = sqlGenerator.createDeleteByPath(PropertyPath.from("ref", DummyEntity.class)); + + assertThat(sql).isEqualTo( + "DELETE FROM " + user + ".ReferencedEntity WHERE " + user + ".DummyEntity = :rootId"); + }); + } + + @Test // DATAJDBC-107 + public void cascadingDeleteAllSecondLevel() { + + testAgainstMultipleUsers(user -> { + + SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); + + String sql = sqlGenerator.createDeleteByPath(PropertyPath.from("ref.further", DummyEntity.class)); + + assertThat(sql).isEqualTo( + "DELETE FROM " + user + ".SecondLevelReferencedEntity " + + "WHERE " + user + ".ReferencedEntity IN " + + "(SELECT l1id FROM " + user + ".ReferencedEntity " + + "WHERE " + user + ".DummyEntity = :rootId)"); + }); + } + + @Test // DATAJDBC-107 + public void deleteAll() { + + testAgainstMultipleUsers(user -> { + + SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); + + String sql = sqlGenerator.createDeleteAllSql(null); + + assertThat(sql).isEqualTo("DELETE FROM " + user + ".DummyEntity"); + }); + } + + @Test // DATAJDBC-107 + public void cascadingDeleteAllFirstLevel() { + + testAgainstMultipleUsers(user -> { + + SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); + + String sql = sqlGenerator.createDeleteAllSql(PropertyPath.from("ref", DummyEntity.class)); + + assertThat(sql).isEqualTo( + "DELETE FROM " + user + ".ReferencedEntity WHERE " + user + ".DummyEntity IS NOT NULL"); + }); + } + + @Test // DATAJDBC-107 + public void cascadingDeleteSecondLevel() { + + testAgainstMultipleUsers(user -> { + + SqlGenerator sqlGenerator = configureSqlGenerator(contextualNamingStrategy); + + String sql = sqlGenerator.createDeleteAllSql(PropertyPath.from("ref.further", DummyEntity.class)); + + assertThat(sql).isEqualTo( + "DELETE FROM " + user + ".SecondLevelReferencedEntity " + + "WHERE " + user + ".ReferencedEntity IN " + + "(SELECT l1id FROM " + user + ".ReferencedEntity " + + "WHERE " + user + ".DummyEntity IS NOT NULL)"); + }); + } + + /** + * Take a set of user-based assertions and run them against multiple users, in different threads. + */ + private void testAgainstMultipleUsers(Consumer testAssertions) { + + CountDownLatch latch = new CountDownLatch(2); + + threadedTest("User1", latch, testAssertions); + threadedTest("User2", latch, testAssertions); + + try { + latch.await(10L, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + /** + * Inside a {@link Runnable}, fetch the {@link ThreadLocal}-based username and execute the provided + * set of assertions. Then signal through the provided {@link CountDownLatch}. + */ + private void threadedTest(String user, CountDownLatch latch, Consumer testAssertions) { + + new Thread(() -> { + userHandler.set(user); + + testAssertions.accept(user); + + latch.countDown(); + }).start(); + } + + /** + * Plug in a custom {@link NamingStrategy} for this test case. + */ + private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { + + JdbcMappingContext context = new JdbcMappingContext(namingStrategy); + JdbcPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); + + return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + } + + static class DummyEntity { + + @Id Long id; + String name; + ReferencedEntity ref; + } + + static class ReferencedEntity { + + @Id Long l1id; + String content; + SecondLevelReferencedEntity further; + } + + static class SecondLevelReferencedEntity { + + @Id Long l2id; + String something; + } + +} diff --git a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java new file mode 100644 index 0000000000..29cecf745e --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorFixedNamingStrategyUnitTests.java @@ -0,0 +1,201 @@ +/* + * Copyright 2017 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 org.springframework.data.jdbc.core; + +import static org.assertj.core.api.Assertions.*; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Test; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; +import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; +import org.springframework.data.jdbc.mapping.model.NamingStrategy; +import org.springframework.data.mapping.PropertyPath; + +/** + * Unit tests to a fixed {@link NamingStrategy} implementation containing a hard wired schema, table, and property prefix. + * + * NOTE: Due to the need to verify SQL generation and {@link SqlGenerator}'s package-private status suggests + * this unit test exist in this package, not {@literal org.springframework.data.jdbc.mappings.model}. + * + * @author Greg Turnquist + */ +public class SqlGeneratorFixedNamingStrategyUnitTests { + + final NamingStrategy fixedCustomTablePrefixStrategy = new DefaultNamingStrategy() { + + @Override + public String getSchema() { + return "FixedCustomSchema"; + } + + @Override + public String getTableName(Class type) { + return "FixedCustomTablePrefix_" + type.getSimpleName(); + } + + @Override + public String getColumnName(JdbcPersistentProperty property) { + return "FixedCustomPropertyPrefix_" + property.getName(); + } + }; + + final NamingStrategy upperCaseLowerCaseStrategy = new DefaultNamingStrategy() { + + @Override + public String getTableName(Class type) { + return type.getSimpleName().toUpperCase(); + } + + @Override + public String getColumnName(JdbcPersistentProperty property) { + return property.getName().toLowerCase(); + } + }; + + @Test // DATAJDBC-107 + public void findOneWithOverriddenFixedTableName() { + + SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); + + String sql = sqlGenerator.getFindOne(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) // + .startsWith("SELECT") // + .contains("FixedCustomSchema.FixedCustomTablePrefix_DummyEntity.FixedCustomPropertyPrefix_id AS FixedCustomPropertyPrefix_id,") // + .contains("FixedCustomSchema.FixedCustomTablePrefix_DummyEntity.FixedCustomPropertyPrefix_name AS FixedCustomPropertyPrefix_name,") // + .contains("ref.FixedCustomPropertyPrefix_l1id AS ref_FixedCustomPropertyPrefix_l1id") // + .contains("ref.FixedCustomPropertyPrefix_content AS ref_FixedCustomPropertyPrefix_content") // + .contains("FROM FixedCustomSchema.FixedCustomTablePrefix_DummyEntity"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-107 + public void findOneWithUppercasedTablesAndLowercasedColumns() { + + SqlGenerator sqlGenerator = configureSqlGenerator(upperCaseLowerCaseStrategy); + + String sql = sqlGenerator.getFindOne(); + + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) // + .startsWith("SELECT") // + .contains("DUMMYENTITY.id AS id,") // + .contains("DUMMYENTITY.name AS name,") // + .contains("ref.l1id AS ref_l1id") // + .contains("ref.content AS ref_content") // + .contains("FROM DUMMYENTITY"); + softAssertions.assertAll(); + } + + @Test // DATAJDBC-107 + public void cascadingDeleteFirstLevel() { + + SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); + + String sql = sqlGenerator.createDeleteByPath(PropertyPath.from("ref", DummyEntity.class)); + + assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_DummyEntity = :rootId"); + } + + @Test // DATAJDBC-107 + public void cascadingDeleteAllSecondLevel() { + + SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); + + String sql = sqlGenerator.createDeleteByPath(PropertyPath.from("ref.further", DummyEntity.class)); + + assertThat(sql).isEqualTo( + "DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity IN " + + "(SELECT FixedCustomPropertyPrefix_l1id " + + "FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_DummyEntity = :rootId)"); + } + + @Test // DATAJDBC-107 + public void deleteAll() { + + SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); + + String sql = sqlGenerator.createDeleteAllSql(null); + + assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_DummyEntity"); + } + + @Test // DATAJDBC-107 + public void cascadingDeleteAllFirstLevel() { + + SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); + + String sql = sqlGenerator.createDeleteAllSql(PropertyPath.from("ref", DummyEntity.class)); + + assertThat(sql).isEqualTo("DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_DummyEntity IS NOT NULL"); + } + + @Test // DATAJDBC-107 + public void cascadingDeleteSecondLevel() { + + SqlGenerator sqlGenerator = configureSqlGenerator(fixedCustomTablePrefixStrategy); + + String sql = sqlGenerator.createDeleteAllSql(PropertyPath.from("ref.further", DummyEntity.class)); + + assertThat(sql).isEqualTo( + "DELETE FROM FixedCustomSchema.FixedCustomTablePrefix_SecondLevelReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity IN " + + "(SELECT FixedCustomPropertyPrefix_l1id " + + "FROM FixedCustomSchema.FixedCustomTablePrefix_ReferencedEntity " + + "WHERE FixedCustomSchema.FixedCustomTablePrefix_DummyEntity IS NOT NULL)"); + } + + /** + * Plug in a custom {@link NamingStrategy} for this test case. + * + * @param namingStrategy + */ + private SqlGenerator configureSqlGenerator(NamingStrategy namingStrategy) { + + JdbcMappingContext context = new JdbcMappingContext(namingStrategy); + JdbcPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); + return new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + } + + static class DummyEntity { + + @Id Long id; + String name; + ReferencedEntity ref; + } + + static class ReferencedEntity { + + @Id Long l1id; + String content; + SecondLevelReferencedEntity further; + } + + static class SecondLevelReferencedEntity { + + @Id Long l2id; + String something; + } + +} diff --git a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java index 14b027aec0..0b9238dbf9 100644 --- a/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/SqlGeneratorUnitTests.java @@ -18,10 +18,13 @@ import static org.assertj.core.api.Assertions.*; import org.assertj.core.api.SoftAssertions; +import org.junit.Before; import org.junit.Test; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; +import org.springframework.data.jdbc.mapping.model.NamingStrategy; import org.springframework.data.mapping.PropertyPath; /** @@ -31,22 +34,33 @@ */ public class SqlGeneratorUnitTests { - JdbcMappingContext context = new JdbcMappingContext(); - JdbcPersistentEntity persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); - SqlGenerator sqlGenerator = new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + private NamingStrategy namingStrategy; + private JdbcMappingContext context; + private JdbcPersistentEntity persistentEntity; + private SqlGenerator sqlGenerator; + + @Before + public void setUp() { + + this.namingStrategy = new DefaultNamingStrategy(); + this.context = new JdbcMappingContext(namingStrategy); + this.persistentEntity = context.getRequiredPersistentEntity(DummyEntity.class); + this.sqlGenerator = new SqlGenerator(context, persistentEntity, new SqlGeneratorSource(context)); + } @Test // DATAJDBC-112 public void findOne() { String sql = sqlGenerator.getFindOne(); - new SoftAssertions().assertThat(sql) // + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(sql) // .startsWith("SELECT") // - .contains("DummyEntity.id as id,") // - .contains("DummyEntity.name as name,") // - .contains("ref.id AS ref_id") // + .contains("DummyEntity.id AS id,") // + .contains("DummyEntity.name AS name,") // + .contains("ref.l1id AS ref_l1id") // .contains("ref.content AS ref_content").contains(" FROM DummyEntity"); - new SoftAssertions().assertAll(); + softAssertions.assertAll(); } @Test // DATAJDBC-112 diff --git a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriterUnitTests.java index f84b23d8b7..4f66b36fd2 100644 --- a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriterUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityDeleteWriterUnitTests.java @@ -25,6 +25,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.conversion.AggregateChange.Kind; import org.springframework.data.jdbc.core.conversion.DbAction.Delete; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; /** @@ -35,7 +36,7 @@ @RunWith(MockitoJUnitRunner.class) public class JdbcEntityDeleteWriterUnitTests { - JdbcEntityDeleteWriter converter = new JdbcEntityDeleteWriter(new JdbcMappingContext()); + JdbcEntityDeleteWriter converter = new JdbcEntityDeleteWriter(new JdbcMappingContext(new DefaultNamingStrategy())); @Test public void deleteDeletesTheEntityAndReferencedEntities() { diff --git a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java index 9d1e352310..87ce0441ba 100644 --- a/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/conversion/JdbcEntityWriterUnitTests.java @@ -27,6 +27,7 @@ import org.springframework.data.jdbc.core.conversion.DbAction.Delete; import org.springframework.data.jdbc.core.conversion.DbAction.Insert; import org.springframework.data.jdbc.core.conversion.DbAction.Update; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; /** @@ -37,7 +38,7 @@ @RunWith(MockitoJUnitRunner.class) public class JdbcEntityWriterUnitTests { - JdbcEntityWriter converter = new JdbcEntityWriter(new JdbcMappingContext()); + JdbcEntityWriter converter = new JdbcEntityWriter(new JdbcMappingContext(new DefaultNamingStrategy())); @Test // DATAJDBC-112 public void newEntityGetsConvertedToOneInsert() { diff --git a/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentPropertyUnitTests.java index 1df1dedb65..ce9bf881e4 100644 --- a/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentPropertyUnitTests.java @@ -28,7 +28,7 @@ import org.springframework.data.mapping.PropertyHandler; /** - * Unti tests for the {@link BasicJdbcPersistentProperty}. + * Unit tests for the {@link BasicJdbcPersistentProperty}. * * @author Jens Schauder */ @@ -36,7 +36,9 @@ public class BasicJdbcPersistentPropertyUnitTests { @Test // DATAJDBC-104 public void enumGetsStoredAsString() { - JdbcPersistentEntity persistentEntity = new JdbcMappingContext().getRequiredPersistentEntity(DummyEntity.class); + + JdbcPersistentEntity persistentEntity = new JdbcMappingContext(new DefaultNamingStrategy()) + .getRequiredPersistentEntity(DummyEntity.class); persistentEntity.doWithProperties((PropertyHandler) p -> { switch (p.getName()) { diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java index 230ad6d1ab..316a621415 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java @@ -33,6 +33,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; +import org.springframework.data.jdbc.mapping.model.NamingStrategy; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.repository.CrudRepository; @@ -128,22 +130,36 @@ Class testClass() { return JdbcRepositoryIdGenerationIntegrationTests.class; } + /** + * {@link NamingStrategy} that harmlessly uppercases the table name, + * demonstrating how to inject one while not breaking existing SQL operations. + */ + @Bean + NamingStrategy namingStrategy() { + return new DefaultNamingStrategy() { + @Override + public String getTableName(Class type) { + return type.getSimpleName().toUpperCase(); + } + }; + } + @Bean NamedParameterJdbcTemplate template(DataSource db) { return new NamedParameterJdbcTemplate(db); } @Bean - ReadOnlyIdEntityRepository readOnlyIdRepository(DataSource db) { + ReadOnlyIdEntityRepository readOnlyIdRepository(DataSource db, NamingStrategy namingStrategy) { - return new JdbcRepositoryFactory(new NamedParameterJdbcTemplate(db), mock(ApplicationEventPublisher.class)) + return new JdbcRepositoryFactory(new NamedParameterJdbcTemplate(db), mock(ApplicationEventPublisher.class), namingStrategy) .getRepository(ReadOnlyIdEntityRepository.class); } @Bean PrimitiveIdEntityRepository primitiveIdRepository(NamedParameterJdbcTemplate template) { - return new JdbcRepositoryFactory(template, mock(ApplicationEventPublisher.class)) + return new JdbcRepositoryFactory(template, mock(ApplicationEventPublisher.class), new DefaultNamingStrategy()) .getRepository(PrimitiveIdEntityRepository.class); } } diff --git a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index 982152d019..af0a6e5e24 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -24,6 +24,7 @@ import org.springframework.data.jdbc.mapping.event.BeforeUpdate; import org.springframework.data.jdbc.mapping.event.Identifier; import org.springframework.data.jdbc.mapping.event.JdbcEvent; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -43,7 +44,7 @@ public class SimpleJdbcRepositoryEventsUnitTests { public void before() { NamedParameterJdbcOperations operations = createIdGeneratingOperations(); - JdbcRepositoryFactory factory = new JdbcRepositoryFactory(operations, publisher); + JdbcRepositoryFactory factory = new JdbcRepositoryFactory(operations, publisher, new DefaultNamingStrategy()); repository = factory.getRepository(DummyEntityRepository.class); } diff --git a/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index 0c93cc3f6d..caa1460b28 100644 --- a/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -22,6 +22,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.data.jdbc.mapping.model.DefaultNamingStrategy; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; @@ -41,7 +42,7 @@ public class TestConfiguration { @Bean JdbcRepositoryFactory jdbcRepositoryFactory() { - return new JdbcRepositoryFactory(namedParameterJdbcTemplate(), publisher); + return new JdbcRepositoryFactory(namedParameterJdbcTemplate(), publisher, new DefaultNamingStrategy()); } @Bean