diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/pom.xml b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/pom.xml
index df76c788c..075b40ae5 100644
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/pom.xml
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/pom.xml
@@ -78,6 +78,23 @@
postgresql
test
+
+
+ com.microsoft.sqlserver
+ mssql-jdbc
+ test
+
+
+ org.testcontainers
+ mssqlserver
+ test
+
+
+
+ org.hsqldb
+ hsqldb
+ runtime
+
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializer.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializer.java
deleted file mode 100644
index 546b272d3..000000000
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializer.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2024-2025 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.ai.model.chat.memory.jdbc.autoconfigure;
-
-import java.util.List;
-
-import javax.sql.DataSource;
-
-import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
-import org.springframework.boot.jdbc.init.PlatformPlaceholderDatabaseDriverResolver;
-import org.springframework.boot.sql.init.DatabaseInitializationMode;
-import org.springframework.boot.sql.init.DatabaseInitializationSettings;
-
-/**
- * Performs database initialization for the JDBC Chat Memory Repository.
- *
- * @since 1.0.0
- */
-class JdbcChatMemoryDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer {
-
- private static final String SCHEMA_LOCATION = "classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql";
-
- JdbcChatMemoryDataSourceScriptDatabaseInitializer(DataSource dataSource) {
- super(dataSource, getSettings(dataSource));
- }
-
- static DatabaseInitializationSettings getSettings(DataSource dataSource) {
- var settings = new DatabaseInitializationSettings();
- settings.setSchemaLocations(resolveSchemaLocations(dataSource));
- settings.setMode(DatabaseInitializationMode.ALWAYS);
- settings.setContinueOnError(true);
-
- return settings;
- }
-
- static List resolveSchemaLocations(DataSource dataSource) {
- var platformResolver = new PlatformPlaceholderDatabaseDriverResolver();
-
- return platformResolver.resolveAll(dataSource, SCHEMA_LOCATION);
- }
-
-}
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryProperties.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryProperties.java
deleted file mode 100644
index 6ff1a2a18..000000000
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryProperties.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2024-2025 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.ai.model.chat.memory.jdbc.autoconfigure;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-/**
- * @author Jonathan Leijendekker
- * @author Thomas Vitale
- * @since 1.0.0
- */
-@ConfigurationProperties(JdbcChatMemoryProperties.CONFIG_PREFIX)
-public class JdbcChatMemoryProperties {
-
- public static final String CONFIG_PREFIX = "spring.ai.chat.memory.repository.jdbc";
-
- /**
- * Whether to initialize the schema on startup.
- */
- private boolean initializeSchema = true;
-
- public boolean isInitializeSchema() {
- return this.initializeSchema;
- }
-
- public void setInitializeSchema(boolean initializeSchema) {
- this.initializeSchema = initializeSchema;
- }
-
-}
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryAutoConfiguration.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryAutoConfiguration.java
similarity index 72%
rename from auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryAutoConfiguration.java
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryAutoConfiguration.java
index a6942b07d..2d0a89403 100644
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryAutoConfiguration.java
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryAutoConfiguration.java
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-package org.springframework.ai.model.chat.memory.jdbc.autoconfigure;
+package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.ai.chat.memory.jdbc.JdbcChatMemoryDialect;
import org.springframework.ai.chat.memory.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -39,24 +39,23 @@ import org.springframework.jdbc.core.JdbcTemplate;
*/
@AutoConfiguration(after = JdbcTemplateAutoConfiguration.class, before = ChatMemoryAutoConfiguration.class)
@ConditionalOnClass({ JdbcChatMemoryRepository.class, DataSource.class, JdbcTemplate.class })
-@EnableConfigurationProperties(JdbcChatMemoryProperties.class)
-public class JdbcChatMemoryAutoConfiguration {
+@EnableConfigurationProperties(JdbcChatMemoryRepositoryProperties.class)
+public class JdbcChatMemoryRepositoryAutoConfiguration {
- private static final Logger logger = LoggerFactory.getLogger(JdbcChatMemoryAutoConfiguration.class);
+ private static final Logger logger = LoggerFactory.getLogger(JdbcChatMemoryRepositoryAutoConfiguration.class);
@Bean
@ConditionalOnMissingBean
- JdbcChatMemoryRepository chatMemoryRepository(JdbcTemplate jdbcTemplate) {
- return JdbcChatMemoryRepository.builder().jdbcTemplate(jdbcTemplate).build();
+ JdbcChatMemoryRepository chatMemoryRepository(JdbcTemplate jdbcTemplate, DataSource dataSource) {
+ JdbcChatMemoryDialect dialect = JdbcChatMemoryDialect.from(dataSource);
+ return JdbcChatMemoryRepository.builder().jdbcTemplate(jdbcTemplate).dialect(dialect).build();
}
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = JdbcChatMemoryProperties.CONFIG_PREFIX, name = "initialize-schema",
- havingValue = "true", matchIfMissing = true)
- JdbcChatMemoryDataSourceScriptDatabaseInitializer jdbcChatMemoryScriptDatabaseInitializer(DataSource dataSource) {
- logger.debug("Initializing schema for JdbcChatMemoryRepository");
- return new JdbcChatMemoryDataSourceScriptDatabaseInitializer(dataSource);
+ JdbcChatMemoryRepositorySchemaInitializer jdbcChatMemoryScriptDatabaseInitializer(DataSource dataSource,
+ JdbcChatMemoryRepositoryProperties properties) {
+ return new JdbcChatMemoryRepositorySchemaInitializer(dataSource, properties);
}
}
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryProperties.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryProperties.java
new file mode 100644
index 000000000..47e64e5a9
--- /dev/null
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryProperties.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author Jonathan Leijendekker
+ * @author Thomas Vitale
+ * @since 1.0.0
+ */
+@ConfigurationProperties(JdbcChatMemoryRepositoryProperties.CONFIG_PREFIX)
+public class JdbcChatMemoryRepositoryProperties {
+
+ public static final String CONFIG_PREFIX = "spring.ai.chat.memory.repository.jdbc";
+
+ /**
+ * Whether to initialize the schema on startup. Values: embedded, always, never.
+ * Default is embedded.
+ */
+ private DatabaseInitializationMode initializeSchema = DatabaseInitializationMode.EMBEDDED;
+
+ /**
+ * Locations of schema (DDL) scripts. Supports comma-separated list. Default is
+ * classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql
+ */
+ private String schema = "classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql";
+
+ public DatabaseInitializationMode getInitializeSchema() {
+ return this.initializeSchema;
+ }
+
+ public void setInitializeSchema(DatabaseInitializationMode initializeSchema) {
+ this.initializeSchema = initializeSchema;
+ }
+
+ public String getSchema() {
+ return this.schema;
+ }
+
+ public void setSchema(String schema) {
+ this.schema = schema;
+ }
+
+ public enum DatabaseInitializationMode {
+
+ /**
+ * Always initialize the database.
+ */
+ ALWAYS,
+
+ /**
+ * Only initialize an embedded database.
+ */
+ EMBEDDED,
+
+ /**
+ * Never initialize the database.
+ */
+ NEVER
+
+ }
+
+}
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializer.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializer.java
new file mode 100644
index 000000000..e202e4794
--- /dev/null
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializer.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
+
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
+import org.springframework.boot.jdbc.init.PlatformPlaceholderDatabaseDriverResolver;
+import org.springframework.boot.sql.init.DatabaseInitializationMode;
+import org.springframework.boot.sql.init.DatabaseInitializationSettings;
+import org.springframework.util.StringUtils;
+
+/**
+ * Performs database initialization for the JDBC Chat Memory Repository.
+ *
+ * @author Mark Pollack
+ * @since 1.0.0
+ */
+class JdbcChatMemoryRepositorySchemaInitializer extends DataSourceScriptDatabaseInitializer {
+
+ private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql";
+
+ JdbcChatMemoryRepositorySchemaInitializer(DataSource dataSource, JdbcChatMemoryRepositoryProperties properties) {
+ super(dataSource, getSettings(dataSource, properties));
+ }
+
+ static DatabaseInitializationSettings getSettings(DataSource dataSource,
+ JdbcChatMemoryRepositoryProperties properties) {
+ var settings = new DatabaseInitializationSettings();
+
+ // Determine schema locations
+ String schemaProp = properties.getSchema();
+ List schemaLocations;
+ PlatformPlaceholderDatabaseDriverResolver resolver = new PlatformPlaceholderDatabaseDriverResolver();
+ try {
+ String url = dataSource.getConnection().getMetaData().getURL().toLowerCase();
+ if (url.contains("hsqldb")) {
+ schemaLocations = List.of("classpath:org/springframework/ai/chat/memory/jdbc/schema-hsqldb.sql");
+ }
+ else if (StringUtils.hasText(schemaProp)) {
+ schemaLocations = resolver.resolveAll(dataSource, schemaProp);
+ }
+ else {
+ schemaLocations = resolver.resolveAll(dataSource, DEFAULT_SCHEMA_LOCATION);
+ }
+ }
+ catch (Exception e) {
+ // fallback to default
+ if (StringUtils.hasText(schemaProp)) {
+ schemaLocations = resolver.resolveAll(dataSource, schemaProp);
+ }
+ else {
+ schemaLocations = resolver.resolveAll(dataSource, DEFAULT_SCHEMA_LOCATION);
+ }
+ }
+ settings.setSchemaLocations(schemaLocations);
+
+ // Determine initialization mode
+ JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode init = properties.getInitializeSchema();
+ DatabaseInitializationMode mode;
+ if (JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.ALWAYS.equals(init)) {
+ mode = DatabaseInitializationMode.ALWAYS;
+ }
+ else if (JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.NEVER.equals(init)) {
+ mode = DatabaseInitializationMode.NEVER;
+ }
+ else {
+ // embedded or default
+ mode = DatabaseInitializationMode.EMBEDDED;
+ }
+ settings.setMode(mode);
+ settings.setContinueOnError(true);
+ return settings;
+ }
+
+}
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 6820c4237..b9a864a07 100644
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -13,4 +13,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-org.springframework.ai.model.chat.memory.jdbc.autoconfigure.JdbcChatMemoryAutoConfiguration
+org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure.JdbcChatMemoryRepositoryAutoConfiguration
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryHsqldbAutoConfigurationIT.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryHsqldbAutoConfigurationIT.java
new file mode 100644
index 000000000..cadfeb96b
--- /dev/null
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryHsqldbAutoConfigurationIT.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.Message;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = JdbcChatMemoryHsqldbAutoConfigurationIT.TestConfig.class,
+ properties = { "spring.datasource.url=jdbc:hsqldb:mem:chat_memory_auto_configuration_test;DB_CLOSE_DELAY=-1",
+ "spring.datasource.username=sa", "spring.datasource.password=",
+ "spring.datasource.driver-class-name=org.hsqldb.jdbcDriver",
+ "spring.ai.chat.memory.repository.jdbc.initialize-schema=always", "spring.sql.init.mode=always",
+ "spring.jpa.hibernate.ddl-auto=none", "spring.jpa.defer-datasource-initialization=true",
+ "spring.sql.init.continue-on-error=true", "spring.sql.init.schema-locations=classpath:schema.sql",
+ "logging.level.org.springframework.jdbc=DEBUG",
+ "logging.level.org.springframework.boot.sql.init=DEBUG" })
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
+@ImportAutoConfiguration({ org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration.class,
+ JdbcChatMemoryRepositoryAutoConfiguration.class,
+ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.class,
+ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class,
+ SqlInitializationAutoConfiguration.class })
+public class JdbcChatMemoryHsqldbAutoConfigurationIT {
+
+ @Autowired
+ private ApplicationContext context;
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ /**
+ * can't get the automatic loading of the schema with boot to work.
+ */
+ @Before
+ public void setUp() {
+ // Explicitly initialize the schema
+ try {
+ System.out.println("Explicitly initializing schema...");
+
+ // Debug: Print current schemas and tables
+ try {
+ List schemas = jdbcTemplate.queryForList("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA",
+ String.class);
+ System.out.println("Available schemas: " + schemas);
+
+ List tables = jdbcTemplate
+ .queryForList("SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES", String.class);
+ System.out.println("Available tables: " + tables);
+ }
+ catch (Exception e) {
+ System.out.println("Error listing schemas/tables: " + e.getMessage());
+ }
+
+ // Try a more direct approach with explicit SQL statements
+ try {
+ // Drop the table first if it exists to avoid any conflicts
+ jdbcTemplate.execute("DROP TABLE SPRING_AI_CHAT_MEMORY IF EXISTS");
+ System.out.println("Dropped existing table if it existed");
+
+ // Create the table with a simplified schema
+ jdbcTemplate.execute("CREATE TABLE SPRING_AI_CHAT_MEMORY (" + "conversation_id VARCHAR(36) NOT NULL, "
+ + "content LONGVARCHAR NOT NULL, " + "type VARCHAR(10) NOT NULL, "
+ + "timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL)");
+ System.out.println("Created table with simplified schema");
+
+ // Create index
+ jdbcTemplate.execute(
+ "CREATE INDEX SPRING_AI_CHAT_MEMORY_IDX ON SPRING_AI_CHAT_MEMORY(conversation_id, timestamp DESC)");
+ System.out.println("Created index");
+
+ // Verify table was created
+ boolean tableExists = jdbcTemplate.queryForObject(
+ "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'SPRING_AI_CHAT_MEMORY'",
+ Integer.class) > 0;
+ System.out.println("Table SPRING_AI_CHAT_MEMORY exists after creation: " + tableExists);
+ }
+ catch (Exception e) {
+ System.out.println("Error during direct table creation: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ System.out.println("Schema initialization completed");
+ }
+ catch (Exception e) {
+ System.out.println("Error during explicit schema initialization: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void useAutoConfiguredChatMemoryWithJdbc() {
+ // Check that the custom schema initializer is present
+ assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue();
+
+ // Debug: List all schema-hsqldb.sql resources on the classpath
+ try {
+ java.util.Enumeration resources = Thread.currentThread()
+ .getContextClassLoader()
+ .getResources("org/springframework/ai/chat/memory/jdbc/schema-hsqldb.sql");
+ System.out.println("--- schema-hsqldb.sql resources found on classpath ---");
+ while (resources.hasMoreElements()) {
+ System.out.println(resources.nextElement());
+ }
+ System.out.println("------------------------------------------------------");
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // Verify the table exists by executing a direct query
+ try {
+ boolean tableExists = jdbcTemplate.queryForObject(
+ "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'SPRING_AI_CHAT_MEMORY'",
+ Integer.class) > 0;
+ System.out.println("Table SPRING_AI_CHAT_MEMORY exists: " + tableExists);
+ assertThat(tableExists).isTrue();
+ }
+ catch (Exception e) {
+ System.out.println("Error checking table: " + e.getMessage());
+ e.printStackTrace();
+ fail("Failed to check if table exists: " + e.getMessage());
+ }
+
+ // Now test the ChatMemory functionality
+ assertThat(context.getBean(org.springframework.ai.chat.memory.ChatMemory.class)).isNotNull();
+ assertThat(context.getBean(org.springframework.ai.chat.memory.jdbc.JdbcChatMemoryRepository.class)).isNotNull();
+
+ var chatMemory = context.getBean(org.springframework.ai.chat.memory.ChatMemory.class);
+ var conversationId = java.util.UUID.randomUUID().toString();
+ var userMessage = new UserMessage("Message from the user");
+
+ chatMemory.add(conversationId, userMessage);
+ assertThat(chatMemory.get(conversationId)).hasSize(1);
+ assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage));
+
+ var assistantMessage = new AssistantMessage("Message from the assistant");
+ chatMemory.add(conversationId, List.of(assistantMessage));
+ assertThat(chatMemory.get(conversationId)).hasSize(2);
+ assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage, assistantMessage));
+
+ chatMemory.clear(conversationId);
+ assertThat(chatMemory.get(conversationId)).isEmpty();
+
+ var multipleMessages = List.of(new UserMessage("Message from the user 1"),
+ new AssistantMessage("Message from the assistant 1"));
+ chatMemory.add(conversationId, multipleMessages);
+ assertThat(chatMemory.get(conversationId)).hasSize(multipleMessages.size());
+ assertThat(chatMemory.get(conversationId)).isEqualTo(multipleMessages);
+ }
+
+ @SpringBootConfiguration
+ static class TestConfig {
+
+ }
+
+}
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java
similarity index 64%
rename from auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java
index 5961f25ee..1631eb707 100644
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryPostgresqlAutoConfigurationIT.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.ai.model.chat.memory.jdbc.autoconfigure;
+package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
import java.util.List;
import java.util.UUID;
@@ -42,27 +42,29 @@ import static org.assertj.core.api.Assertions.assertThat;
class JdbcChatMemoryPostgresqlAutoConfigurationIT {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
- .withConfiguration(AutoConfigurations.of(JdbcChatMemoryAutoConfiguration.class,
+ .withConfiguration(AutoConfigurations.of(JdbcChatMemoryRepositoryAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.url=jdbc:tc:postgresql:17:///");
@Test
void jdbcChatMemoryScriptDatabaseInitializer_shouldBeLoaded() {
- this.contextRunner.withPropertyValues("spring.ai.chat.memory.jdbc.initialize-schema=true")
- .run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
- this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=true")
+ this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=always")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
}
@Test
- void jdbcChatMemoryScriptDatabaseInitializer_shouldNotBeLoaded() {
- this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=false")
- .run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isFalse());
+ void jdbcChatMemoryScriptDatabaseInitializer_shouldNotRunSchemaInit() {
+ this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=never")
+ .run(context -> {
+ assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue();
+ // Optionally, check that the schema is not initialized (could check table
+ // absence if needed)
+ });
}
@Test
- void initializeSchemaEnabledWithProperty() {
- this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=true")
+ void initializeSchemaEmbeddedDefault() {
+ this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=embedded")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
}
@@ -94,38 +96,40 @@ class JdbcChatMemoryPostgresqlAutoConfigurationIT {
@Test
void useAutoConfiguredChatMemoryWithJdbc() {
- this.contextRunner.withConfiguration(AutoConfigurations.of(ChatMemoryAutoConfiguration.class)).run(context -> {
- assertThat(context).hasSingleBean(ChatMemory.class);
- assertThat(context).hasSingleBean(JdbcChatMemoryRepository.class);
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ChatMemoryAutoConfiguration.class))
+ .withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=always")
+ .run(context -> {
+ assertThat(context).hasSingleBean(ChatMemory.class);
+ assertThat(context).hasSingleBean(JdbcChatMemoryRepository.class);
- var chatMemory = context.getBean(ChatMemory.class);
- var conversationId = UUID.randomUUID().toString();
- var userMessage = new UserMessage("Message from the user");
+ var chatMemory = context.getBean(ChatMemory.class);
+ var conversationId = UUID.randomUUID().toString();
+ var userMessage = new UserMessage("Message from the user");
- chatMemory.add(conversationId, userMessage);
+ chatMemory.add(conversationId, userMessage);
- assertThat(chatMemory.get(conversationId)).hasSize(1);
- assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage));
+ assertThat(chatMemory.get(conversationId)).hasSize(1);
+ assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage));
- var assistantMessage = new AssistantMessage("Message from the assistant");
+ var assistantMessage = new AssistantMessage("Message from the assistant");
- chatMemory.add(conversationId, List.of(assistantMessage));
+ chatMemory.add(conversationId, List.of(assistantMessage));
- assertThat(chatMemory.get(conversationId)).hasSize(2);
- assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage, assistantMessage));
+ assertThat(chatMemory.get(conversationId)).hasSize(2);
+ assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage, assistantMessage));
- chatMemory.clear(conversationId);
+ chatMemory.clear(conversationId);
- assertThat(chatMemory.get(conversationId)).isEmpty();
+ assertThat(chatMemory.get(conversationId)).isEmpty();
- var multipleMessages = List.of(new UserMessage("Message from the user 1"),
- new AssistantMessage("Message from the assistant 1"));
+ var multipleMessages = List.of(new UserMessage("Message from the user 1"),
+ new AssistantMessage("Message from the assistant 1"));
- chatMemory.add(conversationId, multipleMessages);
+ chatMemory.add(conversationId, multipleMessages);
- assertThat(chatMemory.get(conversationId)).hasSize(multipleMessages.size());
- assertThat(chatMemory.get(conversationId)).isEqualTo(multipleMessages);
- });
+ assertThat(chatMemory.get(conversationId)).hasSize(multipleMessages.size());
+ assertThat(chatMemory.get(conversationId)).isEqualTo(multipleMessages);
+ });
}
}
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPropertiesTests.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPropertiesTests.java
similarity index 58%
rename from auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPropertiesTests.java
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPropertiesTests.java
index 196176149..50f851ef6 100644
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryPropertiesTests.java
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPropertiesTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.ai.model.chat.memory.jdbc.autoconfigure;
+package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
import org.junit.jupiter.api.Test;
@@ -23,21 +23,21 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jonathan Leijendekker
*/
-class JdbcChatMemoryPropertiesTests {
+class JdbcChatMemoryRepositoryPropertiesTests {
@Test
void defaultValues() {
- var props = new JdbcChatMemoryProperties();
-
- assertThat(props.isInitializeSchema()).isTrue();
+ var props = new JdbcChatMemoryRepositoryProperties();
+ assertThat(props.getInitializeSchema())
+ .isEqualTo(JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.EMBEDDED);
}
@Test
void customValues() {
- var props = new JdbcChatMemoryProperties();
- props.setInitializeSchema(false);
-
- assertThat(props.isInitializeSchema()).isFalse();
+ var props = new JdbcChatMemoryRepositoryProperties();
+ props.setInitializeSchema(JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.NEVER);
+ assertThat(props.getInitializeSchema())
+ .isEqualTo(JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.NEVER);
}
}
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializerPostgresqlTests.java
similarity index 84%
rename from auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests.java
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializerPostgresqlTests.java
index cd53f2bd8..d4d175fb7 100644
--- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/jdbc/autoconfigure/JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests.java
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializerPostgresqlTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.ai.model.chat.memory.jdbc.autoconfigure;
+package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
import javax.sql.DataSource;
@@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Jonathan Leijendekker
*/
@Testcontainers
-class JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests {
+class JdbcChatMemoryRepositorySchemaInitializerPostgresqlTests {
static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("postgres:17");
@@ -47,7 +47,7 @@ class JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests {
.withPassword("postgres");
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
- .withConfiguration(AutoConfigurations.of(JdbcChatMemoryAutoConfiguration.class,
+ .withConfiguration(AutoConfigurations.of(JdbcChatMemoryRepositoryAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class, DataSourceAutoConfiguration.class))
.withPropertyValues(String.format("spring.datasource.url=%s", postgresContainer.getJdbcUrl()),
String.format("spring.datasource.username=%s", postgresContainer.getUsername()),
@@ -57,7 +57,9 @@ class JdbcChatMemoryDataSourceScriptDatabaseInitializerPostgresqlTests {
void getSettings_shouldHaveSchemaLocations() {
this.contextRunner.run(context -> {
var dataSource = context.getBean(DataSource.class);
- var settings = JdbcChatMemoryDataSourceScriptDatabaseInitializer.getSettings(dataSource);
+ // Use new signature: requires JdbcChatMemoryRepositoryProperties
+ var settings = JdbcChatMemoryRepositorySchemaInitializer.getSettings(dataSource,
+ new JdbcChatMemoryRepositoryProperties());
assertThat(settings.getSchemaLocations())
.containsOnly("classpath:org/springframework/ai/chat/memory/jdbc/schema-postgresql.sql");
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemorySqlServerAutoConfigurationIT.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemorySqlServerAutoConfigurationIT.java
new file mode 100644
index 000000000..72281c19c
--- /dev/null
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemorySqlServerAutoConfigurationIT.java
@@ -0,0 +1,110 @@
+/*
+ * Integration test for SQL Server using Testcontainers, following the same structure as the PostgreSQL test.
+ */
+package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.ai.chat.memory.ChatMemory;
+import org.springframework.ai.chat.memory.jdbc.JdbcChatMemoryRepository;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.Message;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.testcontainers.containers.MSSQLServerContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Testcontainers
+class JdbcChatMemorySqlServerAutoConfigurationIT {
+
+ static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName
+ .parse("mcr.microsoft.com/mssql/server:2022-latest");
+
+ @Container
+ @SuppressWarnings("resource")
+ static MSSQLServerContainer> mssqlContainer = new MSSQLServerContainer<>(DEFAULT_IMAGE_NAME).acceptLicense()
+ .withEnv("MSSQL_DATABASE", "chat_memory_auto_configuration_test")
+ .withPassword("Strong!NotR34LLyPassword")
+ .withUrlParam("loginTimeout", "60") // Give more time for the login
+ .withUrlParam("connectRetryCount", "10") // Retry 10 times
+ .withUrlParam("connectRetryInterval", "10")
+ .withStartupTimeout(Duration.ofSeconds(60));
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(JdbcChatMemoryRepositoryAutoConfiguration.class,
+ JdbcTemplateAutoConfiguration.class, DataSourceAutoConfiguration.class))
+ .withPropertyValues(String.format("spring.datasource.url=%s", mssqlContainer.getJdbcUrl()),
+ String.format("spring.datasource.username=%s", mssqlContainer.getUsername()),
+ String.format("spring.datasource.password=%s", mssqlContainer.getPassword()));
+
+ @Test
+ void jdbcChatMemoryScriptDatabaseInitializer_shouldBeLoaded() {
+ this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=always")
+ .run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
+ }
+
+ @Test
+ void jdbcChatMemoryScriptDatabaseInitializer_shouldNotRunSchemaInit() {
+ this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=never")
+ .run(context -> {
+ assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue();
+ });
+ }
+
+ @Test
+ void initializeSchemaEmbeddedDefault() {
+ this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=embedded")
+ .run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
+ }
+
+ @Test
+ void useAutoConfiguredChatMemoryWithJdbc() {
+ this.contextRunner.withConfiguration(AutoConfigurations.of(ChatMemoryAutoConfiguration.class))
+ .withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=always")
+ .run(context -> {
+ assertThat(context).hasSingleBean(ChatMemory.class);
+ assertThat(context).hasSingleBean(JdbcChatMemoryRepository.class);
+
+ var chatMemory = context.getBean(ChatMemory.class);
+ var conversationId = UUID.randomUUID().toString();
+ var userMessage = new UserMessage("Message from the user");
+
+ chatMemory.add(conversationId, userMessage);
+
+ assertThat(chatMemory.get(conversationId)).hasSize(1);
+ assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage));
+
+ var assistantMessage = new AssistantMessage("Message from the assistant");
+
+ chatMemory.add(conversationId, List.of(assistantMessage));
+
+ assertThat(chatMemory.get(conversationId)).hasSize(2);
+ assertThat(chatMemory.get(conversationId)).isEqualTo(List.of(userMessage, assistantMessage));
+
+ chatMemory.clear(conversationId);
+
+ assertThat(chatMemory.get(conversationId)).isEmpty();
+
+ var multipleMessages = List.of(new UserMessage("Message from the user 1"),
+ new AssistantMessage("Message from the assistant 1"));
+
+ chatMemory.add(conversationId, multipleMessages);
+
+ assertThat(chatMemory.get(conversationId)).hasSize(multipleMessages.size());
+ assertThat(chatMemory.get(conversationId)).isEqualTo(multipleMessages);
+ });
+ }
+
+}
diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/resources/schema.sql b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/resources/schema.sql
new file mode 100644
index 000000000..2f34e27d3
--- /dev/null
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory-jdbc/src/test/resources/schema.sql
@@ -0,0 +1,12 @@
+-- Test-specific schema initialization for HSQLDB
+CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
+ conversation_id VARCHAR(36) NOT NULL,
+ content LONGVARCHAR NOT NULL,
+ type VARCHAR(10) NOT NULL,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+CREATE INDEX IF NOT EXISTS SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX ON SPRING_AI_CHAT_MEMORY(conversation_id, timestamp DESC);
+
+-- Add constraint if it doesn't exist
+ALTER TABLE SPRING_AI_CHAT_MEMORY ADD CONSTRAINT TYPE_CHECK CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'));
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/HsqldbChatMemoryDialect.java b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/HsqldbChatMemoryDialect.java
new file mode 100644
index 000000000..39e9770f6
--- /dev/null
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/HsqldbChatMemoryDialect.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.chat.memory.jdbc;
+
+/**
+ * HSQLDB-specific SQL dialect for chat memory repository.
+ */
+public class HsqldbChatMemoryDialect implements JdbcChatMemoryDialect {
+
+ @Override
+ public String getSelectMessagesSql() {
+ return "SELECT content, type FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ? ORDER BY timestamp ASC";
+ }
+
+ @Override
+ public String getInsertMessageSql() {
+ return "INSERT INTO SPRING_AI_CHAT_MEMORY (conversation_id, content, type, timestamp) VALUES (?, ?, ?, ?)";
+ }
+
+ @Override
+ public String getDeleteMessagesSql() {
+ return "DELETE FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ?";
+ }
+
+ @Override
+ public String getSelectConversationIdsSql() {
+ return "SELECT DISTINCT conversation_id FROM SPRING_AI_CHAT_MEMORY";
+ }
+
+}
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/JdbcChatMemoryDialect.java b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/JdbcChatMemoryDialect.java
new file mode 100644
index 000000000..a186dd595
--- /dev/null
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/JdbcChatMemoryDialect.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.chat.memory.jdbc;
+
+import javax.sql.DataSource;
+
+/**
+ * Abstraction for database-specific SQL for chat memory repository.
+ */
+public interface JdbcChatMemoryDialect {
+
+ /**
+ * Returns the SQL to fetch messages for a conversation, ordered by timestamp, with
+ * limit.
+ */
+ String getSelectMessagesSql();
+
+ /**
+ * Returns the SQL to insert a message.
+ */
+ String getInsertMessageSql();
+
+ /**
+ * Returns the SQL to fetch conversation IDs.
+ */
+ String getSelectConversationIdsSql();
+
+ /**
+ * Returns the SQL to delete all messages for a conversation.
+ */
+ String getDeleteMessagesSql();
+
+ /**
+ * Optionally, dialect can provide more advanced SQL as needed.
+ */
+
+ /**
+ * Detects the dialect from the DataSource or JDBC URL.
+ */
+ static JdbcChatMemoryDialect from(DataSource dataSource) {
+ // Simple detection (could be improved)
+ try {
+ String url = dataSource.getConnection().getMetaData().getURL().toLowerCase();
+ if (url.contains("postgresql"))
+ return new PostgresChatMemoryDialect();
+ if (url.contains("mysql"))
+ return new MysqlChatMemoryDialect();
+ if (url.contains("mariadb"))
+ return new MysqlChatMemoryDialect();
+ if (url.contains("sqlserver"))
+ return new SqlServerChatMemoryDialect();
+ if (url.contains("hsqldb"))
+ return new HsqldbChatMemoryDialect();
+ // Add more as needed
+ }
+ catch (Exception ignored) {
+ }
+ return new PostgresChatMemoryDialect(); // default
+ }
+
+}
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/JdbcChatMemoryRepository.java b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/JdbcChatMemoryRepository.java
index cf10413a5..3fbbd3ffb 100644
--- a/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/JdbcChatMemoryRepository.java
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/JdbcChatMemoryRepository.java
@@ -44,34 +44,25 @@ import org.springframework.util.Assert;
* @author Jonathan Leijendekker
* @author Thomas Vitale
* @author Linar Abzaltdinov
+ * @author Mark Pollack
* @since 1.0.0
*/
public class JdbcChatMemoryRepository implements ChatMemoryRepository {
- private static final String QUERY_GET_IDS = """
- SELECT DISTINCT conversation_id FROM ai_chat_memory
- """;
-
- private static final String QUERY_ADD = """
- INSERT INTO ai_chat_memory (conversation_id, content, type, "timestamp") VALUES (?, ?, ?, ?)
- """;
-
- private static final String QUERY_GET = """
- SELECT content, type FROM ai_chat_memory WHERE conversation_id = ? ORDER BY "timestamp"
- """;
-
- private static final String QUERY_CLEAR = "DELETE FROM ai_chat_memory WHERE conversation_id = ?";
-
private final JdbcTemplate jdbcTemplate;
- private JdbcChatMemoryRepository(JdbcTemplate jdbcTemplate) {
+ private final JdbcChatMemoryDialect dialect;
+
+ private JdbcChatMemoryRepository(JdbcTemplate jdbcTemplate, JdbcChatMemoryDialect dialect) {
Assert.notNull(jdbcTemplate, "jdbcTemplate cannot be null");
+ Assert.notNull(dialect, "dialect cannot be null");
this.jdbcTemplate = jdbcTemplate;
+ this.dialect = dialect;
}
@Override
public List findConversationIds() {
- List conversationIds = this.jdbcTemplate.query(QUERY_GET_IDS, rs -> {
+ List conversationIds = this.jdbcTemplate.query(dialect.getSelectConversationIdsSql(), rs -> {
var ids = new ArrayList();
while (rs.next()) {
ids.add(rs.getString(1));
@@ -84,7 +75,7 @@ public class JdbcChatMemoryRepository implements ChatMemoryRepository {
@Override
public List findByConversationId(String conversationId) {
Assert.hasText(conversationId, "conversationId cannot be null or empty");
- return this.jdbcTemplate.query(QUERY_GET, new MessageRowMapper(), conversationId);
+ return this.jdbcTemplate.query(dialect.getSelectMessagesSql(), new MessageRowMapper(), conversationId);
}
@Override
@@ -93,13 +84,14 @@ public class JdbcChatMemoryRepository implements ChatMemoryRepository {
Assert.notNull(messages, "messages cannot be null");
Assert.noNullElements(messages, "messages cannot contain null elements");
this.deleteByConversationId(conversationId);
- this.jdbcTemplate.batchUpdate(QUERY_ADD, new AddBatchPreparedStatement(conversationId, messages));
+ this.jdbcTemplate.batchUpdate(dialect.getInsertMessageSql(),
+ new AddBatchPreparedStatement(conversationId, messages));
}
@Override
public void deleteByConversationId(String conversationId) {
Assert.hasText(conversationId, "conversationId cannot be null or empty");
- this.jdbcTemplate.update(QUERY_CLEAR, conversationId);
+ this.jdbcTemplate.update(dialect.getDeleteMessagesSql(), conversationId);
}
private record AddBatchPreparedStatement(String conversationId, List messages,
@@ -154,6 +146,8 @@ public class JdbcChatMemoryRepository implements ChatMemoryRepository {
private JdbcTemplate jdbcTemplate;
+ private JdbcChatMemoryDialect dialect;
+
private Builder() {
}
@@ -162,8 +156,15 @@ public class JdbcChatMemoryRepository implements ChatMemoryRepository {
return this;
}
+ public Builder dialect(JdbcChatMemoryDialect dialect) {
+ this.dialect = dialect;
+ return this;
+ }
+
public JdbcChatMemoryRepository build() {
- return new JdbcChatMemoryRepository(this.jdbcTemplate);
+ if (this.dialect == null)
+ throw new IllegalStateException("Dialect must be set");
+ return new JdbcChatMemoryRepository(this.jdbcTemplate, this.dialect);
}
}
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/MysqlChatMemoryDialect.java b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/MysqlChatMemoryDialect.java
new file mode 100644
index 000000000..8bc7d2c21
--- /dev/null
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/MysqlChatMemoryDialect.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.chat.memory.jdbc;
+
+/**
+ * Dialect for MySQL.
+ *
+ * @author Mark Pollack
+ * @since 1.0.0
+ */
+public class MysqlChatMemoryDialect implements JdbcChatMemoryDialect {
+
+ @Override
+ public String getSelectMessagesSql() {
+ return "SELECT content, type FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ? ORDER BY `timestamp` DESC LIMIT ?";
+ }
+
+ @Override
+ public String getInsertMessageSql() {
+ return "INSERT INTO SPRING_AI_CHAT_MEMORY (conversation_id, content, type, `timestamp`) VALUES (?, ?, ?, ?)";
+ }
+
+ @Override
+ public String getSelectConversationIdsSql() {
+ return "SELECT DISTINCT conversation_id FROM SPRING_AI_CHAT_MEMORY";
+ }
+
+ @Override
+ public String getDeleteMessagesSql() {
+ return "DELETE FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ?";
+ }
+
+}
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/PostgresChatMemoryDialect.java b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/PostgresChatMemoryDialect.java
new file mode 100644
index 000000000..424eb1aed
--- /dev/null
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/PostgresChatMemoryDialect.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.chat.memory.jdbc;
+
+/**
+ * Dialect for Postgres.
+ *
+ * @author Mark Pollack
+ * @since 1.0.0
+ */
+public class PostgresChatMemoryDialect implements JdbcChatMemoryDialect {
+
+ @Override
+ public String getSelectMessagesSql() {
+ return "SELECT content, type FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ? ORDER BY \"timestamp\"";
+ }
+
+ @Override
+ public String getInsertMessageSql() {
+ return "INSERT INTO SPRING_AI_CHAT_MEMORY (conversation_id, content, type, \"timestamp\") VALUES (?, ?, ?, ?)";
+ }
+
+ @Override
+ public String getSelectConversationIdsSql() {
+ return "SELECT DISTINCT conversation_id FROM SPRING_AI_CHAT_MEMORY";
+ }
+
+ @Override
+ public String getDeleteMessagesSql() {
+ return "DELETE FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ?";
+ }
+
+}
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/SqlServerChatMemoryDialect.java b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/SqlServerChatMemoryDialect.java
new file mode 100644
index 000000000..48704a5f1
--- /dev/null
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/SqlServerChatMemoryDialect.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ai.chat.memory.jdbc;
+
+/**
+ * Dialect for SQL Server.
+ *
+ * @author Mark Pollack
+ * @since 1.0.0
+ */
+public class SqlServerChatMemoryDialect implements JdbcChatMemoryDialect {
+
+ @Override
+ public String getSelectMessagesSql() {
+ return "SELECT content, type FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ? ORDER BY [timestamp] ASC";
+ }
+
+ @Override
+ public String getInsertMessageSql() {
+ return "INSERT INTO SPRING_AI_CHAT_MEMORY (conversation_id, content, type, [timestamp]) VALUES (?, ?, ?, ?)";
+ }
+
+ @Override
+ public String getSelectConversationIdsSql() {
+ return "SELECT DISTINCT conversation_id FROM SPRING_AI_CHAT_MEMORY";
+ }
+
+ @Override
+ public String getDeleteMessagesSql() {
+ return "DELETE FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ?";
+ }
+
+}
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/aot/hint/JdbcChatMemoryRepositoryRuntimeHints.java b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/aot/hint/JdbcChatMemoryRepositoryRuntimeHints.java
index 3b518733f..3ce817185 100644
--- a/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/aot/hint/JdbcChatMemoryRepositoryRuntimeHints.java
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/java/org/springframework/ai/chat/memory/jdbc/aot/hint/JdbcChatMemoryRepositoryRuntimeHints.java
@@ -34,9 +34,7 @@ class JdbcChatMemoryRepositoryRuntimeHints implements RuntimeHintsRegistrar {
hints.reflection()
.registerType(DataSource.class, hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
- hints.resources()
- .registerPattern("org/springframework/ai/chat/memory/jdbc/schema-mariadb.sql")
- .registerPattern("org/springframework/ai/chat/memory/jdbc/schema-postgresql.sql");
+ hints.resources().registerPattern("org/springframework/ai/chat/memory/jdbc/schema-*.sql");
}
}
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-hsqldb.sql b/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-hsqldb.sql
new file mode 100644
index 000000000..da29e6b3b
--- /dev/null
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-hsqldb.sql
@@ -0,0 +1,10 @@
+CREATE TABLE SPRING_AI_CHAT_MEMORY (
+ conversation_id VARCHAR(36) NOT NULL,
+ content LONGVARCHAR NOT NULL,
+ type VARCHAR(10) NOT NULL,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
+);
+
+CREATE INDEX SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX ON SPRING_AI_CHAT_MEMORY(conversation_id, timestamp DESC);
+
+ALTER TABLE SPRING_AI_CHAT_MEMORY ADD CONSTRAINT TYPE_CHECK CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'));
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-mariadb.sql b/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-mariadb.sql
index eb17a22be..6a0ef8755 100644
--- a/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-mariadb.sql
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-mariadb.sql
@@ -1,10 +1,10 @@
-CREATE TABLE IF NOT EXISTS ai_chat_memory (
+CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
conversation_id VARCHAR(36) NOT NULL,
content TEXT NOT NULL,
type VARCHAR(10) NOT NULL,
`timestamp` TIMESTAMP NOT NULL,
- CONSTRAINT type_check CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
+ CONSTRAINT TYPE_CHECK CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
);
-CREATE INDEX IF NOT EXISTS ai_chat_memory_conversation_id_timestamp_idx
-ON ai_chat_memory(conversation_id, `timestamp`);
\ No newline at end of file
+CREATE INDEX IF NOT EXISTS SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX
+ON SPRING_AI_CHAT_MEMORY(conversation_id, `timestamp`);
\ No newline at end of file
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-postgresql.sql b/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-postgresql.sql
index 3061e8a15..0733cbde5 100644
--- a/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-postgresql.sql
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-postgresql.sql
@@ -1,9 +1,9 @@
-CREATE TABLE IF NOT EXISTS ai_chat_memory (
+CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
conversation_id VARCHAR(36) NOT NULL,
content TEXT NOT NULL,
type VARCHAR(10) NOT NULL CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL')),
"timestamp" TIMESTAMP NOT NULL
);
-CREATE INDEX IF NOT EXISTS ai_chat_memory_conversation_id_timestamp_idx
-ON ai_chat_memory(conversation_id, "timestamp");
\ No newline at end of file
+CREATE INDEX IF NOT EXISTS SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX
+ON SPRING_AI_CHAT_MEMORY(conversation_id, "timestamp");
\ No newline at end of file
diff --git a/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-sqlserver.sql b/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-sqlserver.sql
new file mode 100644
index 000000000..1053e7d5b
--- /dev/null
+++ b/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc/schema-sqlserver.sql
@@ -0,0 +1,9 @@
+CREATE TABLE SPRING_AI_CHAT_MEMORY (
+ conversation_id VARCHAR(36) NOT NULL,
+ content NVARCHAR(MAX) NOT NULL,
+ type VARCHAR(10) NOT NULL,
+ [timestamp] DATETIME2 NOT NULL DEFAULT SYSDATETIME(),
+ CONSTRAINT TYPE_CHECK CHECK (type IN ('USER', 'ASSISTANT', 'SYSTEM', 'TOOL'))
+);
+
+CREATE INDEX SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX ON SPRING_AI_CHAT_MEMORY(conversation_id, [timestamp] DESC);
diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat-memory.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat-memory.adoc
index 3b8253c9f..a0d467019 100644
--- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat-memory.adoc
+++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat-memory.adoc
@@ -5,7 +5,7 @@ Large language models (LLMs) are stateless, meaning they do not retain informati
The `ChatMemory` abstraction allows you to implement various types of memory to support different use cases. The underlying storage of the messages is handled by the `ChatMemoryRepository`, whose sole responsibility is to store and retrieve messages. It's up to the `ChatMemory` implementation to decide which messages to keep and when to remove them. Examples of strategies could include keeping the last N messages, keeping messages for a certain time period, or keeping messages up to a certain token limit.
-Before choosing a memory type, it's important to understand the difference between chat memory and chat history.
+Before choosing a memory type, it's essential to understand the difference between chat memory and chat history.
* *Chat Memory*. The information that a large-language model retains and uses to maintain contextual awareness throughout a conversation.
* *Chat History*. The entire conversation history, including all messages exchanged between the user and the model.
@@ -66,7 +66,7 @@ ChatMemoryRepository repository = new InMemoryChatMemoryRepository();
=== JdbcChatMemoryRepository
-`JdbcChatMemoryRepository` is a built-in implementation that uses JDBC to store messages in a relational database. It is suitable for applications that require persistent storage of chat memory.
+`JdbcChatMemoryRepository` is a built-in implementation that uses JDBC to store messages in a relational database. It supports multiple databases out-of-the-box and is suitable for applications that require persistent storage of chat memory.
First, add the following dependency to your project:
@@ -105,12 +105,13 @@ ChatMemory chatMemory = MessageWindowChatMemory.builder()
.build();
----
-If you'd rather create the `JdbcChatMemoryRepository` manually, you can do so by providing a `JdbcTemplate` instance:
+If you'd rather create the `JdbcChatMemoryRepository` manually, you can do so by providing a `JdbcTemplate` instance and optionally a custom `JdbcChatMemoryDialect`:
[source,java]
----
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
+ .dialect(new PostgresChatMemoryDialect()) // Optional: custom dialect, auto-detected by default
.build();
ChatMemory chatMemory = MessageWindowChatMemory.builder()
@@ -119,22 +120,57 @@ ChatMemory chatMemory = MessageWindowChatMemory.builder()
.build();
----
+==== Supported Databases and Dialect Abstraction
+
+Spring AI supports multiple relational databases via a dialect abstraction. The following databases are supported out-of-the-box:
+
+- PostgreSQL
+- MySQL / MariaDB
+- SQL Server
+- HSQLDB
+
+The correct dialect is auto-detected from the JDBC URL. You can extend support for other databases by implementing the `JdbcChatMemoryDialect` interface.
+
==== Configuration Properties
[cols="2,5,1",stripes=even]
|===
|Property | Description | Default Value
-| `spring.ai.chat.memory.repository.jdbc.initialize-schema` | Whether to initialize the schema on startup. | `true`
+| `spring.ai.chat.memory.repository.jdbc.initialize-schema` | Controls when to initialize the schema. Values: `embedded` (default), `always`, `never`. | `embedded`
+| `spring.ai.chat.memory.repository.jdbc.schema` | Location of the schema script to use for initialization. Supports `classpath:` URLs and platform placeholders. | `classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql`
|===
-
==== Schema Initialization
-The auto-configuration will automatically create the `ai_chat_memory` table using the JDBC driver. Currently, only PostgreSQL and MariaDB are supported.
+The auto-configuration will automatically create the `SPRING_AI_CHAT_MEMORY` table on startup, using a vendor-specific SQL script for your database. By default, schema initialization runs only for embedded databases (H2, HSQL, Derby, etc.).
-You can disable the schema initialization by setting the property `spring.ai.chat.memory.repository.jdbc.initialize-schema` to `false`.
+You can control schema initialization using the `spring.ai.chat.memory.repository.jdbc.initialize-schema` property:
-If your project uses a tool like Flyway or Liquibase to manage your database schemas, you can disable the schema initialization and refer to link:https://github.com/spring-projects/spring-ai/tree/main/memory/spring-ai-model-chat-memory-jdbc/src/main/resources/org/springframework/ai/chat/memory/jdbc[these SQL scripts] for configuring those tools to create the `ai_chat_memory` table.
+[source,properties]
+----
+spring.ai.chat.memory.repository.jdbc.initialize-schema=embedded # Only for embedded DBs (default)
+spring.ai.chat.memory.repository.jdbc.initialize-schema=always # Always initialize
+spring.ai.chat.memory.repository.jdbc.initialize-schema=never # Never initialize (useful with Flyway/Liquibase)
+----
+
+To override the schema script location, use:
+
+[source,properties]
+----
+spring.ai.chat.memory.repository.jdbc.schema=classpath:/custom/path/schema-mysql.sql
+----
+
+==== Extending Dialects
+
+To add support for a new database, implement the `JdbcChatMemoryDialect` interface and provide SQL for selecting, inserting, and deleting messages. You can then pass your custom dialect to the repository builder.
+
+[source,java]
+----
+ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
+ .jdbcTemplate(jdbcTemplate)
+ .dialect(new MyCustomDbDialect())
+ .build();
+----
=== Neo4j ChatMemoryRepository