Skip to content

Commit 3e5ab63

Browse files
committed
Fix Auditing for embedded fields.
The reason for auditing to not work on embedded fields is that EmbeddedRelationalPersistentProperty and BasicRelationalPersisntentProperty were not considered equal even when they represent the same field. Note: the fix is somewhat hackish since it breaks the equals contract for EmbeddedRelationalPersistentProperty. Closes #1694 See spring-projects/spring-data-mongodb@1545e18
1 parent dd1c7be commit 3e5ab63

File tree

3 files changed

+223
-72
lines changed

3 files changed

+223
-72
lines changed

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcAuditingHsqlIntegrationTests.java

+171-70
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.springframework.data.jdbc.testing.IntegrationTest;
4545
import org.springframework.data.jdbc.testing.TestClass;
4646
import org.springframework.data.jdbc.testing.TestConfiguration;
47+
import org.springframework.data.relational.core.mapping.Embedded;
4748
import org.springframework.data.relational.core.mapping.NamingStrategy;
4849
import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
4950
import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
@@ -68,50 +69,50 @@ public void auditForAnnotatedEntity() {
6869
AuditingAnnotatedDummyEntityRepository.class, //
6970
Config.class, //
7071
AuditingConfiguration.class) //
71-
.accept(repository -> {
72+
.accept(repository -> {
7273

73-
AuditingConfiguration.currentAuditor = "user01";
74-
LocalDateTime now = LocalDateTime.now();
74+
AuditingConfiguration.currentAuditor = "user01";
75+
LocalDateTime now = LocalDateTime.now();
7576

76-
AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity());
77+
AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity());
7778

78-
assertThat(entity.id).as("id not null").isNotNull();
79-
assertThat(entity.getCreatedBy()).as("created by set").isEqualTo("user01");
80-
assertThat(entity.getCreatedDate()).as("created date set").isAfter(now);
81-
assertThat(entity.getLastModifiedBy()).as("modified by set").isEqualTo("user01");
82-
assertThat(entity.getLastModifiedDate()).as("modified date set")
83-
.isAfterOrEqualTo(entity.getCreatedDate());
84-
assertThat(entity.getLastModifiedDate()).as("modified date after instance creation").isAfter(now);
79+
assertThat(entity.id).as("id not null").isNotNull();
80+
assertThat(entity.getCreatedBy()).as("created by set").isEqualTo("user01");
81+
assertThat(entity.getCreatedDate()).as("created date set").isAfter(now);
82+
assertThat(entity.getLastModifiedBy()).as("modified by set").isEqualTo("user01");
83+
assertThat(entity.getLastModifiedDate()).as("modified date set")
84+
.isAfterOrEqualTo(entity.getCreatedDate());
85+
assertThat(entity.getLastModifiedDate()).as("modified date after instance creation").isAfter(now);
8586

86-
AuditingAnnotatedDummyEntity reloaded = repository.findById(entity.id).get();
87+
AuditingAnnotatedDummyEntity reloaded = repository.findById(entity.id).get();
8788

88-
assertThat(reloaded.getCreatedBy()).as("reload created by").isNotNull();
89-
assertThat(reloaded.getCreatedDate()).as("reload created date").isNotNull();
90-
assertThat(reloaded.getLastModifiedBy()).as("reload modified by").isNotNull();
91-
assertThat(reloaded.getLastModifiedDate()).as("reload modified date").isNotNull();
89+
assertThat(reloaded.getCreatedBy()).as("reload created by").isNotNull();
90+
assertThat(reloaded.getCreatedDate()).as("reload created date").isNotNull();
91+
assertThat(reloaded.getLastModifiedBy()).as("reload modified by").isNotNull();
92+
assertThat(reloaded.getLastModifiedDate()).as("reload modified date").isNotNull();
9293

93-
LocalDateTime beforeCreatedDate = entity.getCreatedDate();
94-
LocalDateTime beforeLastModifiedDate = entity.getLastModifiedDate();
94+
LocalDateTime beforeCreatedDate = entity.getCreatedDate();
95+
LocalDateTime beforeLastModifiedDate = entity.getLastModifiedDate();
9596

96-
sleepMillis(10);
97+
sleepMillis(10);
9798

98-
AuditingConfiguration.currentAuditor = "user02";
99+
AuditingConfiguration.currentAuditor = "user02";
99100

100-
entity = repository.save(entity);
101+
entity = repository.save(entity);
101102

102-
assertThat(entity.getCreatedBy()).as("created by unchanged").isEqualTo("user01");
103-
assertThat(entity.getCreatedDate()).as("created date unchanged").isEqualTo(beforeCreatedDate);
104-
assertThat(entity.getLastModifiedBy()).as("modified by updated").isEqualTo("user02");
105-
assertThat(entity.getLastModifiedDate()).as("modified date updated")
106-
.isAfter(beforeLastModifiedDate);
103+
assertThat(entity.getCreatedBy()).as("created by unchanged").isEqualTo("user01");
104+
assertThat(entity.getCreatedDate()).as("created date unchanged").isEqualTo(beforeCreatedDate);
105+
assertThat(entity.getLastModifiedBy()).as("modified by updated").isEqualTo("user02");
106+
assertThat(entity.getLastModifiedDate()).as("modified date updated")
107+
.isAfter(beforeLastModifiedDate);
107108

108-
reloaded = repository.findById(entity.id).get();
109+
reloaded = repository.findById(entity.id).get();
109110

110-
assertThat(reloaded.getCreatedBy()).as("2. reload created by").isNotNull();
111-
assertThat(reloaded.getCreatedDate()).as("2. reload created date").isNotNull();
112-
assertThat(reloaded.getLastModifiedBy()).as("2. reload modified by").isNotNull();
113-
assertThat(reloaded.getLastModifiedDate()).as("2. reload modified date").isNotNull();
114-
});
111+
assertThat(reloaded.getCreatedBy()).as("2. reload created by").isNotNull();
112+
assertThat(reloaded.getCreatedDate()).as("2. reload created date").isNotNull();
113+
assertThat(reloaded.getLastModifiedBy()).as("2. reload modified by").isNotNull();
114+
assertThat(reloaded.getLastModifiedDate()).as("2. reload modified date").isNotNull();
115+
});
115116
}
116117

117118
@Test // DATAJDBC-204
@@ -121,17 +122,17 @@ public void noAnnotatedEntity() {
121122
DummyEntityRepository.class, //
122123
Config.class, //
123124
AuditingConfiguration.class) //
124-
.accept(repository -> {
125+
.accept(repository -> {
125126

126-
DummyEntity entity = repository.save(new DummyEntity());
127+
DummyEntity entity = repository.save(new DummyEntity());
127128

128-
assertThat(entity.id).isNotNull();
129-
assertThat(repository.findById(entity.id).get()).isEqualTo(entity);
129+
assertThat(entity.id).isNotNull();
130+
assertThat(repository.findById(entity.id).get()).isEqualTo(entity);
130131

131-
entity = repository.save(entity);
132+
entity = repository.save(entity);
132133

133-
assertThat(repository.findById(entity.id)).contains(entity);
134-
});
134+
assertThat(repository.findById(entity.id)).contains(entity);
135+
});
135136
}
136137

137138
@Test // DATAJDBC-204
@@ -141,19 +142,19 @@ public void customDateTimeProvider() {
141142
AuditingAnnotatedDummyEntityRepository.class, //
142143
Config.class, //
143144
CustomizeAuditorAwareAndDateTimeProvider.class) //
144-
.accept(repository -> {
145+
.accept(repository -> {
145146

146-
LocalDateTime currentDateTime = LocalDate.of(2018, 4, 14).atStartOfDay();
147-
CustomizeAuditorAwareAndDateTimeProvider.currentDateTime = currentDateTime;
147+
LocalDateTime currentDateTime = LocalDate.of(2018, 4, 14).atStartOfDay();
148+
CustomizeAuditorAwareAndDateTimeProvider.currentDateTime = currentDateTime;
148149

149-
AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity());
150+
AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity());
150151

151-
assertThat(entity.id).isNotNull();
152-
assertThat(entity.getCreatedBy()).isEqualTo("custom user");
153-
assertThat(entity.getCreatedDate()).isEqualTo(currentDateTime);
154-
assertThat(entity.getLastModifiedBy()).isNull();
155-
assertThat(entity.getLastModifiedDate()).isNull();
156-
});
152+
assertThat(entity.id).isNotNull();
153+
assertThat(entity.getCreatedBy()).isEqualTo("custom user");
154+
assertThat(entity.getCreatedDate()).isEqualTo(currentDateTime);
155+
assertThat(entity.getLastModifiedBy()).isNull();
156+
assertThat(entity.getLastModifiedDate()).isNull();
157+
});
157158
}
158159

159160
@Test // DATAJDBC-204
@@ -163,16 +164,16 @@ public void customAuditorAware() {
163164
AuditingAnnotatedDummyEntityRepository.class, //
164165
Config.class, //
165166
CustomizeAuditorAware.class) //
166-
.accept(repository -> {
167+
.accept(repository -> {
167168

168-
AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity());
169+
AuditingAnnotatedDummyEntity entity = repository.save(new AuditingAnnotatedDummyEntity());
169170

170-
assertThat(entity.id).isNotNull();
171-
assertThat(entity.getCreatedBy()).isEqualTo("user");
172-
assertThat(entity.getCreatedDate()).isNull();
173-
assertThat(entity.getLastModifiedBy()).isEqualTo("user");
174-
assertThat(entity.getLastModifiedDate()).isNull();
175-
});
171+
assertThat(entity.id).isNotNull();
172+
assertThat(entity.getCreatedBy()).isEqualTo("user");
173+
assertThat(entity.getCreatedDate()).isNull();
174+
assertThat(entity.getLastModifiedBy()).isEqualTo("user");
175+
assertThat(entity.getLastModifiedDate()).isNull();
176+
});
176177
}
177178

178179
@Test // DATAJDBC-390
@@ -193,21 +194,97 @@ public void auditingListenerTriggersBeforeDefaultListener() {
193194
});
194195
}
195196

197+
198+
@Test // DATAJDBC-1694
199+
public void auditEmbeddedRecord() {
200+
201+
configureRepositoryWith( //
202+
DummyEntityWithEmbeddedRecordRepository.class, //
203+
Config.class, //
204+
AuditingConfiguration.class) //
205+
.accept(repository -> {
206+
207+
AuditingConfiguration.currentAuditor = "user01";
208+
LocalDateTime now = LocalDateTime.now();
209+
210+
DummyEntityWithEmbeddedRecord entity = repository.save(new DummyEntityWithEmbeddedRecord(null, new EmbeddedAuditing(null, null, null, null)));
211+
212+
assertThat(entity.id).as("id not null").isNotNull();
213+
assertThat(entity.auditing.createdBy).as("created by set").isEqualTo("user01");
214+
assertThat(entity.auditing.createdDate()).as("created date set").isAfter(now);
215+
assertThat(entity.auditing.lastModifiedBy()).as("modified by set").isEqualTo("user01");
216+
assertThat(entity.auditing.lastModifiedDate()).as("modified date set")
217+
.isAfterOrEqualTo(entity.auditing.createdDate());
218+
assertThat(entity.auditing.lastModifiedDate()).as("modified date after instance creation").isAfter(now);
219+
220+
DummyEntityWithEmbeddedRecord reloaded = repository.findById(entity.id).get();
221+
222+
assertThat(reloaded.auditing.createdBy()).as("reload created by").isNotNull();
223+
assertThat(reloaded.auditing.createdDate()).as("reload created date").isNotNull();
224+
assertThat(reloaded.auditing.lastModifiedBy()).as("reload modified by").isNotNull();
225+
assertThat(reloaded.auditing.lastModifiedDate()).as("reload modified date").isNotNull();
226+
227+
LocalDateTime beforeCreatedDate = entity.auditing().createdDate;
228+
LocalDateTime beforeLastModifiedDate = entity.auditing().lastModifiedDate;
229+
230+
sleepMillis(10);
231+
232+
AuditingConfiguration.currentAuditor = "user02";
233+
234+
entity = repository.save(entity);
235+
236+
assertThat(entity.auditing.createdBy()).as("created by unchanged").isEqualTo("user01");
237+
assertThat(entity.auditing.createdDate()).as("created date unchanged").isEqualTo(beforeCreatedDate);
238+
assertThat(entity.auditing.lastModifiedBy()).as("modified by updated").isEqualTo("user02");
239+
assertThat(entity.auditing.lastModifiedDate()).as("modified date updated")
240+
.isAfter(beforeLastModifiedDate);
241+
242+
reloaded = repository.findById(entity.id).get();
243+
244+
assertThat(reloaded.auditing.createdBy()).as("2. reload created by").isNotNull();
245+
assertThat(reloaded.auditing.createdDate()).as("2. reload created date").isNotNull();
246+
assertThat(reloaded.auditing.lastModifiedBy()).as("2. reload modified by").isNotNull();
247+
assertThat(reloaded.auditing.lastModifiedDate()).as("2. reload modified date").isNotNull();
248+
});
249+
}
250+
@Test // DATAJDBC-1694
251+
public void auditEmbeddedNullRecordStaysNull() {
252+
253+
configureRepositoryWith( //
254+
DummyEntityWithEmbeddedRecordRepository.class, //
255+
Config.class, //
256+
AuditingConfiguration.class) //
257+
.accept(repository -> {
258+
259+
AuditingConfiguration.currentAuditor = "user01";
260+
261+
DummyEntityWithEmbeddedRecord entity = repository.save(new DummyEntityWithEmbeddedRecord(null, null));
262+
263+
assertThat(entity.id).as("id not null").isNotNull();
264+
assertThat(entity.auditing).isNull();
265+
266+
DummyEntityWithEmbeddedRecord reloaded = repository.findById(entity.id).get();
267+
268+
assertThat(reloaded.auditing).isNull();
269+
});
270+
}
271+
272+
196273
/**
197274
* Usage looks like this:
198275
* <p>
199276
* {@code configure(MyRepository.class, MyConfiguration) .accept(repository -> { // perform tests on repository here
200277
* }); }
201278
*
202-
* @param repositoryType the type of repository you want to perform tests on.
279+
* @param repositoryType the type of repository you want to perform tests on.
203280
* @param configurationClasses the classes containing the configuration for the
204-
* {@link org.springframework.context.ApplicationContext}.
205-
* @param <T> type of the entity managed by the repository.
206-
* @param <R> type of the repository.
281+
* {@link org.springframework.context.ApplicationContext}.
282+
* @param <T> type of the entity managed by the repository.
283+
* @param <R> type of the repository.
207284
* @return a Consumer for repositories of type {@code R}.
208285
*/
209286
private <T, R extends CrudRepository<T, Long>> Consumer<Consumer<R>> configureRepositoryWith(Class<R> repositoryType,
210-
Class... configurationClasses) {
287+
Class... configurationClasses) {
211288

212289
return (Consumer<R> test) -> {
213290

@@ -228,15 +305,21 @@ private void sleepMillis(int timeout) {
228305
}
229306
}
230307

231-
interface AuditingAnnotatedDummyEntityRepository extends CrudRepository<AuditingAnnotatedDummyEntity, Long> {}
308+
interface AuditingAnnotatedDummyEntityRepository extends CrudRepository<AuditingAnnotatedDummyEntity, Long> {
309+
}
232310

233311
static class AuditingAnnotatedDummyEntity {
234312

235-
@Id long id;
236-
@CreatedBy String createdBy;
237-
@CreatedDate LocalDateTime createdDate;
238-
@LastModifiedBy String lastModifiedBy;
239-
@LastModifiedDate LocalDateTime lastModifiedDate;
313+
@Id
314+
long id;
315+
@CreatedBy
316+
String createdBy;
317+
@CreatedDate
318+
LocalDateTime createdDate;
319+
@LastModifiedBy
320+
String lastModifiedBy;
321+
@LastModifiedDate
322+
LocalDateTime lastModifiedDate;
240323

241324
public long getId() {
242325
return this.id;
@@ -279,11 +362,13 @@ public void setLastModifiedDate(LocalDateTime lastModifiedDate) {
279362
}
280363
}
281364

282-
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
365+
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
366+
}
283367

284368
static class DummyEntity {
285369

286-
@Id private Long id;
370+
@Id
371+
private Long id;
287372
// not actually used, exists just to avoid empty value list during insert.
288373
String name;
289374

@@ -319,6 +404,22 @@ public int hashCode() {
319404
}
320405
}
321406

407+
record DummyEntityWithEmbeddedRecord(
408+
@Id Long id,
409+
@Embedded.Nullable EmbeddedAuditing auditing
410+
) {
411+
}
412+
413+
record EmbeddedAuditing(
414+
@CreatedBy String createdBy,
415+
@CreatedDate LocalDateTime createdDate,
416+
@LastModifiedBy String lastModifiedBy,
417+
@LastModifiedDate LocalDateTime lastModifiedDate
418+
) {
419+
}
420+
interface DummyEntityWithEmbeddedRecordRepository extends CrudRepository<DummyEntityWithEmbeddedRecord, Long> {
421+
}
422+
322423
@Configuration
323424
@EnableJdbcRepositories(considerNestedRepositories = true)
324425
@Import(TestConfiguration.class)

spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentProperty.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,16 @@ public TypeInformation<?> getAssociationTargetTypeInformation() {
290290

291291
@Override
292292
public boolean equals(Object o) {
293-
if (this == o)
293+
294+
if (this == o) {
295+
return true;
296+
}
297+
if (delegate == o) {
294298
return true;
295-
if (o == null || getClass() != o.getClass())
299+
}
300+
if (o == null || getClass() != o.getClass()) {
296301
return false;
302+
}
297303

298304
EmbeddedRelationalPersistentProperty that = (EmbeddedRelationalPersistentProperty) o;
299305

0 commit comments

Comments
 (0)