diff --git a/pom.xml b/pom.xml index 7471461f..0dfc817c 100644 --- a/pom.xml +++ b/pom.xml @@ -204,7 +204,7 @@ **/*HsqlIntegrationTests.java - orcacle + oracle diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java index bd67f5c4..996c620f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java @@ -72,6 +72,7 @@ class JdbcAggregateChangeExecutionContext { } void executeInsertRoot(DbAction.InsertRoot insert) { + RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(insert.getEntityType()); Object id; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java index afa4b500..8b1e729f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java @@ -37,7 +37,7 @@ import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; -import org.springframework.data.relational.core.dialect.LockClause; +import org.springframework.data.relational.core.dialect.IdGeneration; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; @@ -122,11 +122,20 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { KeyHolder holder = new GeneratedKeyHolder(); - operations.update( // - sqlGenerator.getInsert(new HashSet<>(parameterSource.getIdentifiers())), // - parameterSource, // - holder // - ); + IdGeneration idGeneration = sqlGeneratorSource.getDialect().getIdGeneration(); + String insertSql = sqlGenerator.getInsert(new HashSet<>(parameterSource.getIdentifiers())); + + if (idGeneration.driverRequiresKeyColumnNames()) { + + String[] keyColumnNames = getKeyColumnNames(domainType); + if (keyColumnNames.length == 0) { + operations.update(insertSql, parameterSource, holder); + } else { + operations.update(insertSql, parameterSource, holder, keyColumnNames); + } + } else { + operations.update(insertSql, parameterSource, holder); + } return getIdFromHolder(holder, persistentEntity); } @@ -567,6 +576,19 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy { return sqlGeneratorSource.getSqlGenerator(domainType); } + private String[] getKeyColumnNames(Class domainType) { + + RelationalPersistentEntity requiredPersistentEntity = context.getRequiredPersistentEntity(domainType); + + if (!requiredPersistentEntity.hasIdProperty()) { + return new String[0]; + } + + SqlIdentifier idColumn = requiredPersistentEntity.getIdColumn(); + + return new String[] { idColumn.getReference(getIdentifierProcessing()) }; + } + /** * Utility to create {@link Predicate}s. */ diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java index 17c5c930..1c144e0c 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java @@ -33,6 +33,7 @@ import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.H2Dialect; import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.dialect.MySqlDialect; +import org.springframework.data.relational.core.dialect.OracleDialect; import org.springframework.data.relational.core.dialect.PostgresDialect; import org.springframework.data.relational.core.dialect.SqlServerDialect; import org.springframework.data.relational.core.sql.IdentifierProcessing; @@ -131,6 +132,9 @@ public class DialectResolver { if (name.contains("db2")) { return Db2Dialect.INSTANCE; } + if (name.contains("oracle")) { + return OracleDialect.INSTANCE; + } LOG.info(String.format("Couldn't determine Dialect for \"%s\"", name) ); return null; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java index 0b260c18..b8bb4ed8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java @@ -17,6 +17,8 @@ package org.springframework.data.jdbc.core; import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; +import static org.springframework.test.context.TestExecutionListeners.MergeMode.*; import lombok.Data; import lombok.EqualsAndHashCode; @@ -35,7 +37,6 @@ import java.util.function.Function; import java.util.stream.IntStream; import org.assertj.core.api.SoftAssertions; -import org.junit.Assume; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; @@ -53,18 +54,17 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.JdbcConverter; -import org.springframework.data.jdbc.testing.DatabaseProfileValueSource; -import org.springframework.data.jdbc.testing.HsqlDbOnly; +import org.springframework.data.jdbc.testing.AssumeFeatureRule; +import org.springframework.data.jdbc.testing.RequiredFeature; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.relational.core.conversion.DbActionExecutionException; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.Table; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.test.annotation.IfProfileValue; -import org.springframework.test.annotation.ProfileValueSourceConfiguration; -import org.springframework.test.annotation.ProfileValueUtils; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.transaction.annotation.Transactional; @@ -83,7 +83,7 @@ import org.springframework.transaction.annotation.Transactional; */ @ContextConfiguration @Transactional -@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class) +@TestExecutionListeners(value = AssumeFeatureRule.class, mergeMode = MERGE_WITH_DEFAULTS) public class JdbcAggregateTemplateIntegrationTests { @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @@ -91,6 +91,7 @@ public class JdbcAggregateTemplateIntegrationTests { @Autowired JdbcAggregateOperations template; @Autowired NamedParameterJdbcOperations jdbcTemplate; + LegoSet legoSet = createLegoSet("Star Destroyer"); /** @@ -177,13 +178,6 @@ public class JdbcAggregateTemplateIntegrationTests { return "_" + i; } - private static void assumeNot(String dbProfileName) { - - Assume.assumeTrue("true" - .equalsIgnoreCase(ProfileValueUtils.retrieveProfileValueSource(JdbcAggregateTemplateIntegrationTests.class) - .get("current.database.is.not." + dbProfileName))); - } - private static LegoSet createLegoSet(String name) { LegoSet entity = new LegoSet(); @@ -197,6 +191,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadAnEntityWithReferencedEntityById() { template.save(legoSet); @@ -218,6 +213,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadManyEntitiesWithReferencedEntity() { template.save(legoSet); @@ -230,6 +226,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-101 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadManyEntitiesWithReferencedEntitySorted() { template.save(createLegoSet("Lava")); @@ -244,20 +241,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-101 - public void saveAndLoadManyEntitiesWithReferencedEntityPaged() { - - template.save(createLegoSet("Lava")); - template.save(createLegoSet("Star")); - template.save(createLegoSet("Frozen")); - - Iterable reloadedLegoSets = template.findAll(LegoSet.class, PageRequest.of(1, 1)); - - assertThat(reloadedLegoSets) // - .extracting("name") // - .containsExactly("Star"); - } - - @Test // DATAJDBC-101 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadManyEntitiesWithReferencedEntitySortedAndPaged() { template.save(createLegoSet("Lava")); @@ -272,6 +256,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadManyEntitiesByIdWithReferencedEntity() { template.save(legoSet); @@ -283,6 +268,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadAnEntityWithReferencedNullEntity() { legoSet.setManual(null); @@ -295,6 +281,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndDeleteAnEntityWithReferencedEntity() { template.save(legoSet); @@ -310,6 +297,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndDeleteAllWithReferencedEntity() { template.save(legoSet); @@ -325,7 +313,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void updateReferencedEntityFromNull() { legoSet.setManual(null); @@ -344,6 +332,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void updateReferencedEntityToNull() { template.save(legoSet); @@ -374,6 +363,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void replaceReferencedEntity() { template.save(legoSet); @@ -395,7 +385,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-112 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 + @RequiredFeature({SUPPORTS_QUOTED_IDS, TestDatabaseFeatures.Feature.SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES}) public void changeReferencedEntity() { template.save(legoSet); @@ -410,6 +400,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-266 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void oneToOneChildWithoutId() { OneToOneParent parent = new OneToOneParent(); @@ -426,6 +417,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-266 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void oneToOneNullChildWithoutId() { OneToOneParent parent = new OneToOneParent(); @@ -441,6 +433,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-266 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void oneToOneNullAttributes() { OneToOneParent parent = new OneToOneParent(); @@ -456,6 +449,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-125 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadAnEntityWithSecondaryReferenceNull() { template.save(legoSet); @@ -468,6 +462,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-125 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadAnEntityWithSecondaryReferenceNotNull() { legoSet.alternativeInstructions = new Manual(); @@ -489,6 +484,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-276 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadAnEntityWithListOfElementsWithoutId() { ListParent entity = new ListParent(); @@ -507,15 +503,9 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-259 + @RequiredFeature(SUPPORTS_ARRAYS) public void saveAndLoadAnEntityWithArray() { - // MySQL and other do not support array datatypes. See - // https://dev.mysql.com/doc/refman/8.0/en/data-type-overview.html - assumeNot("mysql"); - assumeNot("mariadb"); - assumeNot("mssql"); - assumeNot("db2"); - ArrayOwner arrayOwner = new ArrayOwner(); arrayOwner.digits = new String[] { "one", "two", "three" }; @@ -531,17 +521,9 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-259, DATAJDBC-512 + @RequiredFeature(SUPPORTS_MULTIDIMENSIONAL_ARRAYS) public void saveAndLoadAnEntityWithMultidimensionalArray() { - // MySQL and other do not support array datatypes. See - // https://dev.mysql.com/doc/refman/8.0/en/data-type-overview.html - assumeNot("h2"); - assumeNot("mysql"); - assumeNot("mariadb"); - assumeNot("mssql"); - assumeNot("hsqldb"); - assumeNot("db2"); - ArrayOwner arrayOwner = new ArrayOwner(); arrayOwner.multidimensional = new String[][] { { "one-a", "two-a", "three-a" }, { "one-b", "two-b", "three-b" } }; @@ -558,15 +540,9 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-259 + @RequiredFeature(SUPPORTS_ARRAYS) public void saveAndLoadAnEntityWithList() { - // MySQL and others do not support array datatypes. See - // https://dev.mysql.com/doc/refman/8.0/en/data-type-overview.html - assumeNot("mysql"); - assumeNot("mariadb"); - assumeNot("mssql"); - assumeNot("db2"); - ListOwner arrayOwner = new ListOwner(); arrayOwner.digits.addAll(Arrays.asList("one", "two", "three")); @@ -582,15 +558,9 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-259 + @RequiredFeature(SUPPORTS_ARRAYS) public void saveAndLoadAnEntityWithSet() { - // MySQL and others do not support array datatypes. See - // https://dev.mysql.com/doc/refman/8.0/en/data-type-overview.html - assumeNot("mysql"); - assumeNot("mariadb"); - assumeNot("mssql"); - assumeNot("db2"); - SetOwner setOwner = new SetOwner(); setOwner.digits.addAll(Arrays.asList("one", "two", "three")); @@ -621,6 +591,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-340 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadLongChain() { Chain4 chain4 = new Chain4(); @@ -649,6 +620,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-359 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void saveAndLoadLongChainWithoutIds() { NoIdChain4 chain4 = new NoIdChain4(); @@ -733,7 +705,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-431 - @HsqlDbOnly + @RequiredFeature(IS_HSQL) public void readOnlyGetsLoadedButNotWritten() { WithReadOnly entity = new WithReadOnly(); @@ -841,6 +813,7 @@ public class JdbcAggregateTemplateIntegrationTests { } @Test // DATAJDBC-462 + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void resavingAnUnversionedEntity() { LegoSet legoSet = new LegoSet(); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java index 3ce135e1..0be9a8f6 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java @@ -15,6 +15,10 @@ */ package org.springframework.data.jdbc.core; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; +import static org.springframework.test.context.TestExecutionListeners.MergeMode.*; + import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; @@ -26,17 +30,18 @@ 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.AssumeFeatureRule; +import org.springframework.data.jdbc.testing.RequiredFeature; import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.transaction.annotation.Transactional; -import static org.assertj.core.api.Assertions.*; - /** * Integration tests for {@link JdbcAggregateTemplate} using an entity mapped with an explicite schema. * @@ -44,6 +49,7 @@ import static org.assertj.core.api.Assertions.*; */ @ContextConfiguration @Transactional +@TestExecutionListeners(value = AssumeFeatureRule.class, mergeMode = MERGE_WITH_DEFAULTS) public class JdbcAggregateTemplateSchemaIntegrationTests { @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @@ -52,8 +58,8 @@ public class JdbcAggregateTemplateSchemaIntegrationTests { @Autowired JdbcAggregateOperations template; @Autowired NamedParameterJdbcOperations jdbcTemplate; - @Test + @RequiredFeature(SUPPORTS_QUOTED_IDS) public void insertFindUpdateDelete() { DummyEntity entity = new DummyEntity(); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java index ef9d339a..bd9f4b49 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryCustomConversionIntegrationTests.java @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.repository; import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.context.TestExecutionListeners.MergeMode.*; import java.math.BigDecimal; import java.sql.JDBCType; @@ -37,9 +38,12 @@ import org.springframework.data.convert.WritingConverter; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.core.convert.JdbcValue; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.AssumeFeatureRule; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.repository.CrudRepository; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.transaction.annotation.Transactional; @@ -52,6 +56,7 @@ import org.springframework.transaction.annotation.Transactional; */ @ContextConfiguration @Transactional +@TestExecutionListeners(value = AssumeFeatureRule.class, mergeMode = MERGE_WITH_DEFAULTS) public class JdbcRepositoryCustomConversionIntegrationTests { @Configuration @@ -105,7 +110,7 @@ public class JdbcRepositoryCustomConversionIntegrationTests { public void saveAndLoadAnEntity() { EntityWithStringyBigDecimal entity = new EntityWithStringyBigDecimal(); - entity.stringyNumber = "123456.78910"; + entity.stringyNumber = "123456.78912"; repository.save(entity); @@ -121,7 +126,7 @@ public class JdbcRepositoryCustomConversionIntegrationTests { public void saveAndLoadAnEntityWithReference() { EntityWithStringyBigDecimal entity = new EntityWithStringyBigDecimal(); - entity.stringyNumber = "123456.78910"; + entity.stringyNumber = "123456.78912"; entity.reference = new OtherEntity(); entity.reference.created = new Date(); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java index c4df3155..1170f55d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.java @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.repository; import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.context.TestExecutionListeners.MergeMode.*; import lombok.Data; @@ -31,7 +32,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.AssumeFeatureRule; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Embedded; @@ -41,6 +44,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.test.jdbc.JdbcTestUtils; @@ -53,33 +57,32 @@ import org.springframework.transaction.annotation.Transactional; */ @ContextConfiguration @Transactional +@TestExecutionListeners(value = AssumeFeatureRule.class, mergeMode = MERGE_WITH_DEFAULTS) public class JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests { - @Configuration - @Import(TestConfiguration.class) - static class Config { - - @Autowired JdbcRepositoryFactory factory; - - @Bean - Class testClass() { - return JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.class; - } - - @Bean - DummyEntityRepository dummyEntityRepository() { - return factory.getRepository(DummyEntityRepository.class); - } - - } - @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @Rule public SpringMethodRule methodRule = new SpringMethodRule(); - @Autowired NamedParameterJdbcTemplate template; @Autowired DummyEntityRepository repository; @Autowired Dialect dialect; + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); + + entity.setTest("rootTest"); + + final DummyEntity2 dummyEntity2 = new DummyEntity2(); + dummyEntity2.setTest("c1"); + + final Embeddable embeddable = new Embeddable(); + embeddable.setAttr(1L); + dummyEntity2.setEmbeddable(embeddable); + + entity.setDummyEntity2(dummyEntity2); + + return entity; + } + @Test // DATAJDBC-111 public void savesAnEntity() throws SQLException { @@ -94,8 +97,7 @@ public class JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests { SqlIdentifier id = SqlIdentifier.quoted("ID"); String whereClause = id.toSql(dialect.getIdentifierProcessing()) + " = " + idValue; - return JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), - name, whereClause); + return JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), name, whereClause); } @Test // DATAJDBC-111 @@ -187,6 +189,7 @@ public class JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests { @Test // DATAJDBC-111 public void deleteByEntity() { + DummyEntity one = repository.save(createDummyEntity()); DummyEntity two = repository.save(createDummyEntity()); DummyEntity three = repository.save(createDummyEntity()); @@ -226,25 +229,26 @@ public class JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests { assertThat(repository.findAll()).isEmpty(); } - private static DummyEntity createDummyEntity() { - DummyEntity entity = new DummyEntity(); - - entity.setTest("rootTest"); - - final DummyEntity2 dummyEntity2 = new DummyEntity2(); - dummyEntity2.setTest("c1"); - - final Embeddable embeddable = new Embeddable(); - embeddable.setAttr(1L); - dummyEntity2.setEmbeddable(embeddable); - - entity.setDummyEntity2(dummyEntity2); - - return entity; - } - interface DummyEntityRepository extends CrudRepository {} + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + } + @Data static class DummyEntity { @Column("ID") @Id Long id; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java index db3c0059..41f80044 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.repository; import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.context.TestExecutionListeners.MergeMode.*; import lombok.Data; @@ -31,7 +32,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.AssumeFeatureRule; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Embedded; @@ -41,6 +44,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.test.jdbc.JdbcTestUtils; @@ -54,33 +58,33 @@ import org.springframework.transaction.annotation.Transactional; */ @ContextConfiguration @Transactional +@TestExecutionListeners(value = AssumeFeatureRule.class, mergeMode = MERGE_WITH_DEFAULTS) public class JdbcRepositoryEmbeddedWithReferenceIntegrationTests { - @Configuration - @Import(TestConfiguration.class) - static class Config { - - @Autowired JdbcRepositoryFactory factory; - - @Bean - Class testClass() { - return JdbcRepositoryEmbeddedWithReferenceIntegrationTests.class; - } - - @Bean - DummyEntityRepository dummyEntityRepository() { - return factory.getRepository(DummyEntityRepository.class); - } - - } - @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @Rule public SpringMethodRule methodRule = new SpringMethodRule(); - @Autowired NamedParameterJdbcTemplate template; @Autowired DummyEntityRepository repository; @Autowired Dialect dialect; + private static DummyEntity createDummyEntity() { + + DummyEntity entity = new DummyEntity(); + entity.setTest("root"); + + final Embeddable embeddable = new Embeddable(); + embeddable.setTest("embedded"); + + final DummyEntity2 dummyEntity2 = new DummyEntity2(); + dummyEntity2.setTest("entity"); + + embeddable.setDummyEntity2(dummyEntity2); + + entity.setEmbeddable(embeddable); + + return entity; + } + @Test // DATAJDBC-111 public void savesAnEntity() { @@ -187,6 +191,7 @@ public class JdbcRepositoryEmbeddedWithReferenceIntegrationTests { @Test // DATAJDBC-111 public void deleteByEntity() { + DummyEntity one = repository.save(createDummyEntity()); DummyEntity two = repository.save(createDummyEntity()); DummyEntity three = repository.save(createDummyEntity()); @@ -246,28 +251,28 @@ public class JdbcRepositoryEmbeddedWithReferenceIntegrationTests { } - private static DummyEntity createDummyEntity() { - - DummyEntity entity = new DummyEntity(); - entity.setTest("root"); - - final Embeddable embeddable = new Embeddable(); - embeddable.setTest("embedded"); - - final DummyEntity2 dummyEntity2 = new DummyEntity2(); - dummyEntity2.setTest("entity"); - - embeddable.setDummyEntity2(dummyEntity2); - - entity.setEmbeddable(embeddable); - - return entity; - } - interface DummyEntityRepository extends CrudRepository { List findByTest(String test); } + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryEmbeddedWithReferenceIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + } + @Data private static class DummyEntity { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 28662235..1d8d88e5 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -18,6 +18,8 @@ package org.springframework.data.jdbc.repository; import static java.util.Arrays.*; import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.SoftAssertions.*; +import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; +import static org.springframework.test.context.TestExecutionListeners.MergeMode.*; import lombok.Data; @@ -41,7 +43,10 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; +import org.springframework.data.jdbc.testing.AssumeFeatureRule; +import org.springframework.data.jdbc.testing.RequiredFeature; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent; import org.springframework.data.relational.core.mapping.event.AfterLoadEvent; import org.springframework.data.repository.CrudRepository; @@ -51,6 +56,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.test.jdbc.JdbcTestUtils; @@ -63,56 +69,23 @@ import org.springframework.transaction.annotation.Transactional; * @author Mark Paluch */ @Transactional +@TestExecutionListeners(value = AssumeFeatureRule.class, mergeMode = MERGE_WITH_DEFAULTS) public class JdbcRepositoryIntegrationTests { - @Configuration - @Import(TestConfiguration.class) - static class Config { - - @Autowired JdbcRepositoryFactory factory; - - @Bean - Class testClass() { - return JdbcRepositoryIntegrationTests.class; - } - - @Bean - DummyEntityRepository dummyEntityRepository() { - return factory.getRepository(DummyEntityRepository.class); - } - - @Bean - NamedQueries namedQueries() throws IOException { - - PropertiesFactoryBean properties = new PropertiesFactoryBean(); - properties.setLocation(new ClassPathResource("META-INF/jdbc-named-queries.properties")); - properties.afterPropertiesSet(); - return new PropertiesBasedNamedQueries(properties.getObject()); - } - - @Bean - MyEventListener eventListener() { - return new MyEventListener(); - } - } - - static class MyEventListener implements ApplicationListener> { - - private List> events = new ArrayList<>(); - - @Override - public void onApplicationEvent(AbstractRelationalEvent event) { - events.add(event); - } - } - @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @Rule public SpringMethodRule methodRule = new SpringMethodRule(); - @Autowired NamedParameterJdbcTemplate template; @Autowired DummyEntityRepository repository; @Autowired MyEventListener eventListener; + private static DummyEntity createDummyEntity() { + + DummyEntity entity = new DummyEntity(); + entity.setName("Entity Name"); + + return entity; + } + @Before public void before() { eventListener.events.clear(); @@ -289,6 +262,7 @@ public class JdbcRepositoryIntegrationTests { } @Test // DATAJDBC-464, DATAJDBC-318 + @RequiredFeature(SUPPORTS_DATE_DATATYPES) public void executeQueryWithParameterRequiringConversion() { Instant now = Instant.now(); @@ -382,14 +356,6 @@ public class JdbcRepositoryIntegrationTests { assertThat(repository.countByName(one.getName())).isEqualTo(2); } - private static DummyEntity createDummyEntity() { - - DummyEntity entity = new DummyEntity(); - entity.setName("Entity Name"); - - return entity; - } - interface DummyEntityRepository extends CrudRepository { List findAllByNamedQuery(); @@ -413,11 +379,52 @@ public class JdbcRepositoryIntegrationTests { int countByName(String name); } + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + @Bean + NamedQueries namedQueries() throws IOException { + + PropertiesFactoryBean properties = new PropertiesFactoryBean(); + properties.setLocation(new ClassPathResource("META-INF/jdbc-named-queries.properties")); + properties.afterPropertiesSet(); + return new PropertiesBasedNamedQueries(properties.getObject()); + } + + @Bean + MyEventListener eventListener() { + return new MyEventListener(); + } + } + + static class MyEventListener implements ApplicationListener> { + + private List> events = new ArrayList<>(); + + @Override + public void onApplicationEvent(AbstractRelationalEvent event) { + events.add(event); + } + } + @Data static class DummyEntity { String name; - @Id private Long idProp; Instant pointInTime; + @Id private Long idProp; } static class CustomRowMapper implements RowMapper { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryPropertyConversionIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryPropertyConversionIntegrationTests.java index 8ae91526..85749fe7 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryPropertyConversionIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryPropertyConversionIntegrationTests.java @@ -17,6 +17,8 @@ package org.springframework.data.jdbc.repository; import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; +import static org.springframework.test.context.TestExecutionListeners.MergeMode.*; import lombok.Data; @@ -40,13 +42,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; -import org.springframework.data.jdbc.testing.DatabaseProfileValueSource; +import org.springframework.data.jdbc.testing.AssumeFeatureRule; +import org.springframework.data.jdbc.testing.RequiredFeature; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent; import org.springframework.data.repository.CrudRepository; -import org.springframework.test.annotation.IfProfileValue; -import org.springframework.test.annotation.ProfileValueSourceConfiguration; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.springframework.transaction.annotation.Transactional; @@ -59,10 +62,106 @@ import org.springframework.transaction.annotation.Transactional; * @author Thomas Lang */ @ContextConfiguration -@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class) @Transactional +@TestExecutionListeners(value = AssumeFeatureRule.class, mergeMode = MERGE_WITH_DEFAULTS) public class JdbcRepositoryPropertyConversionIntegrationTests { + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + @Autowired DummyEntityRepository repository; + + private static EntityWithColumnsRequiringConversions createDummyEntity() { + + EntityWithColumnsRequiringConversions entity = new EntityWithColumnsRequiringConversions(); + entity.setSomeEnum(SomeEnum.VALUE); + entity.setBigDecimal(new BigDecimal("123456789012345678901234567890123456789012345678901234567890")); + entity.setBool(true); + // Postgres doesn't seem to be able to handle BigInts larger then a Long, since the driver reads them as Long + entity.setBigInteger(BigInteger.valueOf(Long.MAX_VALUE)); + entity.setDate(Date.from(getNow().toInstant(ZoneOffset.UTC))); + entity.setLocalDateTime(getNow()); + + return entity; + } + + // DATAJDBC-119 + private static LocalDateTime getNow() { + return LocalDateTime.now().withNano(0); + } + + @Test // DATAJDBC-95 + @RequiredFeature(SUPPORTS_HUGE_NUMBERS) + public void saveAndLoadAnEntity() { + + EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); + + assertThat(repository.findById(entity.getIdTimestamp())).hasValueSatisfying(it -> { + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(it.getIdTimestamp()).isEqualTo(entity.getIdTimestamp()); + softly.assertThat(it.getSomeEnum()).isEqualTo(entity.getSomeEnum()); + softly.assertThat(it.getBigDecimal()).isEqualTo(entity.getBigDecimal()); + softly.assertThat(it.isBool()).isEqualTo(entity.isBool()); + softly.assertThat(it.getBigInteger()).isEqualTo(entity.getBigInteger()); + softly.assertThat(it.getDate()).is(representingTheSameAs(entity.getDate())); + softly.assertThat(it.getLocalDateTime()).isEqualTo(entity.getLocalDateTime()); + softly.assertAll(); + }); + } + + @Test // DATAJDBC-95 + @RequiredFeature(SUPPORTS_HUGE_NUMBERS) + public void existsById() { + + EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); + + assertThat(repository.existsById(entity.getIdTimestamp())).isTrue(); + } + + @Test // DATAJDBC-95 + @RequiredFeature(SUPPORTS_HUGE_NUMBERS) + public void findAllById() { + + EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); + + assertThat(repository.findAllById(Collections.singletonList(entity.getIdTimestamp()))).hasSize(1); + } + + @Test // DATAJDBC-95 + @RequiredFeature(SUPPORTS_HUGE_NUMBERS) + public void deleteAll() { + + EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); + + repository.deleteAll(singletonList(entity)); + + assertThat(repository.findAll()).hasSize(0); + } + + @Test // DATAJDBC-95 + @RequiredFeature(SUPPORTS_HUGE_NUMBERS) + public void deleteById() { + + EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); + + repository.deleteById(entity.getIdTimestamp()); + + assertThat(repository.findAll()).hasSize(0); + } + + private Condition representingTheSameAs(Date other) { + + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + String expected = format.format(other); + + return new Condition<>(date -> format.format(date).equals(expected), expected); + } + + enum SomeEnum { + VALUE + } + + interface DummyEntityRepository extends CrudRepository {} + @Configuration @Import(TestConfiguration.class) static class Config { @@ -84,123 +183,19 @@ public class JdbcRepositoryPropertyConversionIntegrationTests { return (ApplicationListener) beforeInsert -> ((EntityWithColumnsRequiringConversions) beforeInsert .getEntity()).setIdTimestamp(getNow()); } - } - @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); - @Rule public SpringMethodRule methodRule = new SpringMethodRule(); - - @Autowired DummyEntityRepository repository; - - @Test // DATAJDBC-95 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 - public void saveAndLoadAnEntity() { - - EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); - - assertThat(repository.findById(entity.getIdTimestamp())).hasValueSatisfying(it -> { - SoftAssertions softly = new SoftAssertions(); - softly.assertThat(it.getIdTimestamp()).isEqualTo(entity.getIdTimestamp()); - softly.assertThat(it.getSomeEnum()).isEqualTo(entity.getSomeEnum()); - softly.assertThat(it.getBigDecimal()).isEqualTo(entity.getBigDecimal()); - softly.assertThat(it.isBool()).isEqualTo(entity.isBool()); - softly.assertThat(it.getBigInteger()).isEqualTo(entity.getBigInteger()); - softly.assertThat(it.getDate()).is(representingTheSameAs(entity.getDate())); - softly.assertThat(it.getLocalDateTime()).isEqualTo(entity.getLocalDateTime()); - softly.assertAll(); - }); - } - - @Test // DATAJDBC-95 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 - public void existsById() { - - EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); - - assertThat(repository.existsById(entity.getIdTimestamp())).isTrue(); - } - - @Test // DATAJDBC-95 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 - public void findAllById() { - - EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); - - assertThat(repository.findAllById(Collections.singletonList(entity.getIdTimestamp()))).hasSize(1); - } - - @Test // DATAJDBC-95 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 - public void deleteAll() { - - EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); - - repository.deleteAll(singletonList(entity)); - - assertThat(repository.findAll()).hasSize(0); - } - - @Test // DATAJDBC-95 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 - public void deleteById() { - - EntityWithColumnsRequiringConversions entity = repository.save(createDummyEntity()); - - repository.deleteById(entity.getIdTimestamp()); - - assertThat(repository.findAll()).hasSize(0); - } - - private static EntityWithColumnsRequiringConversions createDummyEntity() { - - EntityWithColumnsRequiringConversions entity = new EntityWithColumnsRequiringConversions(); - entity.setSomeEnum(SomeEnum.VALUE); - entity.setBigDecimal(new BigDecimal("123456789012345678901234567890123456789012345678901234567890")); - entity.setBool(true); - // Postgres doesn't seem to be able to handle BigInts larger then a Long, since the driver reads them as Long - entity.setBigInteger(BigInteger.valueOf(Long.MAX_VALUE)); - entity.setDate(Date.from(getNow().toInstant(ZoneOffset.UTC))); - entity.setLocalDateTime(getNow()); - - return entity; - } - - // DATAJDBC-119 - private static LocalDateTime getNow() { - return LocalDateTime.now().withNano(0); - } - - private Condition representingTheSameAs(Date other) { - - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); - String expected = format.format(other); - - return new Condition<>(date -> format.format(date).equals(expected), expected); - } - - interface DummyEntityRepository extends CrudRepository {} - @Data static class EntityWithColumnsRequiringConversions { + boolean bool; + SomeEnum someEnum; + BigDecimal bigDecimal; + BigInteger bigInteger; + Date date; + LocalDateTime localDateTime; // ensures conversion on id querying @Id private LocalDateTime idTimestamp; - boolean bool; - - SomeEnum someEnum; - - BigDecimal bigDecimal; - - BigInteger bigInteger; - - Date date; - - LocalDateTime localDateTime; - - } - - enum SomeEnum { - VALUE } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithCollectionsIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithCollectionsIntegrationTests.java index de8a1844..728e2047 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithCollectionsIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithCollectionsIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.repository; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; import junit.framework.AssertionFailedError; import lombok.Data; @@ -34,12 +35,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; -import org.springframework.data.jdbc.testing.DatabaseProfileValueSource; +import org.springframework.data.jdbc.testing.RequiredFeature; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.test.annotation.IfProfileValue; -import org.springframework.test.annotation.ProfileValueSourceConfiguration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; @@ -52,33 +52,22 @@ import org.springframework.transaction.annotation.Transactional; * @author Thomas Lang */ @ContextConfiguration -@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class) @Transactional public class JdbcRepositoryWithCollectionsIntegrationTests { - @Configuration - @Import(TestConfiguration.class) - static class Config { - - @Autowired JdbcRepositoryFactory factory; - - @Bean - Class testClass() { - return JdbcRepositoryWithCollectionsIntegrationTests.class; - } - - @Bean - DummyEntityRepository dummyEntityRepository() { - return factory.getRepository(DummyEntityRepository.class); - } - } - @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @Rule public SpringMethodRule methodRule = new SpringMethodRule(); @Autowired NamedParameterJdbcTemplate template; @Autowired DummyEntityRepository repository; + private static DummyEntity createDummyEntity() { + + DummyEntity entity = new DummyEntity(); + entity.setName("Entity Name"); + return entity; + } + @Test // DATAJDBC-113 public void saveAndLoadEmptySet() { @@ -139,7 +128,7 @@ public class JdbcRepositoryWithCollectionsIntegrationTests { } @Test // DATAJDBC-113 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 + @RequiredFeature(SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES) public void updateSet() { Element element1 = createElement("one"); @@ -203,29 +192,39 @@ public class JdbcRepositoryWithCollectionsIntegrationTests { return element; } - private static DummyEntity createDummyEntity() { - - DummyEntity entity = new DummyEntity(); - entity.setName("Entity Name"); - return entity; - } - interface DummyEntityRepository extends CrudRepository {} + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryWithCollectionsIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + } + @Data static class DummyEntity { - @Id private Long id; String name; Set content = new HashSet<>(); + @Id private Long id; } @RequiredArgsConstructor static class Element { - @Id private Long id; String content; + @Id private Long id; } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java index 5e1b3b0d..88d01f24 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithListsIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.repository; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; import junit.framework.AssertionFailedError; import lombok.Data; @@ -34,12 +35,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; -import org.springframework.data.jdbc.testing.DatabaseProfileValueSource; +import org.springframework.data.jdbc.testing.RequiredFeature; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.test.annotation.IfProfileValue; -import org.springframework.test.annotation.ProfileValueSourceConfiguration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; @@ -52,33 +52,21 @@ import org.springframework.transaction.annotation.Transactional; * @author Thomas Lang */ @ContextConfiguration -@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class) @Transactional public class JdbcRepositoryWithListsIntegrationTests { - @Configuration - @Import(TestConfiguration.class) - static class Config { - - @Autowired JdbcRepositoryFactory factory; - - @Bean - Class testClass() { - return JdbcRepositoryWithListsIntegrationTests.class; - } - - @Bean - DummyEntityRepository dummyEntityRepository() { - return factory.getRepository(DummyEntityRepository.class); - } - } - @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @Rule public SpringMethodRule methodRule = new SpringMethodRule(); - @Autowired NamedParameterJdbcTemplate template; @Autowired DummyEntityRepository repository; + private static DummyEntity createDummyEntity() { + + DummyEntity entity = new DummyEntity(); + entity.setName("Entity Name"); + return entity; + } + @Test // DATAJDBC-130 public void saveAndLoadEmptyList() { @@ -139,7 +127,7 @@ public class JdbcRepositoryWithListsIntegrationTests { } @Test // DATAJDBC-130 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 + @RequiredFeature(SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES) public void updateList() { Element element1 = createElement("one"); @@ -205,29 +193,39 @@ public class JdbcRepositoryWithListsIntegrationTests { return element; } - private static DummyEntity createDummyEntity() { - - DummyEntity entity = new DummyEntity(); - entity.setName("Entity Name"); - return entity; - } - interface DummyEntityRepository extends CrudRepository {} + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryWithListsIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + } + @Data static class DummyEntity { - @Id private Long id; String name; List content = new ArrayList<>(); + @Id private Long id; } @RequiredArgsConstructor static class Element { - @Id private Long id; String content; + @Id private Long id; } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java index 6bdca9ac..471442c2 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithMapsIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.data.jdbc.repository; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*; import junit.framework.AssertionFailedError; import lombok.Data; @@ -33,12 +34,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; -import org.springframework.data.jdbc.testing.DatabaseProfileValueSource; +import org.springframework.data.jdbc.testing.RequiredFeature; import org.springframework.data.jdbc.testing.TestConfiguration; +import org.springframework.data.jdbc.testing.TestDatabaseFeatures; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.test.annotation.IfProfileValue; -import org.springframework.test.annotation.ProfileValueSourceConfiguration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.rules.SpringClassRule; import org.springframework.test.context.junit4.rules.SpringMethodRule; @@ -51,7 +51,6 @@ import org.springframework.transaction.annotation.Transactional; * @author Thomas Lang */ @ContextConfiguration -@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class) @Transactional public class JdbcRepositoryWithMapsIntegrationTests { @@ -140,7 +139,7 @@ public class JdbcRepositoryWithMapsIntegrationTests { } @Test // DATAJDBC-131 - @IfProfileValue(name = "current.database.is.not.mssql", value = "true") // DATAJDBC-278 + @RequiredFeature(SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES) public void updateMap() { Element element1 = createElement("one"); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/AssumeFeatureRule.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/AssumeFeatureRule.java new file mode 100644 index 00000000..beea21a1 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/AssumeFeatureRule.java @@ -0,0 +1,51 @@ +/* + * Copyright 2020 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.testing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestExecutionListener; + +public class AssumeFeatureRule implements TestExecutionListener { + + @Override + public void beforeTestMethod(TestContext testContext) throws Exception { + + ApplicationContext applicationContext = testContext.getApplicationContext(); + TestDatabaseFeatures databaseFeatures = applicationContext.getBean(TestDatabaseFeatures.class); + + List requiredFeatures = new ArrayList<>(); + + RequiredFeature classAnnotation = testContext.getTestClass().getAnnotation(RequiredFeature.class); + if (classAnnotation != null) { + requiredFeatures.addAll(Arrays.asList(classAnnotation.value())); + } + + RequiredFeature methodAnnotation = testContext.getTestMethod().getAnnotation(RequiredFeature.class); + if (methodAnnotation != null) { + requiredFeatures.addAll(Arrays.asList(methodAnnotation.value())); + } + + for (TestDatabaseFeatures.Feature requiredFeature : requiredFeatures) { + requiredFeature.test(databaseFeatures); + } + + } +} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseProfileValueSource.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseProfileValueSource.java deleted file mode 100644 index 7b82f4b8..00000000 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseProfileValueSource.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2018-2020 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.testing; - -import org.springframework.test.annotation.ProfileValueSource; - -/** - * This {@link ProfileValueSource} offers a single set of keys {@code current.database.is.not.} where - * {@code } is a database as used in active profiles to enable integration tests to run with a certain - * database. The value returned for these keys is {@code "true"} or {@code "false"} depending on if the database is - * actually the one currently used by integration tests. - * - * @author Jens Schauder - */ -public class DatabaseProfileValueSource implements ProfileValueSource { - - static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active"; - static final String CURRENT_DATABASE_IS_NOT = "current.database.is.not."; - - private final String currentDatabase; - - DatabaseProfileValueSource() { - - String fromEnvironment = System.getenv(SPRING_PROFILES_ACTIVE); - currentDatabase = fromEnvironment == null ? System.getProperty(SPRING_PROFILES_ACTIVE, "hsqldb") : fromEnvironment; - } - - @Override - public String get(String key) { - - if (!key.startsWith(CURRENT_DATABASE_IS_NOT)) { - return null; - } - - return Boolean.toString(!key.endsWith(currentDatabase)).toLowerCase(); - } -} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseProfileValueSourceUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseProfileValueSourceUnitTests.java deleted file mode 100644 index 93c8213b..00000000 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DatabaseProfileValueSourceUnitTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2019-2020 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.testing; - -import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.jdbc.testing.DatabaseProfileValueSource.*; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -/** - * Unit tests for {@link DatabaseProfileValueSource}. - * - * @author Jens Schauder - */ -public class DatabaseProfileValueSourceUnitTests { - - String oldSystemPropertyValue; - - @Before - public void before() { - oldSystemPropertyValue = System.getProperty(SPRING_PROFILES_ACTIVE); - } - - @After - public void after() { - - if (oldSystemPropertyValue == null) { - System.clearProperty(SPRING_PROFILES_ACTIVE); - } else { - System.setProperty(SPRING_PROFILES_ACTIVE, oldSystemPropertyValue); - } - } - - @Test // DATAJDBC-461 - public void returnNullForUnrelatedProperty() { - - DatabaseProfileValueSource source = new DatabaseProfileValueSource(); - assertThat(source.get("blah")).isNull(); - } - - @Test // DATAJDBC-461 - public void worksWithSystemProperty() { - - System.setProperty(SPRING_PROFILES_ACTIVE, "testProfile"); - - DatabaseProfileValueSource source = new DatabaseProfileValueSource(); - - assertThat(source.get(CURRENT_DATABASE_IS_NOT + "other")).isEqualTo("true"); - assertThat(source.get(CURRENT_DATABASE_IS_NOT + "testProfile")).isEqualTo("false"); - } - -} diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java index fd06126c..3dfa0d2d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.testing; import java.sql.Connection; import java.sql.SQLException; +import java.util.concurrent.TimeUnit; import javax.sql.DataSource; diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java index d1a4c273..69e4fe3a 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java @@ -41,6 +41,7 @@ import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; @Configuration @Profile("mysql") class MySqlDataSourceConfiguration extends DataSourceConfiguration { + private static MySQLContainer MYSQL_CONTAINER; /* diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/OracleDataSourceConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/OracleDataSourceConfiguration.java index 2895533a..2325fdf4 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/OracleDataSourceConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/OracleDataSourceConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2020 the original author or authors. + * Copyright 2020 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. @@ -17,11 +17,22 @@ package org.springframework.data.jdbc.testing; import javax.sql.DataSource; +import org.awaitility.Awaitility; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.testcontainers.containers.OracleContainer; +import java.sql.Connection; +import java.sql.SQLException; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; + +import static org.awaitility.pollinterval.FibonacciPollInterval.*; + /** * {@link DataSource} setup for Oracle Database XE. Starts a docker container with a Oracle database. * @@ -36,6 +47,8 @@ import org.testcontainers.containers.OracleContainer; @Profile("oracle") public class OracleDataSourceConfiguration extends DataSourceConfiguration { + private static final Logger LOG = LoggerFactory.getLogger(OracleDataSourceConfiguration.class); + private static OracleContainer ORACLE_CONTAINER; /* @@ -47,14 +60,36 @@ public class OracleDataSourceConfiguration extends DataSourceConfiguration { if (ORACLE_CONTAINER == null) { - OracleContainer container = new OracleContainer("name_of_your_oracle_xe_image"); + OracleContainer container = new OracleContainer("springci/spring-data-oracle-xe-prebuild:18.4.0").withReuse(true); container.start(); ORACLE_CONTAINER = container; } - return new DriverManagerDataSource(ORACLE_CONTAINER.getJdbcUrl(), ORACLE_CONTAINER.getUsername(), + String jdbcUrl = ORACLE_CONTAINER.getJdbcUrl().replace(":xe", "/XEPDB1"); + + DataSource dataSource = new DriverManagerDataSource(jdbcUrl, ORACLE_CONTAINER.getUsername(), ORACLE_CONTAINER.getPassword()); + + // Oracle container says its ready but it's like with a cat that denies service and still wants food although it had + // its food. Therefore, we make sure that we can properly establish a connection instead of trusting the cat + // ...err... Oracle. + Awaitility.await() + .atMost(5L, TimeUnit.MINUTES ) + .pollInterval(fibonacci(TimeUnit.SECONDS)) + .ignoreException(SQLException.class).until(() -> { + + try (Connection connection = dataSource.getConnection()) { + return true; + } + }); + + + return dataSource; } + @Override + protected void customizePopulator(ResourceDatabasePopulator populator) { + populator.setIgnoreFailedDrops(true); + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/RequiredFeature.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/RequiredFeature.java new file mode 100644 index 00000000..903c3fe8 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/RequiredFeature.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 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.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface RequiredFeature { + TestDatabaseFeatures.Feature[] value(); +} 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 6e69246f..7e8b1ccd 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 @@ -127,4 +127,10 @@ public class TestConfiguration { Dialect jdbcDialect(NamedParameterJdbcOperations operations) { return DialectResolver.getDialect(operations.getJdbcOperations()); } + + @Lazy + @Bean + TestDatabaseFeatures features(NamedParameterJdbcOperations operations) { + return new TestDatabaseFeatures(operations.getJdbcOperations()); + } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestDatabaseFeatures.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestDatabaseFeatures.java new file mode 100644 index 00000000..54f7db72 --- /dev/null +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestDatabaseFeatures.java @@ -0,0 +1,140 @@ +/* + * Copyright 2020 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.testing; + +import static org.assertj.core.api.Assumptions.*; + +import java.util.Arrays; +import java.util.Locale; +import java.util.function.Consumer; + +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcOperations; + +/** + * This class provides information about which features a database integration supports in order to react on the + * presence or absence of features in tests. + * + * @author Jens Schauder + */ +public class TestDatabaseFeatures { + + private final Database database; + + public TestDatabaseFeatures(JdbcOperations jdbcTemplate) { + + String productName = jdbcTemplate.execute( + (ConnectionCallback) c -> c.getMetaData().getDatabaseProductName().toLowerCase(Locale.ENGLISH)); + + database = Arrays.stream(Database.values()).filter(db -> db.matches(productName)).findFirst().get(); + } + + /** + * Oracle returns an oracle.sql.TIMESTAMP which currently cannot be converted to an Instant. See DATAJDBC-569 for + * reference. + */ + private void supportsDateDataTypes() { + assumeThat(database).isNotEqualTo(Database.Oracle); + } + + /** + * Not all databases support really huge numbers as represented by {@link java.math.BigDecimal} and similar. + */ + private void supportsHugeNumbers() { + assumeThat(database).isNotIn(Database.Oracle, Database.SqlServer); + } + + /** + * Oracle does not allow to specify an alias for a joined table with {@code AS}. See DATAJDBC-570 for reference. + */ + private void supportsAsForJoinAlias() { + assumeThat(database).isNotEqualTo(Database.Oracle); + } + + /** + * Oracles JDBC driver seems to have a bug that makes it impossible to acquire generated keys when the column is + * quoted. See + * https://stackoverflow.com/questions/62263576/how-to-get-the-generated-key-for-a-column-with-lowercase-characters-from-oracle + */ + private void supportsQuotedIds() { + assumeThat(database).isNotEqualTo(Database.Oracle); + } + + /** + * Microsoft SqlServer does not allow explicitly setting ids in columns where the value gets generated by the + * database. Such columns therefore must not be used in referenced entities, since we do a delete and insert, which + * must not recreate an id. See https://jira.spring.io/browse/DATAJDBC-210 + */ + private void supportsGeneratedIdsInReferencedEntities() { + assumeThat(database).isNotEqualTo(Database.SqlServer); + } + + private void supportsArrays() { + + assumeThat(database).isNotIn(Database.MySql, Database.MariaDb, Database.SqlServer, Database.Db2, Database.Oracle); + } + + private void supportsMultiDimensionalArrays() { + + supportsArrays(); + assumeThat(database).isNotIn(Database.H2, Database.Hsql); + } + + public void databaseIs(Database database) { + assumeThat(this.database).isEqualTo(database); + } + + public enum Database { + Hsql, H2, MySql, MariaDb, PostgreSql, SqlServer("microsoft"), Db2, Oracle; + + private final String identification; + + Database(String identification) { + this.identification = identification; + } + + Database() { + this.identification = null; + } + + boolean matches(String productName) { + + String identification = this.identification == null ? name().toLowerCase() : this.identification; + return productName.contains(identification); + } + } + + public enum Feature { + + SUPPORTS_DATE_DATATYPES(TestDatabaseFeatures::supportsDateDataTypes), // + SUPPORTS_MULTIDIMENSIONAL_ARRAYS(TestDatabaseFeatures::supportsMultiDimensionalArrays), // + SUPPORTS_QUOTED_IDS(TestDatabaseFeatures::supportsQuotedIds), // + SUPPORTS_HUGE_NUMBERS(TestDatabaseFeatures::supportsHugeNumbers), // + SUPPORTS_ARRAYS(TestDatabaseFeatures::supportsArrays), // + SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES(TestDatabaseFeatures::supportsGeneratedIdsInReferencedEntities), // + IS_HSQL(f -> f.databaseIs(Database.Hsql)); + + private final Consumer featureMethod; + + Feature(Consumer featureMethod) { + this.featureMethod = featureMethod; + } + + void test(TestDatabaseFeatures features) { + featureMethod.accept(features); + } + } +} diff --git a/spring-data-jdbc/src/test/resources/logback.xml b/spring-data-jdbc/src/test/resources/logback.xml index 5288df9b..67cda4af 100644 --- a/spring-data-jdbc/src/test/resources/logback.xml +++ b/spring-data-jdbc/src/test/resources/logback.xml @@ -7,8 +7,8 @@ - - + + diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql new file mode 100644 index 00000000..65592859 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql @@ -0,0 +1,327 @@ +DROP TABLE MANUAL CASCADE CONSTRAINTS PURGE; +DROP TABLE LEGO_SET CASCADE CONSTRAINTS PURGE; +DROP TABLE CHILD_NO_ID CASCADE CONSTRAINTS PURGE; +DROP TABLE ONE_TO_ONE_PARENT CASCADE CONSTRAINTS PURGE; +DROP TABLE ELEMENT_NO_ID CASCADE CONSTRAINTS PURGE; +DROP TABLE LIST_PARENT CASCADE CONSTRAINTS PURGE; +DROP TABLE BYTE_ARRAY_OWNER CASCADE CONSTRAINTS PURGE; +DROP TABLE CHAIN0 CASCADE CONSTRAINTS PURGE; +DROP TABLE CHAIN1 CASCADE CONSTRAINTS PURGE; +DROP TABLE CHAIN2 CASCADE CONSTRAINTS PURGE; +DROP TABLE CHAIN3 CASCADE CONSTRAINTS PURGE; +DROP TABLE CHAIN4 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_CHAIN0 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_CHAIN1 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_CHAIN2 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_CHAIN3 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_CHAIN4 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_LIST_CHAIN0 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_LIST_CHAIN1 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_LIST_CHAIN2 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_LIST_CHAIN3 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_LIST_CHAIN4 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_MAP_CHAIN0 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_MAP_CHAIN1 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_MAP_CHAIN2 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_MAP_CHAIN3 CASCADE CONSTRAINTS PURGE; +DROP TABLE NO_ID_MAP_CHAIN4 CASCADE CONSTRAINTS PURGE; +DROP TABLE VERSIONED_AGGREGATE CASCADE CONSTRAINTS PURGE; +DROP TABLE WITH_READ_ONLY CASCADE CONSTRAINTS PURGE; + +CREATE TABLE LEGO_SET +( + "id1" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR(30) +); +CREATE TABLE MANUAL +( + "id2" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + LEGO_SET NUMBER, + ALTERNATIVE NUMBER, + CONTENT VARCHAR(2000) +); + +ALTER TABLE MANUAL + ADD FOREIGN KEY (LEGO_SET) + REFERENCES LEGO_SET ("id1"); + +CREATE TABLE ONE_TO_ONE_PARENT +( + "id3" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + content VARCHAR(30) +); +CREATE TABLE Child_No_Id +( + ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, + "content" VARCHAR(30) +); + +CREATE TABLE LIST_PARENT +( + "id4" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR(100) +); +CREATE TABLE element_no_id +( + CONTENT VARCHAR(100), + LIST_PARENT_key NUMBER, + LIST_PARENT NUMBER +); + +CREATE TABLE BYTE_ARRAY_OWNER +( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + BINARY_DATA RAW(100) NOT NULL +); + +CREATE TABLE CHAIN4 +( + FOUR NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + FOUR_VALUE VARCHAR(20) +); + + +CREATE TABLE CHAIN3 +( + THREE NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + THREE_VALUE VARCHAR(20), + CHAIN4 NUMBER, + FOREIGN KEY (CHAIN4) REFERENCES CHAIN4 (FOUR) +); + +CREATE TABLE CHAIN2 +( + TWO NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + TWO_VALUE VARCHAR(20), + CHAIN3 NUMBER, + FOREIGN KEY (CHAIN3) REFERENCES CHAIN3 (THREE) +); + +CREATE TABLE CHAIN1 +( + ONE NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + ONE_VALUE VARCHAR(20), + CHAIN2 NUMBER, + FOREIGN KEY (CHAIN2) REFERENCES CHAIN2 (TWO) +); + +CREATE TABLE CHAIN0 +( + ZERO NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + ZERO_VALUE VARCHAR(20), + CHAIN1 NUMBER, + FOREIGN KEY (CHAIN1) REFERENCES CHAIN1 (ONE) +); + +CREATE TABLE NO_ID_CHAIN4 +( + FOUR NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + FOUR_VALUE VARCHAR(20) +); + +CREATE TABLE NO_ID_CHAIN3 +( + THREE_VALUE VARCHAR(20), + NO_ID_CHAIN4 NUMBER, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) +); + +CREATE TABLE NO_ID_CHAIN2 +( + TWO_VALUE VARCHAR(20), + NO_ID_CHAIN4 NUMBER, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) +); + +CREATE TABLE NO_ID_CHAIN1 +( + ONE_VALUE VARCHAR(20), + NO_ID_CHAIN4 NUMBER, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) +); + +CREATE TABLE NO_ID_CHAIN0 +( + ZERO_VALUE VARCHAR(20), + NO_ID_CHAIN4 NUMBER, + FOREIGN KEY (NO_ID_CHAIN4) REFERENCES NO_ID_CHAIN4 (FOUR) +); + +CREATE TABLE NO_ID_LIST_CHAIN4 +( + FOUR NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + FOUR_VALUE VARCHAR(20) +); + +CREATE TABLE NO_ID_LIST_CHAIN3 +( + THREE_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 NUMBER, + NO_ID_LIST_CHAIN4_KEY NUMBER, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY), + FOREIGN KEY (NO_ID_LIST_CHAIN4) REFERENCES NO_ID_LIST_CHAIN4 (FOUR) +); + +CREATE TABLE NO_ID_LIST_CHAIN2 +( + TWO_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 NUMBER, + NO_ID_LIST_CHAIN4_KEY NUMBER, + NO_ID_LIST_CHAIN3_KEY NUMBER, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY), + FOREIGN KEY ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY + ) REFERENCES NO_ID_LIST_CHAIN3 ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY + ) +); + +CREATE TABLE NO_ID_LIST_CHAIN1 +( + ONE_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 NUMBER, + NO_ID_LIST_CHAIN4_KEY NUMBER, + NO_ID_LIST_CHAIN3_KEY NUMBER, + NO_ID_LIST_CHAIN2_KEY NUMBER, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY), + FOREIGN KEY ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY + ) REFERENCES NO_ID_LIST_CHAIN2 ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY + ) +); + +CREATE TABLE NO_ID_LIST_CHAIN0 +( + ZERO_VALUE VARCHAR(20), + NO_ID_LIST_CHAIN4 NUMBER, + NO_ID_LIST_CHAIN4_KEY NUMBER, + NO_ID_LIST_CHAIN3_KEY NUMBER, + NO_ID_LIST_CHAIN2_KEY NUMBER, + NO_ID_LIST_CHAIN1_KEY NUMBER, + PRIMARY KEY (NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY, + NO_ID_LIST_CHAIN1_KEY), + FOREIGN KEY ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY + ) REFERENCES NO_ID_LIST_CHAIN1 ( + NO_ID_LIST_CHAIN4, + NO_ID_LIST_CHAIN4_KEY, + NO_ID_LIST_CHAIN3_KEY, + NO_ID_LIST_CHAIN2_KEY + ) +); + + + +CREATE TABLE NO_ID_MAP_CHAIN4 +( + FOUR NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + FOUR_VALUE VARCHAR(20) +); + +CREATE TABLE NO_ID_MAP_CHAIN3 +( + THREE_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 NUMBER, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY), + FOREIGN KEY (NO_ID_MAP_CHAIN4) REFERENCES NO_ID_MAP_CHAIN4 (FOUR) +); + +CREATE TABLE NO_ID_MAP_CHAIN2 +( + TWO_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 NUMBER, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + NO_ID_MAP_CHAIN3_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY), + FOREIGN KEY ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY + ) REFERENCES NO_ID_MAP_CHAIN3 ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY + ) +); + +CREATE TABLE NO_ID_MAP_CHAIN1 +( + ONE_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 NUMBER, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + NO_ID_MAP_CHAIN3_KEY VARCHAR(20), + NO_ID_MAP_CHAIN2_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY), + FOREIGN KEY ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY + ) REFERENCES NO_ID_MAP_CHAIN2 ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY + ) +); + +CREATE TABLE NO_ID_MAP_CHAIN0 +( + ZERO_VALUE VARCHAR(20), + NO_ID_MAP_CHAIN4 NUMBER, + NO_ID_MAP_CHAIN4_KEY VARCHAR(20), + NO_ID_MAP_CHAIN3_KEY VARCHAR(20), + NO_ID_MAP_CHAIN2_KEY VARCHAR(20), + NO_ID_MAP_CHAIN1_KEY VARCHAR(20), + PRIMARY KEY (NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY, + NO_ID_MAP_CHAIN1_KEY), + FOREIGN KEY ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY + ) REFERENCES NO_ID_MAP_CHAIN1 ( + NO_ID_MAP_CHAIN4, + NO_ID_MAP_CHAIN4_KEY, + NO_ID_MAP_CHAIN3_KEY, + NO_ID_MAP_CHAIN2_KEY + ) +); + +CREATE TABLE VERSIONED_AGGREGATE +( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + VERSION NUMBER +); + +CREATE TABLE WITH_READ_ONLY +( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR(200), + READ_ONLY VARCHAR(200) DEFAULT 'from-db' +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateSchemaIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateSchemaIntegrationTests-oracle.sql new file mode 100644 index 00000000..b93ef418 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateSchemaIntegrationTests-oracle.sql @@ -0,0 +1,18 @@ +DROP USER OTHER CASCADE; + + +CREATE USER OTHER; + +CREATE TABLE OTHER.DUMMY_ENTITY +( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR2(30) +); + + +CREATE TABLE OTHER.REFERENCED +( + DUMMY_ENTITY INTEGER, + NAME VARCHAR2(30) +); + diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcRepositoriesIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcRepositoriesIntegrationTests-oracle.sql new file mode 100644 index 00000000..24f9f775 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository.config/EnableJdbcRepositoriesIntegrationTests-oracle.sql @@ -0,0 +1,3 @@ +DROP TABLE DUMMY_ENTITY; + +CREATE TABLE DUMMY_ENTITY ( id NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY); \ No newline at end of file diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryConcurrencyIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryConcurrencyIntegrationTests-oracle.sql new file mode 100644 index 00000000..e6d9bfb0 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryConcurrencyIntegrationTests-oracle.sql @@ -0,0 +1,14 @@ +DROP TABLE DUMMY_ENTITY CASCADE CONSTRAINTS PURGE; +DROP TABLE ELEMENT CASCADE CONSTRAINTS PURGE; + +CREATE TABLE DUMMY_ENTITY ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + NAME VARCHAR2(100) +); + +CREATE TABLE ELEMENT ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + CONTENT NUMBER, + DUMMY_ENTITY_KEY NUMBER , + DUMMY_ENTITY NUMBER +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-oracle.sql new file mode 100644 index 00000000..1b02ef72 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryCustomConversionIntegrationTests-oracle.sql @@ -0,0 +1,14 @@ +DROP TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL CASCADE CONSTRAINTS PURGE; +DROP TABLE OTHER_ENTITY CASCADE CONSTRAINTS PURGE; + +CREATE TABLE ENTITY_WITH_STRINGY_BIG_DECIMAL ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + STRINGY_NUMBER DECIMAL(20,10) +); + +CREATE TABLE OTHER_ENTITY ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + CREATED DATE, + ENTITY_WITH_STRINGY_BIG_DECIMAL INTEGER +); + diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-oracle.sql new file mode 100644 index 00000000..1a27e7c6 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedImmutableIntegrationTests-oracle.sql @@ -0,0 +1,7 @@ +DROP TABLE DUMMY_ENTITY CASCADE CONSTRAINTS PURGE; + +CREATE TABLE DUMMY_ENTITY ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + PREFIX_ATTR1 NUMBER, + PREFIX_ATTR2 VARCHAR2(100) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-oracle.sql new file mode 100644 index 00000000..4ad71309 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedIntegrationTests-oracle.sql @@ -0,0 +1,9 @@ +DROP TABLE DUMMY_ENTITY CASCADE CONSTRAINTS PURGE; + +CREATE TABLE DUMMY_ENTITY ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + TEST VARCHAR2(100), + PREFIX2_ATTR NUMBER , + PREFIX_TEST VARCHAR2(100), + PREFIX_PREFIX2_ATTR NUMBER +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-oracle.sql new file mode 100644 index 00000000..0ab81989 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedNotInAggregateRootIntegrationTests-oracle.sql @@ -0,0 +1,12 @@ +DROP TABLE DUMMY_ENTITY CASCADE CONSTRAINTS PURGE; +DROP TABLE DUMMY_ENTITY2 CASCADE CONSTRAINTS PURGE; + +CREATE TABLE DUMMY_ENTITY ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + TEST VARCHAR2(100) +); +CREATE TABLE DUMMY_ENTITY2 ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + TEST VARCHAR2(100), + PREFIX_ATTR NUMBER +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-oracle.sql new file mode 100644 index 00000000..66f369fc --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithCollectionIntegrationTests-oracle.sql @@ -0,0 +1,17 @@ +DROP TABLE DUMMY_ENTITY2 CASCADE CONSTRAINTS PURGE; +DROP TABLE DUMMY_ENTITY CASCADE CONSTRAINTS PURGE; + +CREATE TABLE DUMMY_ENTITY +( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + TEST VARCHAR2(100), + PREFIX_TEST VARCHAR2(100) +); + +CREATE TABLE DUMMY_ENTITY2 +( + ID NUMBER, + ORDER_KEY NUMBER, + TEST VARCHAR2(100), + PRIMARY KEY (ID, ORDER_KEY) +) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-oracle.sql new file mode 100644 index 00000000..40437415 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests-oracle.sql @@ -0,0 +1,16 @@ +DROP TABLE DUMMY_ENTITY2 CASCADE CONSTRAINTS PURGE; +DROP TABLE DUMMY_ENTITY CASCADE CONSTRAINTS PURGE; + + + +CREATE TABLE dummy_entity +( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + TEST VARCHAR2(100), + PREFIX_TEST VARCHAR2(100) +); +CREATE TABLE dummy_entity2 +( + ID NUMBER , + TEST VARCHAR2(100) +) diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-oracle.sql new file mode 100644 index 00000000..772fdfc1 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-oracle.sql @@ -0,0 +1,18 @@ +DROP TABLE ReadOnlyIdEntity; +DROP TABLE PrimitiveIdEntity; +DROP TABLE ImmutableWithManualIdentity; + +CREATE TABLE ReadOnlyIdEntity ( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR2(100) +); + +CREATE TABLE PrimitiveIdEntity ( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR2(100) +); + +CREATE TABLE ImmutableWithManualIdentity ( + ID NUMBER PRIMARY KEY, + NAME VARCHAR2(100) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql new file mode 100644 index 00000000..28b2d80e --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql @@ -0,0 +1,8 @@ +DROP TABLE DUMMY_ENTITY CASCADE CONSTRAINTS PURGE; + +CREATE TABLE DUMMY_ENTITY +( + ID_PROP NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + NAME VARCHAR2(100), + POINT_IN_TIME TIMESTAMP +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-db2.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-db2.sql index 9ddb7fe3..dba16a76 100644 --- a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-db2.sql +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-db2.sql @@ -2,7 +2,8 @@ DROP TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS; CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS ( id_Timestamp DATETIME NOT NULL PRIMARY KEY, - bool boolean, SOME_ENUM VARCHAR(100), + bool boolean, + SOME_ENUM VARCHAR(100), big_Decimal VARCHAR(100), big_Integer BIGINT, date DATETIME, diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-oracle.sql new file mode 100644 index 00000000..bfa63a53 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryPropertyConversionIntegrationTests-oracle.sql @@ -0,0 +1,12 @@ +DROP TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS; + +CREATE TABLE ENTITY_WITH_COLUMNS_REQUIRING_CONVERSIONS ( + ID_TIMESTAMP TIMESTAMP PRIMARY KEY, + BOOL CHAR(1), + SOME_ENUM VARCHAR2(100), + BIG_DECIMAL DECIMAL (38), + BIG_INTEGER NUMBER(38, 0), + "DATE" TIMESTAMP, + LOCAL_DATE_TIME TIMESTAMP, + ZONED_DATE_TIME VARCHAR2(30) +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-oracle.sql new file mode 100644 index 00000000..ab6fb458 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryResultSetExtractorIntegrationTests-oracle.sql @@ -0,0 +1,14 @@ +DROP TABLE ADDRESS; +DROP TABLE PERSON; + +CREATE TABLE PERSON ( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR2(100) +); + +CREATE TABLE ADDRESS ( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + STREET VARCHAR2(100), + PERSON_ID NUMBER); + +ALTER TABLE ADDRESS ADD FOREIGN KEY (PERSON_ID) REFERENCES PERSON(ID); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithCollectionsIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithCollectionsIntegrationTests-oracle.sql new file mode 100644 index 00000000..52dbc96a --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithCollectionsIntegrationTests-oracle.sql @@ -0,0 +1,13 @@ +DROP TABLE ELEMENT; +DROP TABLE DUMMY_ENTITY; + +CREATE TABLE DUMMY_ENTITY ( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR2(100) +); + +CREATE TABLE ELEMENT ( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + CONTENT VARCHAR(100), + DUMMY_ENTITY NUMBER +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-oracle.sql new file mode 100644 index 00000000..47b21124 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithListsIntegrationTests-oracle.sql @@ -0,0 +1,14 @@ +DROP TABLE ELEMENT; +DROP TABLE DUMMY_ENTITY; + +CREATE TABLE DUMMY_ENTITY ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + NAME VARCHAR2(100) +); + +CREATE TABLE ELEMENT ( + ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, + CONTENT VARCHAR(100), + DUMMY_ENTITY_KEY NUMBER, + DUMMY_ENTITY NUMBER +); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithMapsIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithMapsIntegrationTests-oracle.sql new file mode 100644 index 00000000..6fb0e811 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithMapsIntegrationTests-oracle.sql @@ -0,0 +1,17 @@ +DROP TABLE ELEMENT; +DROP TABLE DUMMY_ENTITY; + +CREATE TABLE DUMMY_ENTITY ( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + NAME VARCHAR2(100) +); + +CREATE TABLE ELEMENT ( + ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, + CONTENT VARCHAR2(100), + DUMMY_ENTITY_KEY VARCHAR2(100), + DUMMY_ENTITY NUMBER ); + +ALTER TABLE ELEMENT + ADD FOREIGN KEY (DUMMY_ENTITY) + REFERENCES DUMMY_ENTITY(ID); diff --git a/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/StringBasedJdbcQueryMappingConfigurationIntegrationTests-oracle.sql b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/StringBasedJdbcQueryMappingConfigurationIntegrationTests-oracle.sql new file mode 100644 index 00000000..18c251e1 --- /dev/null +++ b/spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/StringBasedJdbcQueryMappingConfigurationIntegrationTests-oracle.sql @@ -0,0 +1,2 @@ +DROP TABLE CAR; +CREATE TABLE CAR ( id NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, model VARCHAR(100)); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java index aa1bad1a..e9182841 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java @@ -81,4 +81,8 @@ public interface Dialect { default Escaper getLikeEscaper() { return Escaper.DEFAULT; } + + default IdGeneration getIdGeneration(){ + return IdGeneration.DEFAULT; + }; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/IdGeneration.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/IdGeneration.java new file mode 100644 index 00000000..19285ff3 --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/IdGeneration.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020 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.relational.core.dialect; + +import java.sql.Connection; + +/** + * Describes the how obtaining generated ids after an insert works for a given JDBC driver. + * + * @author Jens Schauder + * @since 2.1 + */ +public interface IdGeneration { + /** + * A default instance working for many databases and equivalent to Spring Data JDBCs behavior before version 2.1. + */ + IdGeneration DEFAULT = new IdGeneration() {}; + + /** + * Does the driver require the specification of those columns for which a generated id shall be returned. + *

+ * This should be {@literal false} for most dialects. One notable exception is Oracle. + * + * @return {@literal true} if the a list of column names should get passed to the JDBC driver for which ids shall be + * generated. + * + * @see Connection#prepareStatement(String, String[])? + */ + default boolean driverRequiresKeyColumnNames() { + return false; + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java new file mode 100644 index 00000000..d1f23c3f --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019-2020 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.relational.core.dialect; + +import java.util.List; + +import org.springframework.data.relational.core.sql.IdentifierProcessing; +import org.springframework.data.relational.core.sql.LockOptions; +import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing; +import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * An SQL dialect for Oracle. + * + * @author Jens Schauder + * @since 2.1 + */ +public class OracleDialect extends AnsiDialect { + + /** + * Singleton instance. + */ + public static final OracleDialect INSTANCE = new OracleDialect(); + + private static final IdGeneration ID_GENERATION = new IdGeneration() { + @Override + public boolean driverRequiresKeyColumnNames() { + return true; + } + }; + + protected OracleDialect() {} + + @Override + public IdGeneration getIdGeneration() { + return ID_GENERATION; + } +}