Align JdbcChatMemoryRepositoryProperties with other modules in Spring Boot

Such as `BatchProperties.Jdbc`, `IntegrationProperties.Jdbc`, `JdbcSessionProperties` and `QuartzProperties.Jdbc`

1. Reuse Spring Boot's `DatabaseInitializationMode`
2. Allow to customize platform
3. add `OnJdbcChatMemoryRepositoryDatasourceInitializationCondition` for `JdbcChatMemoryRepositorySchemaInitializer`

Signed-off-by: Yanming Zhou <zhouyanming@gmail.com>
This commit is contained in:
Yanming Zhou
2025-05-12 11:33:48 +08:00
committed by Ilayaperumal Gopinathan
parent a6bb325b62
commit e10fbde7a1
6 changed files with 56 additions and 79 deletions

View File

@@ -18,9 +18,6 @@ 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;
@@ -28,13 +25,16 @@ 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.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* @author Jonathan Leijendekker
* @author Thomas Vitale
* @author Yanming Zhou
* @since 1.0.0
*/
@AutoConfiguration(after = JdbcTemplateAutoConfiguration.class, before = ChatMemoryAutoConfiguration.class)
@@ -51,9 +51,19 @@ public class JdbcChatMemoryRepositoryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnJdbcChatMemoryRepositoryDatasourceInitializationCondition.class)
JdbcChatMemoryRepositorySchemaInitializer jdbcChatMemoryScriptDatabaseInitializer(DataSource dataSource,
JdbcChatMemoryRepositoryProperties properties) {
return new JdbcChatMemoryRepositorySchemaInitializer(dataSource, properties);
}
static class OnJdbcChatMemoryRepositoryDatasourceInitializationCondition extends OnDatabaseInitializationCondition {
OnJdbcChatMemoryRepositoryDatasourceInitializationCondition() {
super("Jdbc Chat Memory Repository",
JdbcChatMemoryRepositoryProperties.CONFIG_PREFIX + ".initialize-schema");
}
}
}

View File

@@ -17,10 +17,12 @@
package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
/**
* @author Jonathan Leijendekker
* @author Thomas Vitale
* @author Yanming Zhou
* @since 1.0.0
*/
@ConfigurationProperties(JdbcChatMemoryRepositoryProperties.CONFIG_PREFIX)
@@ -28,6 +30,8 @@ public class JdbcChatMemoryRepositoryProperties {
public static final String CONFIG_PREFIX = "spring.ai.chat.memory.repository.jdbc";
private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/springframework/ai/chat/memory/jdbc/schema-@@platform@@.sql";
/**
* Whether to initialize the schema on startup. Values: embedded, always, never.
* Default is embedded.
@@ -38,7 +42,13 @@ public class JdbcChatMemoryRepositoryProperties {
* 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";
private String schema = DEFAULT_SCHEMA_LOCATION;
/**
* Platform to use in initialization scripts if the @@platform@@ placeholder is used.
* Auto-detected by default.
*/
private String platform;
public DatabaseInitializationMode getInitializeSchema() {
return this.initializeSchema;
@@ -48,6 +58,14 @@ public class JdbcChatMemoryRepositoryProperties {
this.initializeSchema = initializeSchema;
}
public String getPlatform() {
return platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public String getSchema() {
return this.schema;
}
@@ -56,23 +74,4 @@ public class JdbcChatMemoryRepositoryProperties {
this.schema = schema;
}
public enum DatabaseInitializationMode {
/**
* Always initialize the database.
*/
ALWAYS,
/**
* Only initialize an embedded database.
*/
EMBEDDED,
/**
* Never initialize the database.
*/
NEVER
}
}

View File

@@ -22,7 +22,6 @@ 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;
@@ -30,12 +29,11 @@ import org.springframework.util.StringUtils;
* Performs database initialization for the JDBC Chat Memory Repository.
*
* @author Mark Pollack
* @author Yanming Zhou
* @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));
}
@@ -43,50 +41,19 @@ class JdbcChatMemoryRepositorySchemaInitializer extends DataSourceScriptDatabase
static DatabaseInitializationSettings getSettings(DataSource dataSource,
JdbcChatMemoryRepositoryProperties properties) {
var settings = new DatabaseInitializationSettings();
// Determine schema locations
String schemaProp = properties.getSchema();
List<String> 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.setSchemaLocations(resolveSchemaLocations(dataSource, properties));
settings.setMode(properties.getInitializeSchema());
settings.setContinueOnError(true);
return settings;
}
private static List<String> resolveSchemaLocations(DataSource dataSource,
JdbcChatMemoryRepositoryProperties properties) {
PlatformPlaceholderDatabaseDriverResolver platformResolver = new PlatformPlaceholderDatabaseDriverResolver();
if (StringUtils.hasText(properties.getPlatform())) {
return platformResolver.resolveAll(properties.getPlatform(), properties.getSchema());
}
return platformResolver.resolveAll(dataSource, properties.getSchema());
}
}

View File

@@ -38,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Jonathan Leijendekker
* @author Thomas Vitale
* @author Linar Abzaltdinov
* @author Yanming Zhou
*/
class JdbcChatMemoryPostgresqlAutoConfigurationIT {
@@ -49,14 +50,14 @@ class JdbcChatMemoryPostgresqlAutoConfigurationIT {
@Test
void jdbcChatMemoryScriptDatabaseInitializer_shouldBeLoaded() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=always")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
.run(context -> assertThat(context).hasBean("jdbcChatMemoryScriptDatabaseInitializer"));
}
@Test
void jdbcChatMemoryScriptDatabaseInitializer_shouldNotRunSchemaInit() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=never")
.run(context -> {
assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue();
assertThat(context).doesNotHaveBean("jdbcChatMemoryScriptDatabaseInitializer");
// Optionally, check that the schema is not initialized (could check table
// absence if needed)
});
@@ -65,7 +66,7 @@ class JdbcChatMemoryPostgresqlAutoConfigurationIT {
@Test
void initializeSchemaEmbeddedDefault() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=embedded")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
.run(context -> assertThat(context).hasBean("jdbcChatMemoryScriptDatabaseInitializer"));
}
@Test

View File

@@ -18,6 +18,8 @@ package org.springframework.ai.model.chat.memory.repository.jdbc.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
import static org.assertj.core.api.Assertions.assertThat;
/**
@@ -28,16 +30,14 @@ class JdbcChatMemoryRepositoryPropertiesTests {
@Test
void defaultValues() {
var props = new JdbcChatMemoryRepositoryProperties();
assertThat(props.getInitializeSchema())
.isEqualTo(JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.EMBEDDED);
assertThat(props.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.EMBEDDED);
}
@Test
void customValues() {
var props = new JdbcChatMemoryRepositoryProperties();
props.setInitializeSchema(JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.NEVER);
assertThat(props.getInitializeSchema())
.isEqualTo(JdbcChatMemoryRepositoryProperties.DatabaseInitializationMode.NEVER);
props.setInitializeSchema(DatabaseInitializationMode.NEVER);
assertThat(props.getInitializeSchema()).isEqualTo(DatabaseInitializationMode.NEVER);
}
}

View File

@@ -52,21 +52,21 @@ class JdbcChatMemorySqlServerAutoConfigurationIT {
@Test
void jdbcChatMemoryScriptDatabaseInitializer_shouldBeLoaded() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=always")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
.run(context -> assertThat(context).hasBean("jdbcChatMemoryScriptDatabaseInitializer"));
}
@Test
void jdbcChatMemoryScriptDatabaseInitializer_shouldNotRunSchemaInit() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=never")
.run(context -> {
assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue();
assertThat(context).doesNotHaveBean("jdbcChatMemoryScriptDatabaseInitializer");
});
}
@Test
void initializeSchemaEmbeddedDefault() {
this.contextRunner.withPropertyValues("spring.ai.chat.memory.repository.jdbc.initialize-schema=embedded")
.run(context -> assertThat(context.containsBean("jdbcChatMemoryScriptDatabaseInitializer")).isTrue());
.run(context -> assertThat(context).hasBean("jdbcChatMemoryScriptDatabaseInitializer"));
}
@Test