SpringBoot整合JOOQ测试遇UnsatisfiedDependencyException问题求助
问题排查与优化方案
我基于SpringBoot 2.7.10和JOOQ 3.14开发了基础CRUD API,现有测试代码无法运行,遇到org.springframework.beans.factory.UnsatisfiedDependencyException异常:测试类无法注入DSLContext Bean,移除BaseRepositoryTest中的@SpringBootApplication(exclude = {R2dbcAutoConfiguration.class})注解时,抽象类也会触发该异常。以下是复现代码,以及对应的问题排查和优化方案。
复现代码
模型类Owner
package com.data.platform.core.models; import java.util.Date; import java.util.UUID; public class Owner { private UUID ownerId; private String ownerName; private String ownerEmail; private Date updatedAt; private Date createdAt; public Owner() { } public Owner(UUID ownerId, String ownerName, String ownerEmail, Date updatedAt, Date createdAt) { this.ownerId = ownerId; this.ownerName = ownerName; this.ownerEmail = ownerEmail; this.updatedAt = updatedAt; this.createdAt = createdAt; } public UUID getOwnerId() { return ownerId; } public void setOwnerId(UUID ownerId) { this.ownerId = ownerId; } public String getOwnerName() { return ownerName; } public void setOwnerName(String ownerName) { this.ownerName = ownerName; } public String getOwnerEmail() { return ownerEmail; } public void setOwnerEmail(String ownerEmail) { this.ownerEmail = ownerEmail; } public Date getUpdatedAt() { return updatedAt; } public void setUpdatedAt(Date updatedAt) { this.updatedAt = updatedAt; } public Date getCreatedAt() { return createdAt; } public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; } }
仓库实现类OwnerRepositoryImpl
package com.data.platform.core.repository.impl; import com.data.platform.core.dto.requests.OwnerDtoRequest; import com.data.platform.core.entity.operation.tables.Owners; import com.data.platform.core.models.Owner; import com.data.platform.core.converters.OwnerConverter; import com.data.platform.core.repository.OwnerRepository; import org.jooq.DSLContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import java.util.UUID; @Repository public class OwnerRepositoryImpl implements OwnerRepository { @Autowired private DSLContext dslContext; @Autowired private OwnerConverter ownerConverter; @Override public List<Owner> findAll(Integer size, Integer offset) { return dslContext.selectFrom(Owners.OWNERS) .limit(size) .offset(offset) .fetch(ownerConverter); } @Override public Optional<Owner> findById(UUID ownerId) { return dslContext.selectFrom(Owners.OWNERS) .where(Owners.OWNERS.OWNER_ID.eq(ownerId)) .fetch().map(ownerConverter).stream().findFirst(); } @Override public Owner insert(OwnerDtoRequest owner) { return dslContext.insertInto(Owners.OWNERS) .set(Owners.OWNERS.OWNER_NAME, owner.getOwnerName()) .set(Owners.OWNERS.OWNER_EMAIL, owner.getOwnerEmail()) .returning() .fetchOne() .map(ownerConverter); } @Override public Owner update(UUID ownerId, OwnerDtoRequest owner) { return dslContext.update(Owners.OWNERS) .set(Owners.OWNERS.OWNER_NAME, owner.getOwnerName()) .set(Owners.OWNERS.OWNER_EMAIL, owner.getOwnerEmail()) .where(Owners.OWNERS.OWNER_ID.eq(ownerId)) .returning() .fetchOne() .map(ownerConverter); } @Override public Integer delete(UUID ownerId) { return dslContext.delete(Owners.OWNERS) .where(Owners.OWNERS.OWNER_ID.eq(ownerId)) .execute(); } }
转换器OwnerConverter
package com.data.platform.core.converters; import com.data.platform.core.entity.operation.tables.records.OwnersRecord; import com.data.platform.core.models.Owner; import org.jetbrains.annotations.Nullable; import org.jooq.Record; import org.jooq.RecordMapper; import org.springframework.stereotype.Component; import java.time.ZoneId; import java.util.Date; import java.util.function.Function; @Component public class OwnerConverter implements Function<OwnersRecord, Owner>, RecordMapper<Record, Owner> { @Override public Owner apply(OwnersRecord ownersRecord) { // 原代码存在未定义变量错误,已修复 Date createdAtDate = Date.from(ownersRecord.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant()); Date updateAtDate = Date.from(ownersRecord.getUpdatedAt().atZone(ZoneId.systemDefault()).toInstant()); return new Owner(ownersRecord.getOwnerId(), ownersRecord.getOwnerName(), ownersRecord.getOwnerEmail(), updateAtDate, createdAtDate); } @Override public @Nullable Owner map(Record r) { return apply((OwnersRecord) r); } }
测试基类BaseRepositoryTest
package com.data.platform.core.repository.common; import org.jooq.DSLContext; import org.junit.After; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.testcontainers.containers.PostgreSQLContainer; import static com.data.platform.core.entity.operation.tables.Owners.OWNERS; @RunWith(SpringRunner.class) @ContextConfiguration(initializers = BaseRepositoryTest.Initializer.class) @SpringBootApplication(scanBasePackages = "com.data.platform.core", exclude = {R2dbcAutoConfiguration.class}) public abstract class BaseRepositoryTest { public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:14") .withDatabaseName("data-platform") .withUsername("postgres") .withPassword("password"); @Autowired DSLContext dslContext; static { postgres.start(); } static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues.of( "spring.datasource.url=" + postgres.getJdbcUrl(), "spring.datasource.username=" + postgres.getUsername(), "spring.datasource.password=" + postgres.getPassword(), "spring.jooq.sql-dialect=POSTGRES" ) .applyTo(configurableApplicationContext.getEnvironment()); } } @After public void cleanUpAll() { ownerCleanUp(); } void ownerCleanUp() { dslContext.deleteFrom(OWNERS).execute(); } }
测试类OwnerRepositoryTest
package com.data.platform.core.repository; import com.data.platform.core.converters.OwnerConverter; import com.data.platform.core.entity.operation.tables.Owners; import com.data.platform.core.models.Owner; import com.data.platform.core.repository.common.BaseRepositoryTest; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import java.time.ZoneId; import java.util.List; import static com.data.platform.core.repository.fixtures.OwnerRepositoryTestFixture.defaultListOfOwners; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; public class OwnerRepositoryTest extends BaseRepositoryTest { @Autowired OwnerRepository ownerRepository; @Autowired OwnerConverter ownerConverter; @Before public void setUp() { // 预置测试数据到测试容器数据库 List<Owner> testOwners = defaultListOfOwners(); testOwners.forEach(owner -> { dslContext.insertInto(Owners.OWNERS) .set(Owners.OWNERS.OWNER_ID, owner.getOwnerId()) .set(Owners.OWNERS.OWNER_NAME, owner.getOwnerName()) .set(Owners.OWNERS.OWNER_EMAIL, owner.getOwnerEmail()) .set(Owners.OWNERS.CREATED_AT, owner.getCreatedAt().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()) .set(Owners.OWNERS.UPDATED_AT, owner.getUpdatedAt().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()) .execute(); }); } @Test public void testFindAll() { Integer size = 10; Integer offset = 0; List<Owner> expectedOwners = defaultListOfOwners(); List<Owner> actualOwners = ownerRepository.findAll(size, offset); assertThat(actualOwners.size(), is(expectedOwners.size())); assertThat(actualOwners.get(0).getOwnerEmail(), is(equalTo(expectedOwners.get(0).getOwnerEmail()))); } }
问题排查
1. DSLContext无法注入的核心原因
- Spring扫描范围不足:原
BaseRepositoryTest的@SpringBootApplication默认只扫描当前包,而Repository、Converter等Bean在其他子包下,导致Spring无法发现这些Bean,无法完成DSLContext的依赖链初始化。 - JOOQ配置缺失:未显式指定JOOQ的SQL方言,Spring无法正确适配PostgreSQL生成DSLContext实例。
- 测试数据未预置:原测试类直接断言预期数据,但未将数据插入测试容器的数据库,导致查询结果为空。
2. 代码中的其他错误
- OwnerConverter变量未定义:原
apply方法中使用了未声明的createdAtDate和updateAtDate变量,导致编译失败。 - Repository导入路径错误:原代码中
import com.data.platform.core.Repository.OwnerRepository;的包名大小写错误,应为小写repository。
优化方案
1. 调整测试基类配置
- 新增
scanBasePackages指定根扫描包,确保所有Bean被Spring发现。 - 添加JOOQ方言配置,明确指定为PostgreSQL。
- 使用
@After注解在每个测试后自动清理数据,避免测试污染。
2. 修复转换器代码
补充日期转换逻辑,将JOOQ生成的LocalDateTime转换为Date类型,解决编译错误。
3. 完善测试类
新增@Before方法预置测试数据到数据库,确保查询接口能获取到预期数据。
4. 额外优化建议
- 改用JUnit 5:SpringBoot 2.7.x支持JUnit 5,配合
@SpringBootTest和@Testcontainers注解,容器生命周期管理更简洁。 - 重写Owner的equals/hashCode:方便测试中直接比较对象,避免逐个字段断言的繁琐。
- 规范JOOQ代码生成:确保生成的实体类字段与数据库完全匹配,减少类型转换问题。
内容的提问来源于stack exchange,提问作者Paulo Henrique
相关产品推荐
相关产品推荐

