Class JdbcSessionRepository
- All Implemented Interfaces:
SessionRepository
SessionRepository on a relational database: AI_SESSION (id, expiry,
metadata JSON, event version), AI_SESSION_INDEX (the indexed metadata
attributes, one row per attribute) and AI_SESSION_EVENT (the log, ordered by a
database-assigned seq). The schema scripts are under
classpath:org/springframework/ai/session/jdbc/.
On every save the metadata entries whose keys are
indexed, the principal by default, are
copied into AI_SESSION_INDEX, on the first 512
characters of the value, and findByMetadata(java.lang.String, java.lang.String) is one query over that table, with
the whole value checked on the sessions it returns. An event is stored in the four
columns SessionEventCodec defines: the event type, the message type, the
message text, its own column so a store can search it, and a JSON payload.
The event_version column of AI_SESSION advances on every
appendEvent(org.springframework.ai.session.SessionEvent) that appends a new event and on every successful
appendEvents(java.lang.String, java.util.List<org.springframework.ai.session.SessionEvent>, long), which claims the version with
UPDATE … WHERE event_version = ? before inserting. appendEvent(org.springframework.ai.session.SessionEvent) looks
the event id up under that row lock before inserting, so a replay is a no-op without a
duplicate-key error, which would mark a caller's surrounding transaction rollback-only,
or abort it on PostgreSQL. A read of the active view
selects the newest COMPACTION row, fetches the rows it names plus every row
from it on, and lets EventFilter.apply(List) order, filter and window them.
Every mutation runs in a TransactionTemplate; the class is thread-safe as the
DataSource is.
- Since:
- 2.1.0
- Author:
- Christian Tzolov
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final class -
Field Summary
FieldsModifier and TypeFieldDescriptionThe metadata keys every repository indexes: the principal.static final intThe number of leading characters of a metadata value the index row holds: with four bytes per character on MySQL, the(attr_name, attr_value)index stays under InnoDB's key limit. -
Method Summary
Modifier and TypeMethodDescriptionvoidappendEvent(SessionEvent event) Appends one event to the log of the session named bySessionEvent.getSessionId().booleanappendEvents(String sessionId, List<SessionEvent> events, long expectedVersion) Appends several events in order, atomically, only if the log's version equalsexpectedVersion; otherwise nothing is written andfalseis returned.builder()Returns a newJdbcSessionRepository.Builder.voidDeletes the session and its events.intdeleteExpiredSessions(Instant before) Deletes every session that expired before the given instant, with their events, in one atomic step per session, so a session whose expiry was extended concurrently (SessionRepository.save(org.springframework.ai.session.Session)) is kept.@Nullable SessionfindByMetadata(String name, String value) findEvents(String sessionId, EventFilter filter) The events of the session the filter selects, oldest first; an empty list for a session that does not exist.longgetEventVersion(String sessionId) The version of the session's log, advanced on everySessionRepository.appendEvent(org.springframework.ai.session.SessionEvent)that appends a new event and on every successfulSessionRepository.appendEvents(java.lang.String, java.util.List<org.springframework.ai.session.SessionEvent>, long);0for a session that does not exist or has no events.The metadata keys this repository indexes; seefindByMetadata(java.lang.String, java.lang.String).Persists session metadata (create or update).booleansaveIfAbsent(Session session) Inserts the session unless one with the same id exists, atomically: a primary-key-guardedINSERT, or a compare-and-set put.
-
Field Details
-
MAX_INDEXED_VALUE_LENGTH
public static final int MAX_INDEXED_VALUE_LENGTHThe number of leading characters of a metadata value the index row holds: with four bytes per character on MySQL, the(attr_name, attr_value)index stays under InnoDB's key limit. The row is a prefix index:findByMetadata(java.lang.String, java.lang.String)compares the whole value on the sessions the query returns, so two values that share their first 512 characters are told apart.- See Also:
-
DEFAULT_INDEXED_ATTRIBUTES
The metadata keys every repository indexes: the principal.
-
-
Method Details
-
getIndexedAttributes
The metadata keys this repository indexes; seefindByMetadata(java.lang.String, java.lang.String). -
save
Description copied from interface:SessionRepositoryPersists session metadata (create or update). If the session already exists its event log and its originalSession.createdAt()are preserved; the other metadata fields are replaced.- Specified by:
savein interfaceSessionRepository- Returns:
- the saved session
-
saveIfAbsent
Description copied from interface:SessionRepositoryInserts the session unless one with the same id exists, atomically: a primary-key-guardedINSERT, or a compare-and-set put.SessionService.create(org.springframework.ai.session.CreateSessionRequest)relies on it, so two concurrent creates of one id cannot both succeed.- Specified by:
saveIfAbsentin interfaceSessionRepository- Returns:
trueif the session was inserted,falseif one with the same id exists; the existing session is left untouched
-
findById
- Specified by:
findByIdin interfaceSessionRepository
-
findByMetadata
Description copied from interface:SessionRepositoryThe sessions whosemetadataholdsvalueundername, oldest first (creation time, then id). The one scoped lookup of the store, in the shape of Spring Session'sfindByIndexNameAndIndexValue: the store indexes attributes and has no notion of ownership. Every store indexesSessionAttributes.PRINCIPAL_NAME; a store that cannot index every key documents which others it answers for.- Specified by:
findByMetadatain interfaceSessionRepository- Parameters:
name- the metadata keyvalue- the value, compared as a string
-
deleteExpiredSessions
Description copied from interface:SessionRepositoryDeletes every session that expired before the given instant, with their events, in one atomic step per session, so a session whose expiry was extended concurrently (SessionRepository.save(org.springframework.ai.session.Session)) is kept.- Specified by:
deleteExpiredSessionsin interfaceSessionRepository- Parameters:
before- the expiry cut-off- Returns:
- the number of sessions deleted
-
delete
Description copied from interface:SessionRepositoryDeletes the session and its events.- Specified by:
deletein interfaceSessionRepository
-
appendEvent
Description copied from interface:SessionRepositoryAppends one event to the log of the session named bySessionEvent.getSessionId().Idempotent by id: an event whose id is already in the session's log is a retried append and a no-op, neither a duplicate nor an error, and the version is not advanced. A caller that wants to retry safely supplies its own id rather than the builder's random one. Event ids are unique across sessions: a persistent store keys events by id alone and rejects an id used by another session rather than dropping the event.
- Specified by:
appendEventin interfaceSessionRepository
-
appendEvents
Description copied from interface:SessionRepositoryAppends several events in order, atomically, only if the log's version equalsexpectedVersion; otherwise nothing is written andfalseis returned. On success the version advances once, whatever the number of events. This is how a compaction is recorded: oneEventType.COMPACTIONboundary and its summary events, with the version check keeping two concurrent compactions from both landing. ReadSessionRepository.getEventVersion(java.lang.String)beforeSessionRepository.findEvents(java.lang.String, org.springframework.ai.session.EventFilter), then pass that version here.- Specified by:
appendEventsin interfaceSessionRepository- Parameters:
sessionId- the session whose log is appended to; every event must belong to itevents- the events, in order; at least one, with ids that are new to the logexpectedVersion- the version the caller read- Returns:
truewhen the events were appended,falsewhen the version did not match
-
getEventVersion
Description copied from interface:SessionRepositoryThe version of the session's log, advanced on everySessionRepository.appendEvent(org.springframework.ai.session.SessionEvent)that appends a new event and on every successfulSessionRepository.appendEvents(java.lang.String, java.util.List<org.springframework.ai.session.SessionEvent>, long);0for a session that does not exist or has no events. Read it beforeSessionRepository.findEvents(java.lang.String, org.springframework.ai.session.EventFilter), so that the version passed toSessionRepository.appendEvents(java.lang.String, java.util.List<org.springframework.ai.session.SessionEvent>, long)is not newer than the events read.- Specified by:
getEventVersionin interfaceSessionRepository
-
findEvents
Description copied from interface:SessionRepositoryThe events of the session the filter selects, oldest first; an empty list for a session that does not exist.A
EventFilter.Window.Lastwindow returns the newest N matching events, extended back to the start of the turn it lands in (the nearest precedingturn startamong the matches), so a tool call is never returned without its results; the extension is skipped when no turn start precedes the window. WithEventFilter.activeView()the filter applies to the active view, the events the newestEventType.COMPACTIONboundary names and everything appended after it, rather than to the whole log.EventFilter.apply(List)is the reference implementation of both. A store pushes the criteria and the window down to its query, so that a read does not cost the whole log, and may fetch only the rows a view can contain and hand them toapply, asJdbcSessionRepositorydoes.- Specified by:
findEventsin interfaceSessionRepository
-
builder
Returns a newJdbcSessionRepository.Builder.
-