3333import java .util .ArrayList ;
3434import java .util .List ;
3535import java .util .stream .Stream ;
36+ import org .hisp .dhis .tracker .model .Enrollment ;
37+ import org .hisp .dhis .tracker .model .Relationship ;
38+ import org .hisp .dhis .tracker .model .SingleEvent ;
3639import org .hisp .dhis .tracker .model .TrackedEntity ;
3740import org .hisp .dhis .tracker .model .TrackedEntityAttributeValue ;
41+ import org .hisp .dhis .tracker .model .TrackerEvent ;
3842
3943/**
4044 * Accumulates entity-level writes staged during the persist loop and applies them at a single flush
4145 * point. Mirrors {@link ChangeLogAccumulator} and shares the same mark/rollback contract for
4246 * per-entity error isolation in non-atomic mode.
4347 *
44- * <p>Phase 3 scope: TEAV inserts/updates/deletes only. The flush implementation currently delegates
45- * back to {@link EntityManager} so that Hibernate continues to drive persistence while the staging
46- * shape is in place. Phase 6 replaces {@link #flush(EntityManager)} with a connection-based
47- * implementation that emits multi-row INSERT / unnest UPDATE / tuple DELETE statements. Top-level
48- * entities (TrackedEntity, Enrollment, events, relationships) are added by Phase 4.
48+ * <p>Current scope: TrackedEntity, Enrollment, TrackerEvent, SingleEvent, Relationship inserts and
49+ * updates, plus TEAV inserts/updates/deletes. The flush implementation delegates back to {@link
50+ * EntityManager} so that Hibernate continues to drive persistence while the staging shape is in
51+ * place. Phase 6 replaces {@link #flush(EntityManager)} with a connection-based implementation that
52+ * emits multi-row INSERT / unnest UPDATE / tuple DELETE statements.
53+ *
54+ * <p>Each {@link AbstractTrackerPersister#persist} call creates its own batch, so in practice only
55+ * one top-level entity type list is populated per batch (the persister's own type) alongside any
56+ * TEAVs staged by its attribute-handling code.
4957 */
5058class EntityWriteBatch {
5159
60+ private final List <TrackedEntity > teInserts = new ArrayList <>();
61+ private final List <TrackedEntity > teUpdates = new ArrayList <>();
62+
63+ private final List <Enrollment > enrollmentInserts = new ArrayList <>();
64+ private final List <Enrollment > enrollmentUpdates = new ArrayList <>();
65+
66+ private final List <TrackerEvent > trackerEventInserts = new ArrayList <>();
67+ private final List <TrackerEvent > trackerEventUpdates = new ArrayList <>();
68+
69+ private final List <SingleEvent > singleEventInserts = new ArrayList <>();
70+ private final List <SingleEvent > singleEventUpdates = new ArrayList <>();
71+
72+ private final List <Relationship > relationshipInserts = new ArrayList <>();
73+
5274 private final List <TrackedEntityAttributeValue > teavInserts = new ArrayList <>();
5375 private final List <TrackedEntityAttributeValue > teavUpdates = new ArrayList <>();
5476 private final List <TrackedEntityAttributeValue > teavDeletes = new ArrayList <>();
5577
78+ void stageInsert (TrackedEntity trackedEntity ) {
79+ teInserts .add (trackedEntity );
80+ }
81+
82+ void stageUpdate (TrackedEntity trackedEntity ) {
83+ teUpdates .add (trackedEntity );
84+ }
85+
86+ void stageInsert (Enrollment enrollment ) {
87+ enrollmentInserts .add (enrollment );
88+ }
89+
90+ void stageUpdate (Enrollment enrollment ) {
91+ enrollmentUpdates .add (enrollment );
92+ }
93+
94+ void stageInsert (TrackerEvent event ) {
95+ trackerEventInserts .add (event );
96+ }
97+
98+ void stageUpdate (TrackerEvent event ) {
99+ trackerEventUpdates .add (event );
100+ }
101+
102+ void stageInsert (SingleEvent event ) {
103+ singleEventInserts .add (event );
104+ }
105+
106+ void stageUpdate (SingleEvent event ) {
107+ singleEventUpdates .add (event );
108+ }
109+
110+ /**
111+ * Relationships are never updated -- {@link AbstractTrackerPersister} ignores update payloads.
112+ */
113+ void stageInsert (Relationship relationship ) {
114+ relationshipInserts .add (relationship );
115+ }
116+
56117 void stageTeavInsert (TrackedEntityAttributeValue value ) {
57118 teavInserts .add (value );
58119 }
@@ -79,10 +140,31 @@ Stream<TrackedEntityAttributeValue> stagedFor(TrackedEntity trackedEntity) {
79140 }
80141
81142 Mark mark () {
82- return new Mark (teavInserts .size (), teavUpdates .size (), teavDeletes .size ());
143+ return new Mark (
144+ teInserts .size (),
145+ teUpdates .size (),
146+ enrollmentInserts .size (),
147+ enrollmentUpdates .size (),
148+ trackerEventInserts .size (),
149+ trackerEventUpdates .size (),
150+ singleEventInserts .size (),
151+ singleEventUpdates .size (),
152+ relationshipInserts .size (),
153+ teavInserts .size (),
154+ teavUpdates .size (),
155+ teavDeletes .size ());
83156 }
84157
85158 void rollbackTo (Mark mark ) {
159+ truncate (teInserts , mark .teInsertSize );
160+ truncate (teUpdates , mark .teUpdateSize );
161+ truncate (enrollmentInserts , mark .enrollmentInsertSize );
162+ truncate (enrollmentUpdates , mark .enrollmentUpdateSize );
163+ truncate (trackerEventInserts , mark .trackerEventInsertSize );
164+ truncate (trackerEventUpdates , mark .trackerEventUpdateSize );
165+ truncate (singleEventInserts , mark .singleEventInsertSize );
166+ truncate (singleEventUpdates , mark .singleEventUpdateSize );
167+ truncate (relationshipInserts , mark .relationshipInsertSize );
86168 truncate (teavInserts , mark .teavInsertSize );
87169 truncate (teavUpdates , mark .teavUpdateSize );
88170 truncate (teavDeletes , mark .teavDeleteSize );
@@ -92,12 +174,27 @@ void rollbackTo(Mark mark) {
92174 * Applies all staged writes through the provided {@link EntityManager}. Temporary delegating
93175 * implementation; Phase 6 swaps this for JDBC multi-row statements that take a {@link
94176 * java.sql.Connection}.
177+ *
178+ * <p>Top-level entities are flushed before TEAVs so that Hibernate assigns ids (and inserts the
179+ * rows, when {@code em.flush()} runs) before any TEAV that references them. The top-level order
180+ * (TE, Enrollment, TrackerEvent, SingleEvent, Relationship) matches the persister call order
181+ * enforced by {@code DefaultTrackerBundleService.commit()} and is FK-safe.
95182 */
96183 void flush (EntityManager entityManager ) {
97- if (teavInserts . isEmpty () && teavUpdates . isEmpty () && teavDeletes . isEmpty ()) {
184+ if (isEmpty ()) {
98185 return ;
99186 }
100187
188+ persistAll (entityManager , teInserts );
189+ mergeAll (entityManager , teUpdates );
190+ persistAll (entityManager , enrollmentInserts );
191+ mergeAll (entityManager , enrollmentUpdates );
192+ persistAll (entityManager , trackerEventInserts );
193+ mergeAll (entityManager , trackerEventUpdates );
194+ persistAll (entityManager , singleEventInserts );
195+ mergeAll (entityManager , singleEventUpdates );
196+ persistAll (entityManager , relationshipInserts );
197+
101198 for (TrackedEntityAttributeValue v : teavInserts ) {
102199 entityManager .persist (v );
103200 }
@@ -108,16 +205,64 @@ void flush(EntityManager entityManager) {
108205 entityManager .remove (entityManager .contains (v ) ? v : entityManager .merge (v ));
109206 }
110207
208+ teInserts .clear ();
209+ teUpdates .clear ();
210+ enrollmentInserts .clear ();
211+ enrollmentUpdates .clear ();
212+ trackerEventInserts .clear ();
213+ trackerEventUpdates .clear ();
214+ singleEventInserts .clear ();
215+ singleEventUpdates .clear ();
216+ relationshipInserts .clear ();
111217 teavInserts .clear ();
112218 teavUpdates .clear ();
113219 teavDeletes .clear ();
114220 }
115221
222+ private boolean isEmpty () {
223+ return teInserts .isEmpty ()
224+ && teUpdates .isEmpty ()
225+ && enrollmentInserts .isEmpty ()
226+ && enrollmentUpdates .isEmpty ()
227+ && trackerEventInserts .isEmpty ()
228+ && trackerEventUpdates .isEmpty ()
229+ && singleEventInserts .isEmpty ()
230+ && singleEventUpdates .isEmpty ()
231+ && relationshipInserts .isEmpty ()
232+ && teavInserts .isEmpty ()
233+ && teavUpdates .isEmpty ()
234+ && teavDeletes .isEmpty ();
235+ }
236+
237+ private static <T > void persistAll (EntityManager entityManager , List <T > entities ) {
238+ for (T entity : entities ) {
239+ entityManager .persist (entity );
240+ }
241+ }
242+
243+ private static <T > void mergeAll (EntityManager entityManager , List <T > entities ) {
244+ for (T entity : entities ) {
245+ entityManager .merge (entity );
246+ }
247+ }
248+
116249 private static <T > void truncate (List <T > list , int size ) {
117250 if (list .size () > size ) {
118251 list .subList (size , list .size ()).clear ();
119252 }
120253 }
121254
122- record Mark (int teavInsertSize , int teavUpdateSize , int teavDeleteSize ) {}
255+ record Mark (
256+ int teInsertSize ,
257+ int teUpdateSize ,
258+ int enrollmentInsertSize ,
259+ int enrollmentUpdateSize ,
260+ int trackerEventInsertSize ,
261+ int trackerEventUpdateSize ,
262+ int singleEventInsertSize ,
263+ int singleEventUpdateSize ,
264+ int relationshipInsertSize ,
265+ int teavInsertSize ,
266+ int teavUpdateSize ,
267+ int teavDeleteSize ) {}
123268}
0 commit comments