@@ -385,7 +385,10 @@ public boolean tryExecuteNow(@Nonnull UID jobId) {
385385 and (schedulingtype != 'ONCE_ASAP' or lastfinished is null)
386386 """ ;
387387 return runWriteInStatelessSession (
388- q -> q .createNativeQuery (sql ).setParameter ("id" , jobId .getValue ()).executeUpdate ())
388+ q ->
389+ synchronizedNativeQuery (q , sql )
390+ .setParameter ("id" , jobId .getValue ())
391+ .executeUpdate ())
389392 > 0 ;
390393 }
391394
@@ -413,7 +416,10 @@ and not exists (
413416 )
414417 """ ;
415418 return runWriteInStatelessSession (
416- q -> q .createNativeQuery (sql ).setParameter ("id" , jobId .getValue ()).executeUpdate ())
419+ q ->
420+ synchronizedNativeQuery (q , sql )
421+ .setParameter ("id" , jobId .getValue ())
422+ .executeUpdate ())
417423 > 0 ;
418424 }
419425
@@ -444,7 +450,10 @@ public boolean tryCancel(@Nonnull UID jobId) {
444450 )
445451 """ ;
446452 return runWriteInStatelessSession (
447- q -> q .createNativeQuery (sql ).setParameter ("id" , jobId .getValue ()).executeUpdate ())
453+ q ->
454+ synchronizedNativeQuery (q , sql )
455+ .setParameter ("id" , jobId .getValue ())
456+ .executeUpdate ())
448457 > 0 ;
449458 }
450459
@@ -475,7 +484,7 @@ public boolean tryFinish(@Nonnull UID jobId, JobStatus status) {
475484 """ ;
476485 return runWriteInStatelessSession (
477486 q ->
478- q . createNativeQuery ( sql )
487+ synchronizedNativeQuery ( q , sql )
479488 .setParameter ("id" , jobId .getValue ())
480489 .setParameter ("status" , status .name ())
481490 .executeUpdate ())
@@ -503,7 +512,7 @@ public boolean trySkip(@Nonnull String queue) {
503512 or lastexecuted < (select lastexecuted from jobconfiguration where queuename = :queue and queueposition = 0 limit 1))
504513 """ ;
505514 return runWriteInStatelessSession (
506- q -> q . createNativeQuery ( sql ).setParameter ("queue" , queue ).executeUpdate ())
515+ q -> synchronizedNativeQuery ( q , sql ).setParameter ("queue" , queue ).executeUpdate ())
507516 > 0 ;
508517 }
509518
@@ -521,7 +530,7 @@ public void updateProgress(
521530 """ ;
522531 runWriteInStatelessSession (
523532 q ->
524- q . createNativeQuery ( sql )
533+ synchronizedNativeQuery ( q , sql )
525534 .setParameter ("id" , jobId .getValue ())
526535 .setParameter ("json" , progressJson )
527536 .setParameter ("errors" , errorCodes )
@@ -539,7 +548,7 @@ public int updateDisabledJobs() {
539548 where jobstatus = 'SCHEDULED'
540549 and enabled = false
541550 """ ;
542- return runWriteInStatelessSession (q -> q . createNativeQuery ( sql ).executeUpdate ());
551+ return runWriteInStatelessSession (q -> synchronizedNativeQuery ( q , sql ).executeUpdate ());
543552 }
544553
545554 @ Override
@@ -557,7 +566,7 @@ and now() > lastfinished + :ttl * interval '1 minute'
557566 int deletedCount =
558567 runWriteInStatelessSession (
559568 q ->
560- q . createNativeQuery ( sql )
569+ synchronizedNativeQuery ( q , sql )
561570 .setLockOptions (new LockOptions (LockMode .PESSIMISTIC_WRITE ).setTimeOut (2000 ))
562571 .setParameter ("ttl" , max (1 , ttlMinutes ))
563572 .executeUpdate ());
@@ -574,7 +583,7 @@ and uid not in (select uid from jobconfiguration where schedulingtype = 'ONCE_AS
574583 """ ;
575584 runWriteInStatelessSession (
576585 q ->
577- q . createNativeQuery ( sql2 )
586+ synchronizedNativeQuery ( q , sql2 )
578587 .setLockOptions (new LockOptions (LockMode .PESSIMISTIC_WRITE ).setTimeOut (2000 ))
579588 .executeUpdate ());
580589 return deletedCount ;
@@ -606,7 +615,7 @@ and now() > lastalive + :timeout * interval '1 minute'
606615 """ ;
607616 return runWriteInStatelessSession (
608617 q ->
609- q . createNativeQuery ( sql )
618+ synchronizedNativeQuery ( q , sql )
610619 .setParameter ("timeout" , max (1 , timeoutMinutes ))
611620 .executeUpdate ());
612621 }
@@ -637,10 +646,26 @@ public boolean tryRevertNow(@Nonnull UID jobId) {
637646 and now() > jobconfiguration.lastalive + interval '1 minute'
638647 """ ;
639648 return runWriteInStatelessSession (
640- q -> q .createNativeQuery (sql ).setParameter ("id" , jobId .getValue ()).executeUpdate ())
649+ q ->
650+ synchronizedNativeQuery (q , sql )
651+ .setParameter ("id" , jobId .getValue ())
652+ .executeUpdate ())
641653 > 0 ;
642654 }
643655
656+ /**
657+ * Creates a native query that declares the table it touches. Without a synchronized query space,
658+ * Hibernate cannot know which cached entities a native {@code executeUpdate} affects and
659+ * conservatively invalidates EVERY second level cache region (a wholesale cache wipe). The
660+ * scheduler writes through this store every ~20 seconds, which without this declaration empties
661+ * all caches at that cadence (l2-cache-truth Phase 4 finding, 2026-08-07).
662+ */
663+ private static NativeQuery <?> synchronizedNativeQuery (StatelessSession session , String sql ) {
664+ NativeQuery <?> query = session .createNativeQuery (sql );
665+ query .addSynchronizedQuerySpace ("jobconfiguration" );
666+ return query ;
667+ }
668+
644669 @ SuppressWarnings ("unchecked" )
645670 private static <T > T getSingleResultOrNull (NativeQuery <?> query ) {
646671 return (T ) query .getResultStream ().findFirst ().orElse (null );
0 commit comments