|
|
|
@ -15,13 +15,14 @@ |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
package org.springframework.data.jdbc.repository; |
|
|
|
package org.springframework.data.jdbc.repository; |
|
|
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.*; |
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
|
|
|
|
|
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Objects; |
|
|
|
|
|
|
|
import java.util.concurrent.atomic.AtomicLong; |
|
|
|
import java.util.concurrent.atomic.AtomicLong; |
|
|
|
|
|
|
|
import java.util.concurrent.atomic.AtomicReference; |
|
|
|
|
|
|
|
|
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
|
|
|
|
import org.mockito.Mockito; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.ComponentScan; |
|
|
|
import org.springframework.context.annotation.ComponentScan; |
|
|
|
@ -29,31 +30,53 @@ import org.springframework.context.annotation.Configuration; |
|
|
|
import org.springframework.context.annotation.FilterType; |
|
|
|
import org.springframework.context.annotation.FilterType; |
|
|
|
import org.springframework.context.annotation.Import; |
|
|
|
import org.springframework.context.annotation.Import; |
|
|
|
import org.springframework.data.annotation.Id; |
|
|
|
import org.springframework.data.annotation.Id; |
|
|
|
|
|
|
|
import org.springframework.data.annotation.PersistenceCreator; |
|
|
|
|
|
|
|
import org.springframework.data.annotation.Transient; |
|
|
|
|
|
|
|
import org.springframework.data.domain.Persistable; |
|
|
|
|
|
|
|
import org.springframework.data.jdbc.core.mapping.IdGeneratingBeforeSaveCallback; |
|
|
|
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; |
|
|
|
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; |
|
|
|
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; |
|
|
|
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; |
|
|
|
import org.springframework.data.jdbc.testing.IntegrationTest; |
|
|
|
import org.springframework.data.jdbc.testing.IntegrationTest; |
|
|
|
import org.springframework.data.jdbc.testing.TestConfiguration; |
|
|
|
import org.springframework.data.jdbc.testing.TestConfiguration; |
|
|
|
|
|
|
|
import org.springframework.data.relational.core.conversion.MutableAggregateChange; |
|
|
|
import org.springframework.data.relational.core.mapping.NamingStrategy; |
|
|
|
import org.springframework.data.relational.core.mapping.NamingStrategy; |
|
|
|
|
|
|
|
import org.springframework.data.relational.core.mapping.Sequence; |
|
|
|
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; |
|
|
|
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; |
|
|
|
import org.springframework.data.repository.CrudRepository; |
|
|
|
import org.springframework.data.repository.CrudRepository; |
|
|
|
import org.springframework.data.repository.ListCrudRepository; |
|
|
|
import org.springframework.data.repository.ListCrudRepository; |
|
|
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; |
|
|
|
import org.springframework.test.context.jdbc.Sql; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Testing special cases for id generation with {@link SimpleJdbcRepository}. |
|
|
|
* Testing special cases for id generation with {@link SimpleJdbcRepository}. |
|
|
|
* |
|
|
|
* |
|
|
|
* @author Jens Schauder |
|
|
|
* @author Jens Schauder |
|
|
|
* @author Greg Turnquist |
|
|
|
* @author Greg Turnquist |
|
|
|
|
|
|
|
* @author Mikhail Polivakha |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@IntegrationTest |
|
|
|
@IntegrationTest |
|
|
|
class JdbcRepositoryIdGenerationIntegrationTests { |
|
|
|
class JdbcRepositoryIdGenerationIntegrationTests { |
|
|
|
|
|
|
|
|
|
|
|
@Autowired NamedParameterJdbcTemplate template; |
|
|
|
@Autowired |
|
|
|
@Autowired ReadOnlyIdEntityRepository readOnlyIdRepository; |
|
|
|
ReadOnlyIdEntityRepository readOnlyIdRepository; |
|
|
|
@Autowired PrimitiveIdEntityRepository primitiveIdRepository; |
|
|
|
@Autowired |
|
|
|
@Autowired ImmutableWithManualIdEntityRepository immutableWithManualIdEntityRepository; |
|
|
|
PrimitiveIdEntityRepository primitiveIdRepository; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
ImmutableWithManualIdEntityRepository immutableWithManualIdEntityRepository; |
|
|
|
|
|
|
|
|
|
|
|
@Test // DATAJDBC-98
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
SimpleSeqRepository simpleSeqRepository; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
PersistableSeqRepository persistableSeqRepository; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
PrimitiveIdSeqRepository primitiveIdSeqRepository; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
IdGeneratingBeforeSaveCallback idGeneratingCallback; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
// DATAJDBC-98
|
|
|
|
void idWithoutSetterGetsSet() { |
|
|
|
void idWithoutSetterGetsSet() { |
|
|
|
|
|
|
|
|
|
|
|
ReadOnlyIdEntity entity = readOnlyIdRepository.save(new ReadOnlyIdEntity(null, "Entity Name")); |
|
|
|
ReadOnlyIdEntity entity = readOnlyIdRepository.save(new ReadOnlyIdEntity(null, "Entity Name")); |
|
|
|
@ -67,7 +90,8 @@ class JdbcRepositoryIdGenerationIntegrationTests { |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test // DATAJDBC-98
|
|
|
|
@Test |
|
|
|
|
|
|
|
// DATAJDBC-98
|
|
|
|
void primitiveIdGetsSet() { |
|
|
|
void primitiveIdGetsSet() { |
|
|
|
|
|
|
|
|
|
|
|
PrimitiveIdEntity entity = new PrimitiveIdEntity(); |
|
|
|
PrimitiveIdEntity entity = new PrimitiveIdEntity(); |
|
|
|
@ -84,7 +108,8 @@ class JdbcRepositoryIdGenerationIntegrationTests { |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test // DATAJDBC-393
|
|
|
|
@Test |
|
|
|
|
|
|
|
// DATAJDBC-393
|
|
|
|
void manuallyGeneratedId() { |
|
|
|
void manuallyGeneratedId() { |
|
|
|
|
|
|
|
|
|
|
|
ImmutableWithManualIdEntity entity = new ImmutableWithManualIdEntity(null, "immutable"); |
|
|
|
ImmutableWithManualIdEntity entity = new ImmutableWithManualIdEntity(null, "immutable"); |
|
|
|
@ -95,7 +120,8 @@ class JdbcRepositoryIdGenerationIntegrationTests { |
|
|
|
assertThat(immutableWithManualIdEntityRepository.findAll()).hasSize(1); |
|
|
|
assertThat(immutableWithManualIdEntityRepository.findAll()).hasSize(1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test // DATAJDBC-393
|
|
|
|
@Test |
|
|
|
|
|
|
|
// DATAJDBC-393
|
|
|
|
void manuallyGeneratedIdForSaveAll() { |
|
|
|
void manuallyGeneratedIdForSaveAll() { |
|
|
|
|
|
|
|
|
|
|
|
ImmutableWithManualIdEntity one = new ImmutableWithManualIdEntity(null, "one"); |
|
|
|
ImmutableWithManualIdEntity one = new ImmutableWithManualIdEntity(null, "one"); |
|
|
|
@ -107,18 +133,146 @@ class JdbcRepositoryIdGenerationIntegrationTests { |
|
|
|
assertThat(immutableWithManualIdEntityRepository.findAll()).hasSize(2); |
|
|
|
assertThat(immutableWithManualIdEntityRepository.findAll()).hasSize(2); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private interface PrimitiveIdEntityRepository extends ListCrudRepository<PrimitiveIdEntity, Long> {} |
|
|
|
@Test // DATAJDBC-2003
|
|
|
|
|
|
|
|
@Sql(statements = "INSERT INTO SimpleSeq(id, name) VALUES(1, 'initial value');") |
|
|
|
|
|
|
|
void testUpdateAggregateWithSequence() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SimpleSeq entity = new SimpleSeq(); |
|
|
|
|
|
|
|
entity.id = 1L; |
|
|
|
|
|
|
|
entity.name = "New name"; |
|
|
|
|
|
|
|
AtomicReference<SimpleSeq> afterCallback = mockIdGeneratingCallback(entity); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SimpleSeq updated = simpleSeqRepository.save(entity); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assertThat(updated.id).isEqualTo(1L); |
|
|
|
|
|
|
|
assertThat(afterCallback.get()).isSameAs(entity); |
|
|
|
|
|
|
|
assertThat(afterCallback.get().id).isEqualTo(1L); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
// DATAJDBC-2003
|
|
|
|
|
|
|
|
void testInsertPersistableAggregateWithSequenceClientIdIsFavored() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
long initialId = 1L; |
|
|
|
|
|
|
|
PersistableSeq entityWithSeq = PersistableSeq.createNew(initialId, "name"); |
|
|
|
|
|
|
|
AtomicReference<PersistableSeq> afterCallback = mockIdGeneratingCallback(entityWithSeq); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
PersistableSeq saved = persistableSeqRepository.save(entityWithSeq); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We do not expect the SELECT next value from sequence in case we're doing an INSERT with ID provided by the client
|
|
|
|
|
|
|
|
assertThat(saved.getId()).isEqualTo(initialId); |
|
|
|
|
|
|
|
assertThat(afterCallback.get()).isSameAs(entityWithSeq); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
// DATAJDBC-2003
|
|
|
|
|
|
|
|
void testInsertAggregateWithSequenceAndUnsetPrimitiveId() { |
|
|
|
|
|
|
|
|
|
|
|
private interface ReadOnlyIdEntityRepository extends ListCrudRepository<ReadOnlyIdEntity, Long> {} |
|
|
|
PrimitiveIdSeq entity = new PrimitiveIdSeq(); |
|
|
|
|
|
|
|
entity.name = "some name"; |
|
|
|
|
|
|
|
AtomicReference<PrimitiveIdSeq> afterCallback = mockIdGeneratingCallback(entity); |
|
|
|
|
|
|
|
|
|
|
|
private interface ImmutableWithManualIdEntityRepository extends ListCrudRepository<ImmutableWithManualIdEntity, Long> {} |
|
|
|
PrimitiveIdSeq saved = primitiveIdSeqRepository.save(entity); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Select from sequence
|
|
|
|
|
|
|
|
// 2. Actual INSERT
|
|
|
|
|
|
|
|
assertThat(afterCallback.get().id).isEqualTo(1L); |
|
|
|
|
|
|
|
assertThat(saved.id).isEqualTo(1L); // sequence starts with 1
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
|
|
|
|
private <T> AtomicReference<T> mockIdGeneratingCallback(T entity) { |
|
|
|
|
|
|
|
AtomicReference<T> afterCallback = new AtomicReference<>(); |
|
|
|
|
|
|
|
Mockito |
|
|
|
|
|
|
|
.doAnswer(invocationOnMock -> { |
|
|
|
|
|
|
|
afterCallback.set((T) invocationOnMock.callRealMethod()); |
|
|
|
|
|
|
|
return afterCallback.get(); |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.when(idGeneratingCallback) |
|
|
|
|
|
|
|
.onBeforeSave(Mockito.eq(entity), Mockito.any(MutableAggregateChange.class)); |
|
|
|
|
|
|
|
return afterCallback; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface PrimitiveIdEntityRepository extends ListCrudRepository<PrimitiveIdEntity, Long> { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface ReadOnlyIdEntityRepository extends ListCrudRepository<ReadOnlyIdEntity, Long> { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface ImmutableWithManualIdEntityRepository extends ListCrudRepository<ImmutableWithManualIdEntity, Long> { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface SimpleSeqRepository extends ListCrudRepository<SimpleSeq, Long> { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface PersistableSeqRepository extends ListCrudRepository<PersistableSeq, Long> { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface PrimitiveIdSeqRepository extends ListCrudRepository<PrimitiveIdSeq, Long> { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
record ReadOnlyIdEntity(@Id Long id, String name) { |
|
|
|
record ReadOnlyIdEntity(@Id Long id, String name) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static class SimpleSeq { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Id |
|
|
|
|
|
|
|
@Sequence(value = "simple_seq_seq") |
|
|
|
|
|
|
|
private Long id; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String name; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static class PersistableSeq implements Persistable<Long> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Id |
|
|
|
|
|
|
|
@Sequence(value = "persistable_seq_seq") |
|
|
|
|
|
|
|
private Long id; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String name; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Transient |
|
|
|
|
|
|
|
private boolean isNew; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@PersistenceCreator |
|
|
|
|
|
|
|
public PersistableSeq() { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public PersistableSeq(Long id, String name, boolean isNew) { |
|
|
|
|
|
|
|
this.id = id; |
|
|
|
|
|
|
|
this.name = name; |
|
|
|
|
|
|
|
this.isNew = isNew; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static PersistableSeq createNew(Long id, String name) { |
|
|
|
|
|
|
|
return new PersistableSeq(id, name, true); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public Long getId() { |
|
|
|
|
|
|
|
return id; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public boolean isNew() { |
|
|
|
|
|
|
|
return isNew; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static class PrimitiveIdSeq { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Id |
|
|
|
|
|
|
|
@Sequence(value = "primitive_seq_seq") |
|
|
|
|
|
|
|
private long id; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String name; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static class PrimitiveIdEntity { |
|
|
|
static class PrimitiveIdEntity { |
|
|
|
|
|
|
|
|
|
|
|
@Id private long id; |
|
|
|
@Id |
|
|
|
|
|
|
|
private long id; |
|
|
|
String name; |
|
|
|
String name; |
|
|
|
|
|
|
|
|
|
|
|
public long getId() { |
|
|
|
public long getId() { |
|
|
|
@ -142,17 +296,17 @@ class JdbcRepositoryIdGenerationIntegrationTests { |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public Long id() { |
|
|
|
public Long id() { |
|
|
|
return this.id; |
|
|
|
return this.id; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public ImmutableWithManualIdEntity withId(Long id) { |
|
|
|
public ImmutableWithManualIdEntity withId(Long id) { |
|
|
|
return this.id == id ? this : new ImmutableWithManualIdEntity(id, this.name); |
|
|
|
return this.id == id ? this : new ImmutableWithManualIdEntity(id, this.name); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public ImmutableWithManualIdEntity withName(String name) { |
|
|
|
public ImmutableWithManualIdEntity withName(String name) { |
|
|
|
return this.name == name ? this : new ImmutableWithManualIdEntity(this.id, name); |
|
|
|
return this.name == name ? this : new ImmutableWithManualIdEntity(this.id, name); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Configuration |
|
|
|
@Configuration |
|
|
|
@EnableJdbcRepositories(considerNestedRepositories = true, |
|
|
|
@EnableJdbcRepositories(considerNestedRepositories = true, |
|
|
|
|