H2是一个开源的嵌入式关系型数据库,具有以下核心特性:
SpringBoot对H2的自动配置提供了以下便利:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JdbcConnection.class, EmbeddedDatabaseType.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(H2ConsoleProperties.class)
public class H2ConsoleAutoConfiguration {
@Bean
@ConditionalOnProperty(prefix = "spring.h2.console", name = "enabled", havingValue = "true")
public ServletRegistrationBean h2Console() {
// H2控制台Servlet注册
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
public static class EmbeddedDatabaseConfiguration {
}
}
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware {
private ClassLoader classLoader;
private String name;
private boolean generateUniqueName = false;
private String driverClassName;
private String url;
private String username;
private String password;
private String jndiName;
private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED;
// getters and setters
}
class EmbeddedDatabaseCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
DataSourceProperties properties = new DataSourceProperties();
properties.setBeanClassLoader(context.getClassLoader());
properties.setEnvironment(context.getEnvironment());
// 检查是否配置了显式数据源
if (properties.getUrl() != null) {
return ConditionOutcome.noMatch("Existing non-embedded datasource configured");
}
// 检查嵌入式数据库类型
EmbeddedDatabaseType type = properties.getEmbeddedDatabaseConnection();
if (type == null) {
return ConditionOutcome.noMatch("No embedded database connection");
}
return ConditionOutcome.match("Embedded database " + type + " found");
}
}
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@AutoConfiguration(before = { XADataSourceAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class,
HibernateJpaAutoConfiguration.class })
public class DataSourceAutoConfiguration {
// 数据源自动配置类
}
@Configuration(proxyBeanMethods = false)
public class EmbeddedDataSourceConfiguration {
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(DataSource.class)
public EmbeddedDatabase dataSource(DataSourceProperties properties) {
return new EmbeddedDatabaseBuilder()
.setType(properties.determineEmbeddedDatabaseType())
.setName(properties.determineDatabaseName())
.build();
}
}
public class DataSourceProperties {
public EmbeddedDatabaseType determineEmbeddedDatabaseType() {
if (this.embeddedDatabaseConnection != null) {
return this.embeddedDatabaseConnection;
}
return EmbeddedDatabaseConnection.get(getClassLoader()).getType();
}
public String determineDatabaseName() {
if (this.generateUniqueName) {
return UUID.randomUUID().toString();
}
return (this.name != null) ? this.name : "testdb";
}
}
public class DataSourceInitializer {
private void initSchema() {
List scripts = getScripts("spring.datasource.schema",
this.properties.getSchema(), "schema");
if (!scripts.isEmpty()) {
executeScripts(scripts);
}
}
private List getScripts(String propertyName,
List locations, String fallback) {
if (locations != null) {
return getResources(propertyName, locations, true);
}
return getResources(propertyName,
Collections.singletonList("classpath*:" + fallback + "-" +
this.platform + ".sql"),
false);
}
}
private void executeScripts(List resources) {
for (Resource resource : resources) {
ScriptUtils.executeSqlScript(this.dataSource.getConnection(),
new EncodedResource(resource));
}
}
@Bean
public ServletRegistrationBean h2Console(H2ConsoleProperties properties) {
String path = properties.getPath();
String urlMapping = path + (path.endsWith("/") ? "*" : "/*");
ServletRegistrationBean registration = new ServletRegistrationBean<>(
new WebServlet(), urlMapping);
properties.getSettings().forEach(registration::addInitParameter);
registration.setLoadOnStartup(properties.getLoadOnStartup());
return registration;
}
@ConfigurationProperties(prefix = "spring.h2.console")
public class H2ConsoleProperties {
private boolean enabled = false;
private String path = "/h2-console";
private Map settings = new HashMap<>();
private int loadOnStartup = -1;
// getters and setters
}
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class TestDatabaseAutoConfiguration {
@Bean
@ConditionalOnMissingBean(DataSource.class)
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.build();
}
}
public class H2DatabaseShutdownExecutor implements DisposableBean {
private final DataSource dataSource;
@Override
public void destroy() throws Exception {
try (Connection connection = dataSource.getConnection()) {
Statement statement = connection.createStatement();
statement.execute("SHUTDOWN");
}
}
}
@Configuration
public class CustomH2Initializer {
@Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(new ClassPathResource("custom-schema.sql"));
populator.addScript(new ClassPathResource("test-data.sql"));
initializer.setDatabasePopulator(populator);
return initializer;
}
}
@Configuration
public class AdvancedH2Config {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("advancedDb")
.addScript("classpath:com/example/schema.sql")
.addScript("classpath:com/example/test-data.sql")
.setSeparator("@@") // 自定义语句分隔符
.continueOnError(true) // 出错继续执行
.build();
}
}
@Configuration
public class H2ConnectionPoolConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.url("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1")
.driverClassName("org.h2.Driver")
.build();
}
}
public class H2MemoryOptimizer {
public void optimizeMemorySettings(DataSource dataSource) throws SQLException {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
// 设置缓存大小
stmt.execute("SET CACHE_SIZE 32768");
// 关闭日志减少I/O
stmt.execute("SET TRACE_LEVEL_FILE 0");
// 优化内存使用
stmt.execute("SET MAX_MEMORY_ROWS 10000");
}
}
}
Propertieslogging.level.org.springframework.jdbc.datasource.init=DEBUG
logging.level.org.h2=TRACE
logging.level.com.zaxxer.hikari=DEBUG
通过本文的深度解析,我们全面掌握了SpringBoot中内嵌H2数据库的自动初始化逻辑。从自动配置触发条件到数据源创建过程,从脚本初始化机制到控制台集成方案,这套体系为开发测试环境提供了便捷高效的数据库解决方案。合理运用这些机制,可以显著提升开发效率和测试便利性。