我正在使用 Java Spring Boot 设置微服务。该应用程序在 Docker 容器中运行,并连接到本地计算机上的 MySQL 数据库。出于测试目的,我想避免 MySQL 的开销,并使用 H2 内存数据库进行单元测试。
以下文件是配置的一部分:build.gradle、application.properties、application-test.properties 和 Dockerfile
尽管遵循了一些在线指南,我还是遇到了构建错误:
java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@44976b08 testClass = com.apptraqer.apptraqerauthservice.ApptraqerAuthServiceApplicationTests, locations = [], classes = [com.apptraqer.apptraqerauthservice.ApptraqerAuthServiceApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@5023bb8b, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@604f2bd2, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@30b19518, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@62e20a76, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@28e8dde3, org.springframework.boot.test.web.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory$DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer@59252cb6, org.springframework.test.context.support.DynamicPropertiesContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestAnnotation@cc9a5aaa], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:180)
<snip>
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to resolve name [org.hibernate.dialect.MySQL5Dialect] as strategy [org.hibernate.dialect.Dialect]
at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1802)
at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601)
<snip>
at app//org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:108)
at app//org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225)
at app//org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152)
... 19 more
Caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to resolve name [org.hibernate.dialect.MySQL5Dialect] as strategy [org.hibernate.dialect.Dialect]
at app//org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:276)
at app//org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238)
<snip>
at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1849)
at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1798)
... 39 more
Caused by: org.hibernate.boot.registry.selector.spi.StrategySelectionException: Unable to resolve name [org.hibernate.dialect.MySQL5Dialect] as strategy [org.hibernate.dialect.Dialect]
at app//org.hibernate.boot.registry.selector.internal.StrategySelectorImpl.selectStrategyImplementor(StrategySelectorImpl.java:154)
at app//org.hibernate.boot.registry.selector.internal.StrategySelectorImpl.resolveStrategy(StrategySelectorImpl.java:236)
<snip>
at app//org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130)
at app//org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
... 54 more
Caused by: org.hibernate.boot.registry.classloading.spi.ClassLoadingException: Unable to load class [org.hibernate.dialect.MySQL5Dialect]
at app//org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.classForName(ClassLoaderServiceImpl.java:126)
at app//org.hibernate.boot.registry.selector.internal.StrategySelectorImpl.selectStrategyImplementor(StrategySelectorImpl.java:150)
... 64 more
Caused by: java.lang.ClassNotFoundException: Could not load requested class : org.hibernate.dialect.MySQL5Dialect
at org.hibernate.boot.registry.classloading.internal.AggregatedClassLoader.findClass(AggregatedClassLoader.java:216)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:467)
at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.classForName(ClassLoaderServiceImpl.java:123)
... 65 more
可以在此处找到具有我当前设置的存储库: GitHub 存储库:[apptraqer(ehc/ArchitectureOverview 分支)]1
配置 Spring Boot 使用 H2 进行单元测试,同时保留 MySQL 作为运行时配置的最佳方法是什么?我希望获得有关如何调整上述文件并解决这些错误的指导。
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.0'
id 'io.spring.dependency-management' version '1.1.6'
id 'application'
}
application {
mainClass = 'com.apptraqer.apptraqerauthservice.ApptraqerAuthServiceApplication'
}
bootJar {
mainClass = 'com.apptraqer.apptraqerauthservice.ApptraqerAuthServiceApplication'
}
group = 'com.apptraqer'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
// Spring Boot Starters
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
// OAuth2 and Security Enhancements
implementation 'org.springframework.security:spring-security-oauth2-jose'
// JSON Web Token (JJWT)
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-orgjson:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
// Database Connector
runtimeOnly 'mysql:mysql-connector-java:8.0.26'
// Testing Dependencies
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'com.h2database:h2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'javax.persistence:javax.persistence-api:2.2'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
}
tasks.named('test') {
useJUnitPlatform()
}
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:deprecation"
}
我的印象是我需要有两个 application.properties,一个用于 Docker 容器,一个用于测试:
应用程序属性
# application.properties (Common Settings for All Environments)
# Application Name and Port (could be overridden per environment if needed)
spring.application.name=apptraqer-auth-service
server.port=8081
# Database Configuration (default to MySQL for non-test environments)
spring.datasource.url=jdbc:mysql://apptraqer-mysql:3306/apptraqerdata
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Hibernate/JPA Settings (should be the same across all environments)
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
应用程序测试.properties
# application-test.properties (Test Environment Overrides)
# Override Database Configuration for Test with H2 (or whatever you use in tests)
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
#H2 settings
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
Dockerfile
FROM openjdk:17-jdk-slim as build
WORKDIR /app
COPY build.gradle settings.gradle ./
COPY gradlew ./
COPY gradle/wrapper/ ./gradle/wrapper/
ENV SPRING_PROFILES_ACTIVE=test
RUN ./gradlew build --no-daemon
COPY src ./src
RUN ./gradlew build
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=build /app/build/libs/apptraqer-auth-service.jar apptraqer-auth-service.jar
EXPOSE 8081
CMD ["java", "-jar", "apptraqer-auth-service.jar"]
以下是我必须做的更改才能正确构建应用程序。
为了运行单元测试而简单直接地配置 H2,我们可以使用 @AutoConfigureTestDatabase,如 Spring Boot 文档中自动配置数据 JPA 测试部分末尾所述。这将根据我们在类路径中添加的内容(在本例中为 H2)配置测试数据源。 这是我们如何在代码中添加注释的示例
@SpringBootTest
@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
class UserTest{}
请注意,上述内容仅用于替换我们在应用程序中创建的数据源 bean。但是,可以将数据源属性移动到单独的配置文件中,分别用于测试和应用程序,例如将 MySQL 数据源配置放在名为 application-docker.yaml 的文件中,而不在任何其他 yaml 文件中提供任何数据源配置。这样,当我们的数据源自动配置时,它并不总是知道 MySQL 配置(除非配置文件 docker 处于活动状态)。在此设置中,Spring Boot 可以检测类路径上的 H2 并自动为我们配置 H2。部署时,我们可以在 docker 中运行应用程序,配置文件 docker active 指示 spring boot 连接到 MySQL。同样,当运行单元测试时,我们不会激活配置文件 docker,然后 Spring Boot 将配置 H2。
我能找到的其他一些更正是。
implementation 'javax.persistence:javax.persistence-api:2.2'
并且 JPA 导入必须来自 jakarta 包,该包由 spring boot starter jpa 间接拉取@ActiveProfiles(profiles = {"test"})
希望这有帮助。