Model Persistence (JPA + Infinispan)
RealmAdapter (in model/jpa) is the concrete, JPA-backed implementation of the RealmModel SPI contract: every getter reads a JPA-managed RealmEntity and every setter writes to it, often followed by an explicit em.flush(). Because that adapter is too chatty to hit the database on every read, a second RealmAdapter in model/infinispan wraps it behind an Infinispan-backed CachedRealm, serving reads from cache and only falling through to the JPA delegate once a write invalidates it. Together these two adapters (and their UserAdapter peers) are how server-spi's abstract realm/user contracts become durable, cached state used across the whole server.
How RealmAdapter turns SPI calls into JPA reads/writes
model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java · lines 104–174RealmAdapter declares it implements StorageProviderRealmModel (a RealmModel subtype) and JpaModel<RealmEntity>. Its only real state is a JPA RealmEntity, an EntityManager, and the owning KeycloakSession — the adapter itself is a thin, stateless-feeling wrapper around the entity.
Getters like getId()/getName() do nothing more than delegate to the wrapped RealmEntity — the adapter provides no caching of its own; it is a direct, uncached view over the persistence-context entity.
Setters mutate the entity and then call em.flush() to force the write to the database immediately, rather than waiting for transaction commit — a pattern repeated dozens of times through this class.
setEnabled() shows the same flush-on-write pattern. Because every field touch can mean a round trip to the database, calling this adapter directly and repeatedly is expensive — which is exactly the problem the Infinispan cache layer exists to solve.
public class RealmAdapter implements StorageProviderRealmModel, JpaModel<RealmEntity> { protected static final Logger logger = Logger.getLogger(RealmAdapter.class); protected RealmEntity realm; protected EntityManager em; protected KeycloakSession session; @Override public Long getClientsCount() { return session.clients().getClientsCount(this); } private PasswordPolicy passwordPolicy; private OTPPolicy otpPolicy; public RealmAdapter(KeycloakSession session, EntityManager em, RealmEntity realm) { this.session = session; this.em = em; this.realm = realm; } @Override public RealmEntity getEntity() { return realm; } @Override public String getId() { return realm.getId(); } @Override public String getName() { return realm.getName(); } @Override public void setName(String name) { realm.setName(name); em.flush(); } @Override public String getDisplayName() { return realm.getDisplayName(); } @Override public void setDisplayName(String displayName) { realm.setDisplayName(displayName); } @Override public String getDisplayNameHtml() { return getAttribute(RealmAttributes.DISPLAY_NAME_HTML); } @Override public void setDisplayNameHtml(String displayNameHtml) { setAttribute(RealmAttributes.DISPLAY_NAME_HTML, displayNameHtml); } @Override public boolean isEnabled() { return realm.isEnabled(); } @Override public void setEnabled(boolean enabled) { realm.setEnabled(enabled); em.flush(); }
Check your understanding
Answered in place — nothing is graded, everything is explained. 0 / 2 passed
Which class's setters directly mutate a JPA entity and call em.flush()?
What triggers the cached infinispan RealmAdapter to stop serving reads from its CachedRealm snapshot and switch to the live delegate?