Support for ID generation by sequence.
Ids can be annotated with @Sequence to specify a sequence to pull id values from. Closes #1923 Original pull request #1955 Signed-off-by: mipo256 <mikhailpolivakha@gmail.com> Some accidential changes removed. Signed-off-by: schauder <jens.schauder@broadcom.com>
This commit is contained in:
committed by
Jens Schauder
parent
b51c77b50a
commit
d1c996008c
@@ -118,8 +118,13 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
Assert.notEmpty(insertSubjects, "Batch insert must contain at least one InsertSubject");
|
||||
SqlIdentifierParameterSource[] sqlParameterSources = insertSubjects.stream()
|
||||
.map(insertSubject -> sqlParametersFactory.forInsert(insertSubject.getInstance(), domainType,
|
||||
insertSubject.getIdentifier(), idValueSource))
|
||||
.map(insertSubject -> sqlParametersFactory.forInsert( //
|
||||
insertSubject.getInstance(), //
|
||||
domainType, //
|
||||
insertSubject.getIdentifier(), //
|
||||
idValueSource //
|
||||
) //
|
||||
) //
|
||||
.toArray(SqlIdentifierParameterSource[]::new);
|
||||
|
||||
String insertSql = sql(domainType).getInsert(sqlParameterSources[0].getIdentifiers());
|
||||
@@ -280,7 +285,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
@Override
|
||||
public <T> Stream<T> streamAll(Class<T> domainType) {
|
||||
return operations.queryForStream(sql(domainType).getFindAll(), new MapSqlParameterSource(), getEntityRowMapper(domainType));
|
||||
return operations.queryForStream(sql(domainType).getFindAll(), new MapSqlParameterSource(),
|
||||
getEntityRowMapper(domainType));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -364,7 +370,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
@Override
|
||||
public <T> Stream<T> streamAll(Class<T> domainType, Sort sort) {
|
||||
return operations.queryForStream(sql(domainType).getFindAll(sort), new MapSqlParameterSource(), getEntityRowMapper(domainType));
|
||||
return operations.queryForStream(sql(domainType).getFindAll(sort), new MapSqlParameterSource(),
|
||||
getEntityRowMapper(domainType));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -479,5 +486,4 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
return baseProperty.getOwner().getType();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.springframework.data.jdbc.core.mapping;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Callback for generating ID via the database sequence. By default, it is registered as a
|
||||
* bean in {@link AbstractJdbcConfiguration}
|
||||
*
|
||||
* @author Mikhail Polivakha
|
||||
*/
|
||||
public class IdGeneratingBeforeSaveCallback implements BeforeSaveCallback<Object> {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(IdGeneratingBeforeSaveCallback.class);
|
||||
|
||||
private final RelationalMappingContext relationalMappingContext;
|
||||
private final Dialect dialect;
|
||||
private final NamedParameterJdbcOperations operations;
|
||||
|
||||
public IdGeneratingBeforeSaveCallback(
|
||||
RelationalMappingContext relationalMappingContext,
|
||||
Dialect dialect,
|
||||
NamedParameterJdbcOperations namedParameterJdbcOperations
|
||||
) {
|
||||
this.relationalMappingContext = relationalMappingContext;
|
||||
this.dialect = dialect;
|
||||
this.operations = namedParameterJdbcOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onBeforeSave(Object aggregate, MutableAggregateChange<Object> aggregateChange) {
|
||||
Assert.notNull(aggregate, "The aggregate cannot be null at this point");
|
||||
RelationalPersistentEntity<?> persistentEntity = relationalMappingContext.getPersistentEntity(aggregate.getClass());
|
||||
Optional<String> idTargetSequence = persistentEntity.getIdTargetSequence();
|
||||
|
||||
if (dialect.getIdGeneration().sequencesSupported()) {
|
||||
|
||||
if (persistentEntity.getIdProperty() != null) {
|
||||
idTargetSequence
|
||||
.map(s -> dialect.getIdGeneration().nextValueFromSequenceSelect(s))
|
||||
.ifPresent(sql -> {
|
||||
Long idValue = operations.queryForObject(sql, Map.of(), (rs, rowNum) -> rs.getLong(1));
|
||||
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(aggregate);
|
||||
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), idValue);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (idTargetSequence.isPresent()) {
|
||||
LOG.warn("""
|
||||
It seems you're trying to insert an aggregate of type '%s' annotated with @TargetSequence, but the problem is RDBMS you're
|
||||
working with does not support sequences as such. Falling back to identity columns
|
||||
"""
|
||||
.formatted(aggregate.getClass().getName())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return aggregate;
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import org.springframework.data.jdbc.core.JdbcAggregateOperations;
|
||||
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
|
||||
import org.springframework.data.jdbc.core.convert.*;
|
||||
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
|
||||
import org.springframework.data.jdbc.core.mapping.IdGeneratingBeforeSaveCallback;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
@@ -119,6 +120,22 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
|
||||
return mappingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link IdGeneratingBeforeSaveCallback} bean using the configured
|
||||
* {@link #jdbcMappingContext(Optional, JdbcCustomConversions, RelationalManagedTypes)} and
|
||||
* {@link #jdbcDialect(NamedParameterJdbcOperations)}.
|
||||
*
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public IdGeneratingBeforeSaveCallback idGeneratingBeforeSaveCallback(
|
||||
JdbcMappingContext mappingContext,
|
||||
NamedParameterJdbcOperations operations,
|
||||
Dialect dialect
|
||||
) {
|
||||
return new IdGeneratingBeforeSaveCallback(mappingContext, dialect, operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RelationalConverter} using the configured
|
||||
* {@link #jdbcMappingContext(Optional, JdbcCustomConversions, RelationalManagedTypes)}.
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.relational.core.conversion.IdValueSource;
|
||||
import org.springframework.data.relational.core.dialect.AnsiDialect;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
@@ -49,7 +48,6 @@ class SqlParametersFactoryTest {
|
||||
RelationalMappingContext context = new JdbcMappingContext();
|
||||
RelationResolver relationResolver = mock(RelationResolver.class);
|
||||
MappingJdbcConverter converter = new MappingJdbcConverter(context, relationResolver);
|
||||
AnsiDialect dialect = AnsiDialect.INSTANCE;
|
||||
SqlParametersFactory sqlParametersFactory = new SqlParametersFactory(context, converter);
|
||||
|
||||
@Test // DATAJDBC-412
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.springframework.data.jdbc.core.mapping;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
|
||||
import org.springframework.data.relational.core.dialect.MySqlDialect;
|
||||
import org.springframework.data.relational.core.dialect.PostgresDialect;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.data.relational.core.mapping.TargetSequence;
|
||||
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link IdGeneratingBeforeSaveCallback}
|
||||
*
|
||||
* @author Mikhail Polivakha
|
||||
*/
|
||||
class IdGeneratingBeforeSaveCallbackTest {
|
||||
|
||||
@Test
|
||||
void test_mySqlDialect_sequenceGenerationIsNotSupported() {
|
||||
// given
|
||||
RelationalMappingContext relationalMappingContext = new RelationalMappingContext();
|
||||
MySqlDialect mySqlDialect = new MySqlDialect(IdentifierProcessing.NONE);
|
||||
NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
|
||||
|
||||
// and
|
||||
IdGeneratingBeforeSaveCallback subject = new IdGeneratingBeforeSaveCallback(relationalMappingContext, mySqlDialect, operations);
|
||||
|
||||
NoSequenceEntity entity = new NoSequenceEntity();
|
||||
|
||||
// when
|
||||
Object processed = subject.onBeforeSave(entity, MutableAggregateChange.forSave(entity));
|
||||
|
||||
// then
|
||||
Assertions.assertThat(processed).isSameAs(entity);
|
||||
Assertions.assertThat(processed).usingRecursiveComparison().isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_EntityIsNotMarkedWithTargetSequence() {
|
||||
// given
|
||||
RelationalMappingContext relationalMappingContext = new RelationalMappingContext();
|
||||
PostgresDialect mySqlDialect = PostgresDialect.INSTANCE;
|
||||
NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
|
||||
|
||||
// and
|
||||
IdGeneratingBeforeSaveCallback subject = new IdGeneratingBeforeSaveCallback(relationalMappingContext, mySqlDialect, operations);
|
||||
|
||||
NoSequenceEntity entity = new NoSequenceEntity();
|
||||
|
||||
// when
|
||||
Object processed = subject.onBeforeSave(entity, MutableAggregateChange.forSave(entity));
|
||||
|
||||
// then
|
||||
Assertions.assertThat(processed).isSameAs(entity);
|
||||
Assertions.assertThat(processed).usingRecursiveComparison().isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_EntityIdIsPopulatedFromSequence() {
|
||||
// given
|
||||
RelationalMappingContext relationalMappingContext = new RelationalMappingContext();
|
||||
relationalMappingContext.getRequiredPersistentEntity(EntityWithSequence.class);
|
||||
|
||||
PostgresDialect mySqlDialect = PostgresDialect.INSTANCE;
|
||||
NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
|
||||
|
||||
// and
|
||||
long generatedId = 112L;
|
||||
when(operations.queryForObject(anyString(), anyMap(), any(RowMapper.class))).thenReturn(generatedId);
|
||||
|
||||
// and
|
||||
IdGeneratingBeforeSaveCallback subject = new IdGeneratingBeforeSaveCallback(relationalMappingContext, mySqlDialect, operations);
|
||||
|
||||
EntityWithSequence entity = new EntityWithSequence();
|
||||
|
||||
// when
|
||||
Object processed = subject.onBeforeSave(entity, MutableAggregateChange.forSave(entity));
|
||||
|
||||
// then
|
||||
Assertions.assertThat(processed).isSameAs(entity);
|
||||
Assertions
|
||||
.assertThat(processed)
|
||||
.usingRecursiveComparison()
|
||||
.ignoringFields("id")
|
||||
.isEqualTo(entity);
|
||||
Assertions.assertThat(entity.getId()).isEqualTo(generatedId);
|
||||
}
|
||||
|
||||
@Table
|
||||
static class NoSequenceEntity {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
private Long name;
|
||||
}
|
||||
|
||||
@Table
|
||||
static class EntityWithSequence {
|
||||
|
||||
@Id
|
||||
@TargetSequence(value = "id_seq", schema = "public")
|
||||
private Long id;
|
||||
|
||||
private Long name;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.PropertiesFactoryBean;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
@@ -52,16 +51,7 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.data.domain.Limit;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.domain.*;
|
||||
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
||||
import org.springframework.data.jdbc.repository.query.Modifying;
|
||||
import org.springframework.data.jdbc.repository.query.Query;
|
||||
@@ -75,6 +65,7 @@ import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.MappedCollection;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.data.relational.core.mapping.TargetSequence;
|
||||
import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent;
|
||||
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
@@ -115,8 +106,8 @@ public class JdbcRepositoryIntegrationTests {
|
||||
@Autowired DummyEntityRepository repository;
|
||||
@Autowired MyEventListener eventListener;
|
||||
@Autowired RootRepository rootRepository;
|
||||
|
||||
@Autowired WithDelimitedColumnRepository withDelimitedColumnRepository;
|
||||
@Autowired EntityWithSequenceRepository entityWithSequenceRepository;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
@@ -135,6 +126,28 @@ public class JdbcRepositoryIntegrationTests {
|
||||
"id_Prop = " + entity.getIdProp())).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnFeature(value = TestDatabaseFeatures.Feature.SUPPORTS_SEQUENCES)
|
||||
public void saveEntityWithTargetSequenceSpecified() {
|
||||
EntityWithSequence first = entityWithSequenceRepository.save(new EntityWithSequence("first"));
|
||||
EntityWithSequence second = entityWithSequenceRepository.save(new EntityWithSequence("second"));
|
||||
|
||||
assertThat(first.getId()).isNotNull();
|
||||
assertThat(second.getId()).isNotNull();
|
||||
assertThat(first.getId()).isLessThan(second.getId());
|
||||
assertThat(first.getName()).isEqualTo("first");
|
||||
assertThat(second.getName()).isEqualTo("second");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnFeature(value = TestDatabaseFeatures.Feature.SUPPORTS_SEQUENCES)
|
||||
public void batchInsertEntityWithTargetSequenceSpecified() {
|
||||
Iterable<EntityWithSequence> results = entityWithSequenceRepository
|
||||
.saveAll(List.of(new EntityWithSequence("first"), new EntityWithSequence("second")));
|
||||
|
||||
assertThat(results).hasSize(2).extracting(EntityWithSequence::getId).containsExactly(1L, 2L);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-95
|
||||
public void saveAndLoadAnEntity() {
|
||||
|
||||
@@ -1515,6 +1528,8 @@ public class JdbcRepositoryIntegrationTests {
|
||||
|
||||
interface WithDelimitedColumnRepository extends CrudRepository<WithDelimitedColumn, Long> {}
|
||||
|
||||
interface EntityWithSequenceRepository extends CrudRepository<EntityWithSequence, Long> {}
|
||||
|
||||
@Configuration
|
||||
@Import(TestConfiguration.class)
|
||||
static class Config {
|
||||
@@ -1536,6 +1551,11 @@ public class JdbcRepositoryIntegrationTests {
|
||||
return factory.getRepository(WithDelimitedColumnRepository.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
EntityWithSequenceRepository entityWithSequenceRepository() {
|
||||
return factory.getRepository(EntityWithSequenceRepository.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
NamedQueries namedQueries() throws IOException {
|
||||
|
||||
@@ -1839,6 +1859,31 @@ public class JdbcRepositoryIntegrationTests {
|
||||
return entity;
|
||||
}
|
||||
|
||||
static class EntityWithSequence {
|
||||
|
||||
@Id
|
||||
@TargetSequence(sequence = "entity_sequence") private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
public EntityWithSequence(Long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public EntityWithSequence(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
static class DummyEntity {
|
||||
|
||||
String name;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.data.jdbc.testing;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.test.context.junit.jupiter.EnabledIf;
|
||||
|
||||
/**
|
||||
* Annotation that allows to disable a particular test to be executed on a particular database
|
||||
*
|
||||
* @author Mikhail Polivakha
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@ExtendWith(DisabledOnDatabaseExecutionCondition.class)
|
||||
public @interface DisabledOnDatabase {
|
||||
|
||||
/**
|
||||
* The database on which the test is not supposed to run on
|
||||
*/
|
||||
DatabaseType database();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.springframework.data.jdbc.testing;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
|
||||
import org.junit.jupiter.api.extension.ExecutionCondition;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
/**
|
||||
* {@link ExecutionCondition} for the {@link DisabledOnDatabase} annotation
|
||||
*
|
||||
* @author Mikhail Polivakha
|
||||
*/
|
||||
public class DisabledOnDatabaseExecutionCondition implements ExecutionCondition {
|
||||
|
||||
@Override
|
||||
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
|
||||
ApplicationContext applicationContext = SpringExtension.getApplicationContext(context);
|
||||
|
||||
MergedAnnotation<DisabledOnDatabase> disabledOnDatabaseMergedAnnotation = MergedAnnotations
|
||||
.from(context.getRequiredTestMethod(), MergedAnnotations.SearchStrategy.DIRECT)
|
||||
.get(DisabledOnDatabase.class);
|
||||
|
||||
DatabaseType database = disabledOnDatabaseMergedAnnotation.getEnum("database", DatabaseType.class);
|
||||
|
||||
if (ArrayUtils.contains(applicationContext.getEnvironment().getActiveProfiles(), database.getProfile())) {
|
||||
return ConditionEvaluationResult.disabled(
|
||||
"The test method '%s' is disabled for '%s' because of the @DisabledOnDatabase annotation".formatted(context.getRequiredTestMethod().getName(), database)
|
||||
);
|
||||
}
|
||||
return ConditionEvaluationResult.enabled("The test method '%s' is enabled".formatted(context.getRequiredTestMethod()));
|
||||
}
|
||||
}
|
||||
@@ -36,11 +36,15 @@ import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.jdbc.core.convert.*;
|
||||
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
|
||||
import org.springframework.data.jdbc.core.mapping.IdGeneratingBeforeSaveCallback;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
|
||||
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
|
||||
import org.springframework.data.jdbc.repository.config.DialectResolver;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.relational.RelationalManagedTypes;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.mapping.DefaultNamingStrategy;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
@@ -81,10 +85,16 @@ public class TestConfiguration {
|
||||
JdbcRepositoryFactory jdbcRepositoryFactory(
|
||||
@Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
|
||||
Dialect dialect, JdbcConverter converter, Optional<List<NamedQueries>> namedQueries,
|
||||
List<EntityCallback<?>> callbacks,
|
||||
List<EvaluationContextExtension> evaulationContextExtensions) {
|
||||
|
||||
JdbcRepositoryFactory factory = new JdbcRepositoryFactory(dataAccessStrategy, context, converter, dialect,
|
||||
publisher, namedParameterJdbcTemplate());
|
||||
|
||||
factory.setEntityCallbacks(
|
||||
EntityCallbacks.create(callbacks.toArray(new EntityCallback[0]))
|
||||
);
|
||||
|
||||
namedQueries.map(it -> it.iterator().next()).ifPresent(factory::setNamedQueries);
|
||||
|
||||
factory.setEvaluationContextProvider(
|
||||
@@ -164,6 +174,21 @@ public class TestConfiguration {
|
||||
new DefaultJdbcTypeFactory(template.getJdbcOperations(), arrayColumns));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link IdGeneratingBeforeSaveCallback} bean using the configured
|
||||
* {@link #jdbcDialect(NamedParameterJdbcOperations)}.
|
||||
*
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public IdGeneratingBeforeSaveCallback idGeneratingBeforeSaveCallback(
|
||||
JdbcMappingContext mappingContext,
|
||||
NamedParameterJdbcOperations operations,
|
||||
Dialect dialect
|
||||
) {
|
||||
return new IdGeneratingBeforeSaveCallback(mappingContext, dialect, operations);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
|
||||
return DialectResolver.getDialect(operations.getJdbcOperations());
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.jdbc.core.JdbcOperations;
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Chirag Tailor
|
||||
* @author Mikhail Polivakha
|
||||
*/
|
||||
public class TestDatabaseFeatures {
|
||||
|
||||
@@ -79,6 +80,10 @@ public class TestDatabaseFeatures {
|
||||
assumeThat(database).isNotIn(Database.MySql, Database.MariaDb, Database.SqlServer);
|
||||
}
|
||||
|
||||
private void supportsSequences() {
|
||||
assumeThat(database).isNotIn(Database.MySql);
|
||||
}
|
||||
|
||||
private void supportsWhereInTuples() {
|
||||
assumeThat(database).isIn(Database.MySql, Database.PostgreSql);
|
||||
}
|
||||
@@ -117,6 +122,7 @@ public class TestDatabaseFeatures {
|
||||
SUPPORTS_NULL_PRECEDENCE(TestDatabaseFeatures::supportsNullPrecedence),
|
||||
IS_POSTGRES(f -> f.databaseIs(Database.PostgreSql)), //
|
||||
WHERE_IN_TUPLE(TestDatabaseFeatures::supportsWhereInTuples), //
|
||||
SUPPORTS_SEQUENCES(TestDatabaseFeatures::supportsSequences), //
|
||||
IS_HSQL(f -> f.databaseIs(Database.Hsql));
|
||||
|
||||
private final Consumer<TestDatabaseFeatures> featureMethod;
|
||||
|
||||
@@ -3,6 +3,8 @@ DROP TABLE ROOT;
|
||||
DROP TABLE INTERMEDIATE;
|
||||
DROP TABLE LEAF;
|
||||
DROP TABLE WITH_DELIMITED_COLUMN;
|
||||
DROP TABLE ENTITY_WITH_SEQUENCE;
|
||||
DROP SEQUENCE ENTITY_SEQUENCE;
|
||||
|
||||
CREATE TABLE dummy_entity
|
||||
(
|
||||
@@ -45,4 +47,12 @@ CREATE TABLE WITH_DELIMITED_COLUMN
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
"ORG.XTUNIT.IDENTIFIER" VARCHAR(100),
|
||||
STYPE VARCHAR(100)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE ENTITY_WITH_SEQUENCE
|
||||
(
|
||||
ID BIGINT,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
|
||||
@@ -39,4 +39,12 @@ CREATE TABLE WITH_DELIMITED_COLUMN
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
"ORG.XTUNIT.IDENTIFIER" VARCHAR(100),
|
||||
STYPE VARCHAR(100)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE ENTITY_WITH_SEQUENCE
|
||||
(
|
||||
ID BIGINT,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
|
||||
@@ -39,4 +39,12 @@ CREATE TABLE WITH_DELIMITED_COLUMN
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
"ORG.XTUNIT.IDENTIFIER" VARCHAR(100),
|
||||
STYPE VARCHAR(100)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE ENTITY_WITH_SEQUENCE
|
||||
(
|
||||
ID BIGINT,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
|
||||
@@ -39,4 +39,12 @@ CREATE TABLE WITH_DELIMITED_COLUMN
|
||||
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
`ORG.XTUNIT.IDENTIFIER` VARCHAR(100),
|
||||
STYPE VARCHAR(100)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE ENTITY_WITH_SEQUENCE
|
||||
(
|
||||
ID BIGINT,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
|
||||
@@ -3,6 +3,8 @@ DROP TABLE IF EXISTS ROOT;
|
||||
DROP TABLE IF EXISTS INTERMEDIATE;
|
||||
DROP TABLE IF EXISTS LEAF;
|
||||
DROP TABLE IF EXISTS WITH_DELIMITED_COLUMN;
|
||||
DROP TABLE IF EXISTS ENTITY_WITH_SEQUENCE;
|
||||
DROP SEQUENCE IF EXISTS ENTITY_SEQUENCE;
|
||||
|
||||
CREATE TABLE dummy_entity
|
||||
(
|
||||
@@ -45,4 +47,12 @@ CREATE TABLE WITH_DELIMITED_COLUMN
|
||||
ID BIGINT IDENTITY PRIMARY KEY,
|
||||
"ORG.XTUNIT.IDENTIFIER" VARCHAR(100),
|
||||
STYPE VARCHAR(100)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE ENTITY_WITH_SEQUENCE
|
||||
(
|
||||
ID BIGINT,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
|
||||
@@ -3,6 +3,8 @@ DROP TABLE ROOT CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE INTERMEDIATE CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE LEAF CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE WITH_DELIMITED_COLUMN CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE ENTITY_WITH_SEQUENCE CASCADE CONSTRAINTS PURGE;
|
||||
DROP SEQUENCE ENTITY_SEQUENCE;
|
||||
|
||||
CREATE TABLE DUMMY_ENTITY
|
||||
(
|
||||
@@ -46,3 +48,11 @@ CREATE TABLE WITH_DELIMITED_COLUMN
|
||||
"ORG.XTUNIT.IDENTIFIER" VARCHAR(100),
|
||||
STYPE VARCHAR(100)
|
||||
)
|
||||
|
||||
CREATE TABLE ENTITY_WITH_SEQUENCE
|
||||
(
|
||||
ID BIGINT,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
|
||||
@@ -3,6 +3,8 @@ DROP TABLE ROOT;
|
||||
DROP TABLE INTERMEDIATE;
|
||||
DROP TABLE LEAF;
|
||||
DROP TABLE WITH_DELIMITED_COLUMN;
|
||||
DROP TABLE ENTITY_WITH_SEQUENCE;
|
||||
DROP SEQUENCE ENTITY_SEQUENCE;
|
||||
|
||||
CREATE TABLE dummy_entity
|
||||
(
|
||||
@@ -45,4 +47,12 @@ CREATE TABLE "WITH_DELIMITED_COLUMN"
|
||||
ID SERIAL PRIMARY KEY,
|
||||
"ORG.XTUNIT.IDENTIFIER" VARCHAR(100),
|
||||
"STYPE" VARCHAR(100)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE TABLE ENTITY_WITH_SEQUENCE
|
||||
(
|
||||
ID BIGINT,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE;
|
||||
Reference in New Issue
Block a user