Keycloak Onboarding LabChapter VI — Model Persistence (JPA + Infinispan)0/5Contents
Chapter VI

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.


walkthrough

How RealmAdapter turns SPI calls into JPA reads/writes

model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java · lines 104174
lines 104–122

RealmAdapter 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.

lines 129–137

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.

lines 139–143

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.

lines 170–174

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();    }
step 1 of 4
How a realm read/write flows through the cache to JPA
RealmModel (server-spi)…g/keycloak/models/RealmModel.java
RealmCacheSession…infinispan/RealmCacheSession.java
infinispan RealmAdapter (CachedRealmModel)…ache/infinispan/RealmAdapter.java
jpa RealmAdapter (RealmModel)…loak/models/jpa/RealmAdapter.java
RealmEntity (JPA entity)…loak/models/jpa/RealmAdapter.java
Figure I · Click a node to see what it does, where it lives in the code, and its labeled connections — the annotation opens beside it.
checkpoint

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?