Skip to content

Commit d302ad1

Browse files
committed
GH-806 - Revamp JPA archive support.
We now use two separate entity types for archived and non-archived event publications to rpevent hibernate hickups when inserting an instance of a subtype with he same id.
1 parent 6a85fb3 commit d302ad1

File tree

4 files changed

+134
-96
lines changed

4 files changed

+134
-96
lines changed

spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/ArchivedJpaEventPublication.java

Lines changed: 0 additions & 34 deletions
This file was deleted.

spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublication.java

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515
*/
1616
package org.springframework.modulith.events.jpa;
1717

18-
import jakarta.persistence.*;
18+
import jakarta.persistence.Column;
19+
import jakarta.persistence.Entity;
20+
import jakarta.persistence.Id;
21+
import jakarta.persistence.MappedSuperclass;
22+
import jakarta.persistence.Table;
1923

2024
import java.time.Instant;
25+
import java.util.Objects;
2126
import java.util.UUID;
2227

28+
import org.springframework.modulith.events.support.CompletionMode;
2329
import org.springframework.util.Assert;
2430

2531
/**
@@ -28,11 +34,10 @@
2834
* @author Oliver Drotbohm
2935
* @author Dmitry Belyaev
3036
* @author Björn Kieling
37+
* @author Cora Iberkleid
3138
*/
32-
@Entity
33-
@Table(name = "EVENT_PUBLICATION")
34-
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
35-
class JpaEventPublication {
39+
@MappedSuperclass
40+
abstract class JpaEventPublication {
3641

3742
final @Id @Column(length = 16) UUID id;
3843
final Instant publicationDate;
@@ -51,7 +56,8 @@ class JpaEventPublication {
5156
* @param serializedEvent must not be {@literal null} or empty.
5257
* @param eventType must not be {@literal null}.
5358
*/
54-
JpaEventPublication(UUID id, Instant publicationDate, String listenerId, String serializedEvent, Class<?> eventType) {
59+
private JpaEventPublication(UUID id, Instant publicationDate, String listenerId, String serializedEvent,
60+
Class<?> eventType) {
5561

5662
Assert.notNull(id, "Identifier must not be null!");
5763
Assert.notNull(publicationDate, "Publication date must not be null!");
@@ -75,11 +81,77 @@ protected JpaEventPublication() {
7581
this.eventType = null;
7682
}
7783

84+
static JpaEventPublication of(UUID id, Instant publicationDate, String listenerId, String serializedEvent,
85+
Class<?> eventType) {
86+
return new DefaultJpaEventPublication(id, publicationDate, listenerId, serializedEvent, eventType);
87+
}
88+
89+
static Class<? extends JpaEventPublication> getIncompleteType() {
90+
return DefaultJpaEventPublication.class;
91+
}
92+
93+
static Class<? extends JpaEventPublication> getCompletedType(CompletionMode mode) {
94+
return mode == CompletionMode.ARCHIVE ? ArchivedJpaEventPublication.class : DefaultJpaEventPublication.class;
95+
}
96+
7897
ArchivedJpaEventPublication archive(Instant instant) {
7998

8099
var result = new ArchivedJpaEventPublication(id, publicationDate, listenerId, serializedEvent, eventType);
81100
result.completionDate = instant;
82101

83102
return result;
84103
}
104+
105+
/*
106+
* (non-Javadoc)
107+
* @see java.lang.Object#equals(java.lang.Object)
108+
*/
109+
@Override
110+
public boolean equals(Object obj) {
111+
112+
if (obj == this) {
113+
return true;
114+
}
115+
116+
if (!(obj instanceof JpaEventPublication that)) {
117+
return false;
118+
}
119+
120+
return Objects.equals(this.id, that.id);
121+
}
122+
123+
/*
124+
* (non-Javadoc)
125+
* @see java.lang.Object#hashCode()
126+
*/
127+
@Override
128+
public int hashCode() {
129+
return id.hashCode();
130+
}
131+
132+
@Entity(name = "DefaultJpaEventPublication")
133+
@Table(name = "EVENT_PUBLICATION")
134+
private static class DefaultJpaEventPublication extends JpaEventPublication {
135+
136+
private DefaultJpaEventPublication(UUID id, Instant publicationDate, String listenerId, String serializedEvent,
137+
Class<?> eventType) {
138+
super(id, publicationDate, listenerId, serializedEvent, eventType);
139+
}
140+
141+
@SuppressWarnings("unused")
142+
DefaultJpaEventPublication() {}
143+
}
144+
145+
@Entity(name = "ArchivedJpaEventPublication")
146+
@Table(name = "EVENT_PUBLICATION_ARCHIVE")
147+
private static class ArchivedJpaEventPublication extends JpaEventPublication {
148+
149+
private ArchivedJpaEventPublication(UUID id, Instant publicationDate, String listenerId, String serializedEvent,
150+
Class<?> eventType) {
151+
super(id, publicationDate, listenerId, serializedEvent, eventType);
152+
}
153+
154+
@SuppressWarnings("unused")
155+
ArchivedJpaEventPublication() {}
156+
}
85157
}

spring-modulith-events/spring-modulith-events-jpa/src/main/java/org/springframework/modulith/events/jpa/JpaEventPublicationRepository.java

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.modulith.events.core.PublicationTargetIdentifier;
3030
import org.springframework.modulith.events.core.TargetEventPublication;
3131
import org.springframework.modulith.events.support.CompletionMode;
32+
import org.springframework.stereotype.Repository;
3233
import org.springframework.transaction.annotation.Transactional;
3334
import org.springframework.util.Assert;
3435

@@ -41,11 +42,12 @@
4142
* @author Cora Iberkleid
4243
*/
4344
@Transactional
45+
@Repository
4446
class JpaEventPublicationRepository implements EventPublicationRepository {
4547

4648
private static String BY_EVENT_AND_LISTENER_ID = """
4749
select p
48-
from JpaEventPublication p
50+
from DefaultJpaEventPublication p
4951
where
5052
p.serializedEvent = ?1
5153
and p.listenerId = ?2
@@ -63,7 +65,7 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
6365

6466
private static String INCOMPLETE = """
6567
select p
66-
from JpaEventPublication p
68+
from DefaultJpaEventPublication p
6769
where
6870
p.completionDate is null
6971
order by
@@ -72,7 +74,7 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
7274

7375
private static String INCOMPLETE_BEFORE = """
7476
select p
75-
from JpaEventPublication p
77+
from DefaultJpaEventPublication p
7678
where
7779
p.completionDate is null
7880
and p.publicationDate < ?1
@@ -81,34 +83,34 @@ class JpaEventPublicationRepository implements EventPublicationRepository {
8183
""";
8284

8385
private static final String MARK_COMPLETED_BY_EVENT_AND_LISTENER_ID = """
84-
update JpaEventPublication p
86+
update DefaultJpaEventPublication p
8587
set p.completionDate = ?3
8688
where p.serializedEvent = ?1
8789
and p.listenerId = ?2
8890
and p.completionDate is null
8991
""";
9092

9193
private static final String MARK_COMPLETED_BY_ID = """
92-
update JpaEventPublication p
94+
update DefaultJpaEventPublication p
9395
set p.completionDate = ?2
9496
where p.id = ?1
9597
""";
9698

9799
private static final String DELETE = """
98100
delete
99-
from JpaEventPublication p
101+
from DefaultJpaEventPublication p
100102
where p.id in ?1
101103
""";
102104

103105
private static final String DELETE_BY_EVENT_AND_LISTENER_ID = """
104-
delete JpaEventPublication p
106+
delete DefaultJpaEventPublication p
105107
where p.serializedEvent = ?1
106108
and p.listenerId = ?2
107109
""";
108110

109111
private static final String DELETE_BY_ID = """
110112
delete
111-
from JpaEventPublication p
113+
from DefaultJpaEventPublication p
112114
where p.id = ?1
113115
""";
114116

@@ -152,14 +154,12 @@ public JpaEventPublicationRepository(EntityManager entityManager, EventSerialize
152154
this.serializer = serializer;
153155
this.completionMode = completionMode;
154156

155-
var archiveEntityName = completionMode == CompletionMode.ARCHIVE
156-
? ArchivedJpaEventPublication.class.getSimpleName()
157-
: JpaEventPublication.class.getSimpleName();
157+
var archiveEntityName = getCompletedEntityType().getSimpleName();
158158

159159
this.getCompleted = COMPLETE.formatted(archiveEntityName);
160-
this.deleteCompleted = DELETE_COMPLETED.formatted(archiveEntityName);
160+
this.deleteCompleted = DELETE_COMPLETED.formatted(archiveEntityName);
161161
this.deleteCompletedBefore = DELETE_COMPLETED_BEFORE.formatted(archiveEntityName);
162-
}
162+
}
163163

164164
/*
165165
* (non-Javadoc)
@@ -192,15 +192,13 @@ public void markCompleted(Object event, PublicationTargetIdentifier identifier,
192192

193193
} else if (completionMode == CompletionMode.ARCHIVE) {
194194

195-
var publication = entityManager.createQuery(BY_EVENT_AND_LISTENER_ID, JpaEventPublication.class)
195+
var publication = entityManager.createQuery(BY_EVENT_AND_LISTENER_ID, JpaEventPublication.getIncompleteType())
196196
.setParameter(1, serializedEvent)
197197
.setParameter(2, identifierValue)
198198
.getSingleResult();
199199

200-
var archived = publication.archive(completionDate);
201-
202200
entityManager.remove(publication);
203-
entityManager.persist(archived);
201+
entityManager.persist(publication.archive(completionDate));
204202

205203
} else {
206204

@@ -227,12 +225,10 @@ public void markCompleted(UUID identifier, Instant completionDate) {
227225

228226
} else if (completionMode == CompletionMode.ARCHIVE) {
229227

230-
var publication = entityManager.find(JpaEventPublication.class, identifier);
231-
232-
var archived = publication.archive(completionDate);
228+
var publication = entityManager.find(JpaEventPublication.getIncompleteType(), identifier);
233229

234230
entityManager.remove(publication);
235-
entityManager.persist(archived);
231+
entityManager.persist(publication.archive(completionDate));
236232

237233
} else {
238234

@@ -251,7 +247,7 @@ public void markCompleted(UUID identifier, Instant completionDate) {
251247
@Transactional(readOnly = true)
252248
public List<TargetEventPublication> findIncompletePublications() {
253249

254-
return entityManager.createQuery(INCOMPLETE, JpaEventPublication.class)
250+
return entityManager.createQuery(INCOMPLETE, JpaEventPublication.getIncompleteType())
255251
.getResultStream()
256252
.map(this::entityToDomain)
257253
.toList();
@@ -265,7 +261,7 @@ public List<TargetEventPublication> findIncompletePublications() {
265261
@Transactional(readOnly = true)
266262
public List<TargetEventPublication> findIncompletePublicationsPublishedBefore(Instant instant) {
267263

268-
return entityManager.createQuery(INCOMPLETE_BEFORE, JpaEventPublication.class)
264+
return entityManager.createQuery(INCOMPLETE_BEFORE, JpaEventPublication.getIncompleteType())
269265
.setParameter(1, instant)
270266
.getResultStream()
271267
.map(this::entityToDomain)
@@ -292,9 +288,7 @@ public Optional<TargetEventPublication> findIncompletePublicationsByEventAndTarg
292288
@Override
293289
public List<TargetEventPublication> findCompletedPublications() {
294290

295-
var type = completionMode == CompletionMode.ARCHIVE
296-
? ArchivedJpaEventPublication.class
297-
: JpaEventPublication.class;
291+
var type = getCompletedEntityType();
298292

299293
return entityManager.createQuery(getCompleted, type)
300294
.getResultList()
@@ -338,12 +332,22 @@ public void deleteCompletedPublicationsBefore(Instant instant) {
338332
.executeUpdate();
339333
}
340334

341-
private Optional<JpaEventPublication> findEntityBySerializedEventAndListenerIdAndCompletionDateNull( //
335+
/**
336+
* Returns the type representing completed event publications.
337+
*
338+
* @return will never be {@literal null}.
339+
* @since 1.3
340+
*/
341+
public Class<? extends JpaEventPublication> getCompletedEntityType() {
342+
return JpaEventPublication.getCompletedType(completionMode);
343+
}
344+
345+
private Optional<? extends JpaEventPublication> findEntityBySerializedEventAndListenerIdAndCompletionDateNull( //
342346
Object event, PublicationTargetIdentifier listenerId) {
343347

344348
var serializedEvent = serializeEvent(event);
345349

346-
var query = entityManager.createQuery(BY_EVENT_AND_LISTENER_ID, JpaEventPublication.class)
350+
var query = entityManager.createQuery(BY_EVENT_AND_LISTENER_ID, JpaEventPublication.getIncompleteType())
347351
.setParameter(1, serializedEvent)
348352
.setParameter(2, listenerId.getValue());
349353

@@ -355,9 +359,11 @@ private String serializeEvent(Object event) {
355359
}
356360

357361
private JpaEventPublication domainToEntity(TargetEventPublication domain) {
358-
return new JpaEventPublication(domain.getIdentifier(), domain.getPublicationDate(),
359-
domain.getTargetIdentifier().getValue(),
360-
serializeEvent(domain.getEvent()), domain.getEvent().getClass());
362+
363+
var event = domain.getEvent();
364+
365+
return JpaEventPublication.of(domain.getIdentifier(), domain.getPublicationDate(),
366+
domain.getTargetIdentifier().getValue(), serializeEvent(event), event.getClass());
361367
}
362368

363369
private TargetEventPublication entityToDomain(JpaEventPublication entity) {

0 commit comments

Comments
 (0)