@ -15,13 +15,14 @@
@@ -15,13 +15,14 @@
* /
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.Objects ;
import java.util.concurrent.atomic.AtomicLong ;
import java.util.concurrent.atomic.AtomicReference ;
import org.junit.jupiter.api.Test ;
import org.mockito.Mockito ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.context.annotation.Bean ;
import org.springframework.context.annotation.ComponentScan ;
@ -29,31 +30,53 @@ import org.springframework.context.annotation.Configuration;
@@ -29,31 +30,53 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType ;
import org.springframework.context.annotation.Import ;
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.support.SimpleJdbcRepository ;
import org.springframework.data.jdbc.testing.IntegrationTest ;
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.Sequence ;
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback ;
import org.springframework.data.repository.CrudRepository ;
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 } .
*
* @author Jens Schauder
* @author Greg Turnquist
* @author Mikhail Polivakha
* /
@IntegrationTest
class JdbcRepositoryIdGenerationIntegrationTests {
@Autowired NamedParameterJdbcTemplate template ;
@Autowired ReadOnlyIdEntityRepository readOnlyIdRepository ;
@Autowired PrimitiveIdEntityRepository primitiveIdRepository ;
@Autowired ImmutableWithManualIdEntityRepository immutableWithManualIdEntityRepository ;
@Autowired
ReadOnlyIdEntityRepository readOnlyIdRepository ;
@Autowired
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 ( ) {
ReadOnlyIdEntity entity = readOnlyIdRepository . save ( new ReadOnlyIdEntity ( null , "Entity Name" ) ) ;
@ -67,7 +90,8 @@ class JdbcRepositoryIdGenerationIntegrationTests {
@@ -67,7 +90,8 @@ class JdbcRepositoryIdGenerationIntegrationTests {
} ) ;
}
@Test // DATAJDBC-98
@Test
// DATAJDBC-98
void primitiveIdGetsSet ( ) {
PrimitiveIdEntity entity = new PrimitiveIdEntity ( ) ;
@ -84,7 +108,8 @@ class JdbcRepositoryIdGenerationIntegrationTests {
@@ -84,7 +108,8 @@ class JdbcRepositoryIdGenerationIntegrationTests {
} ) ;
}
@Test // DATAJDBC-393
@Test
// DATAJDBC-393
void manuallyGeneratedId ( ) {
ImmutableWithManualIdEntity entity = new ImmutableWithManualIdEntity ( null , "immutable" ) ;
@ -95,7 +120,8 @@ class JdbcRepositoryIdGenerationIntegrationTests {
@@ -95,7 +120,8 @@ class JdbcRepositoryIdGenerationIntegrationTests {
assertThat ( immutableWithManualIdEntityRepository . findAll ( ) ) . hasSize ( 1 ) ;
}
@Test // DATAJDBC-393
@Test
// DATAJDBC-393
void manuallyGeneratedIdForSaveAll ( ) {
ImmutableWithManualIdEntity one = new ImmutableWithManualIdEntity ( null , "one" ) ;
@ -107,18 +133,146 @@ class JdbcRepositoryIdGenerationIntegrationTests {
@@ -107,18 +133,146 @@ class JdbcRepositoryIdGenerationIntegrationTests {
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 ) {
}
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 {
@Id private long id ;
@Id
private long id ;
String name ;
public long getId ( ) {
@ -142,17 +296,17 @@ class JdbcRepositoryIdGenerationIntegrationTests {
@@ -142,17 +296,17 @@ class JdbcRepositoryIdGenerationIntegrationTests {
@Override
public Long id ( ) {
return this . id ;
}
return this . id ;
}
public ImmutableWithManualIdEntity withId ( Long id ) {
return this . id = = id ? this : new ImmutableWithManualIdEntity ( id , this . name ) ;
}
public ImmutableWithManualIdEntity withId ( Long id ) {
return this . id = = id ? this : new ImmutableWithManualIdEntity ( id , this . name ) ;
}
public ImmutableWithManualIdEntity withName ( String name ) {
return this . name = = name ? this : new ImmutableWithManualIdEntity ( this . id , name ) ;
}
public ImmutableWithManualIdEntity withName ( String name ) {
return this . name = = name ? this : new ImmutableWithManualIdEntity ( this . id , name ) ;
}
}
@Configuration
@EnableJdbcRepositories ( considerNestedRepositories = true ,