Spring AI Session JDBC 是一个基于 JDBC 的会话仓库实现,它将 AI 应用的会话数据持久化到关系型数据库中。它专为 Spring AI 框架设计,提供了会话生命周期管理、事件追加、查询以及压缩(compaction)等核心能力,开箱即支持 PostgreSQL、MySQL、MariaDB 和 H2。
接下来,将会会深入剖析其设计精髓------包括表结构、乐观锁机制、消息序列化策略,以及如何利用合成列优化查询性能。
1. 数据表设计:两张表,各司其职
会话数据被拆分为两张表,通过外键关联,既保证了元数据的轻量,又为事件日志的无限增长提供了结构化存储。
#mermaid-svg-DW91qLadEdGHQb5r{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DW91qLadEdGHQb5r .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DW91qLadEdGHQb5r .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DW91qLadEdGHQb5r .error-icon{fill:#552222;}#mermaid-svg-DW91qLadEdGHQb5r .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DW91qLadEdGHQb5r .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DW91qLadEdGHQb5r .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DW91qLadEdGHQb5r .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DW91qLadEdGHQb5r .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DW91qLadEdGHQb5r .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DW91qLadEdGHQb5r .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DW91qLadEdGHQb5r .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DW91qLadEdGHQb5r .marker.cross{stroke:#333333;}#mermaid-svg-DW91qLadEdGHQb5r svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DW91qLadEdGHQb5r p{margin:0;}#mermaid-svg-DW91qLadEdGHQb5r .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-DW91qLadEdGHQb5r .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-DW91qLadEdGHQb5r .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-DW91qLadEdGHQb5r .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-DW91qLadEdGHQb5r .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-DW91qLadEdGHQb5r .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DW91qLadEdGHQb5r .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-DW91qLadEdGHQb5r .node rect,#mermaid-svg-DW91qLadEdGHQb5r .node circle,#mermaid-svg-DW91qLadEdGHQb5r .node ellipse,#mermaid-svg-DW91qLadEdGHQb5r .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DW91qLadEdGHQb5r .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-DW91qLadEdGHQb5r .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-DW91qLadEdGHQb5r .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DW91qLadEdGHQb5r .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-DW91qLadEdGHQb5r .edgeLabel .label text{fill:#333;}#mermaid-svg-DW91qLadEdGHQb5r :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} has
AI_SESSION
string
id
PK
string
user_id
bigint
ttl
json
metadata
int
event_version
AI_SESSION_EVENT
bigint
seq
PK
自增序列,保证顺序
string
session_id
FK
级联删除
timestamp
timestamp
string
message_type
text
message_content
json
message_data
boolean
synthetic
boolean
archived
AI_SESSION(会话主表)
- id:会话唯一标识(字符串,由业务生成,如 UUID)。
- user_id:关联用户,便于按用户检索会话。
- ttl:生存时间(Time-To-Live),用于自动过期清理。
- metadata:JSON 格式的扩展元数据(如自定义标签、上下文信息)。
- event_version :乐观锁版本号,每次追加事件或执行压缩操作时自动递增,用于并发控制(后文详述)。
AI_SESSION_EVENT(事件日志表)
- seq:单调递增的序列号,天然反映事件的写入顺序,无需依赖时间戳排序。
- session_id :外键,指向
AI_SESSION.id,并带有ON DELETE CASCADE,会话删除时关联事件自动清理。 - timestamp:事件发生时间(应用层提供)。
- message_type 、message_content 、message_data :用于存储 Spring AI 的
Message对象(后文专节讲解)。 - synthetic:标记是否为系统生成的事件(例如压缩产生的合成事件)。
- archived :标记事件是否已被压缩归档(压缩时不会物理删除,而是将旧事件标记为
archived=true,保留完整历史)。
2. 集成三步走:依赖、Schema、Bean
2.1 添加 Maven 依赖
xml
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-session-jdbc</artifactId>
<version>${spring-ai-session.version}</version>
</dependency>
2.2 初始化数据库表结构
项目提供了针对不同数据库的 DDL 脚本,位于 classpath 的 org/springframework/ai/session/jdbc/ 目录下:
| 数据库 | 脚本文件名 |
|---|---|
| PostgreSQL | schema-postgresql.sql |
| H2 | schema-h2.sql |
| MySQL/MariaDB | schema-mysql.sql |
咱可以通过 Spring Boot 的 SQL 初始化功能自动执行:
yaml
spring:
sql:
init:
schema-locations: classpath:org/springframework/ai/session/jdbc/schema-postgresql.sql
或者将脚本内容复制到已有的迁移工具(Flyway/Liquibase)的迁移目录中,便于版本管理。
2.3 手动配置 Bean
若未使用自动配置,你需要手动声明 SessionRepository 和 SessionService:
java
@Bean
SessionRepository sessionRepository(DataSource dataSource) {
return JdbcSessionRepository.builder()
.dataSource(dataSource) // 方言会根据 DataSource URL 自动检测
.build();
}
@Bean
SessionService sessionService(SessionRepository sessionRepository) {
return new DefaultSessionService(sessionRepository);
}
自定义选项:Builder 还支持显式指定方言、事务管理器、自定义 JSON 映射器:
java
JdbcSessionRepository.builder()
.dataSource(dataSource)
.dialect(new PostgresJdbcSessionRepositoryDialect()) // 显式指定
.transactionManager(txManager) // 自定义事务管理器
.jsonMapper(customJsonMapper) // 自定义 JSON 序列化
.build();
3. 使用仓库:会话与事件的 CRUD
通过注入 SessionRepository,可以轻松操作会话和事件:
java
@Autowired
SessionRepository sessions;
// 1. 创建会话
Session session = sessions.save(Session.builder()
.id(UUID.randomUUID().toString())
.userId("alice")
.build());
// 2. 追加一个用户消息事件
sessions.appendEvent(SessionEvent.builder()
.id(UUID.randomUUID().toString())
.sessionId(session.id())
.timestamp(Instant.now())
.message(new UserMessage("Hello"))
.build());
// 3. 查询会话的所有事件(支持过滤)
List<SessionEvent> events = sessions.findEvents(
session.id(),
EventFilter.builder().excludeSynthetic(true).build()
);
EventFilter 支持按 synthetic、archived、时间范围等条件过滤,且由于 synthetic 是独立列,excludeSynthetic(true) 会直接生成 SQL 条件(WHERE synthetic = false),无需在应用层扫描所有记录,性能优秀。
4. 支持的数据库
| 数据库 | 注 |
|---|---|
| PostgreSQL | PostgresJdbcSessionRepositoryDialect |
| H2 | H2JdbcSessionRepositoryDialect |
| MySQL/MariaDB | MysqlJdbcSessionRepositoryDialect |
若使用其他数据库,默认回退至 PostgreSQL 方言(兼容性较好)。你也可以自行实现方言并提交贡献。
5. 设计精髓:三个关键决策
5.1 消息序列化:三列拆分,绕过 Jackson 多态陷阱
Spring AI 的 Message 接口有多种实现(如 UserMessage、AssistantMessage、ToolResponseMessage 等),若直接序列化为 JSON 存储,反序列化时需处理多态类型信息,容易出错且依赖 Jackson 注解。
解决方案:将消息拆分为三列:
message_type:存储枚举名称(如USER、ASSISTANT),用于确定具体子类。message_content:存储文本内容(普通字符串)。message_data:存储结构化的附加数据(如工具调用参数、工具响应结果),以 JSON 格式存放。
读取时,根据 message_type 实例化对应的 Message 子类,并用 message_content 和 message_data 填充。这样既避免了多态序列化的复杂性,又保持了数据的可读性和查询灵活性。
5.2 乐观锁与压缩安全:event_version 的妙用
压缩(compaction)操作会合并或归档旧事件,以减少存储空间。但压缩可能与其他写操作(如追加事件)并发,导致数据不一致。
乐观锁机制:
AI_SESSION.event_version初始为 0,每次appendEvent或compactEvents调用时都会 递增。- 压缩过程分两步:
- 原子性抢占版本 :执行
UPDATE AI_SESSION SET event_version = event_version + 1 WHERE id = ? AND event_version = ?(当前版本作为条件)。若更新影响行数为 0,说明版本已被其他线程改变,压缩失败(可重试或放弃)。 - 修改事件表 :在成功抢占新版本后,压缩操作安全地更新事件表的
archived标志或插入合成事件。
- 原子性抢占版本 :执行
这种设计确保了压缩操作的线性一致性,不会因并发冲突而损坏数据。
5.3 synthetic 列独立存储:将过滤下推到数据库
EventFilter 允许排除系统生成的合成事件。如果 synthetic 只存在 metadata JSON 中,过滤时需读取全部事件并解析 JSON,不仅效率低下,还无法利用索引。
决策 :将 synthetic 作为独立的 BOOLEAN 列,并为其建立索引(可选),使得 excludeSynthetic(true) 直接转化为 WHERE synthetic = false,数据库层面即可完成过滤,大幅提升查询性能。这体现了"为查询而设计"的建模原则。
6. 结语
Spring AI Session JDBC 通过简洁的两表模型,结合乐观锁、列拆分、索引优化等实用技术,为 AI 应用提供了可靠、高性能的会话持久化方案。无论你是快速原型开发,还是构建生产级系统,它都能很好地融入你的技术栈。