首先,这是关于 Spring Boot 3.1.9、Java Temurin 17。
使用https://www.baeldung.com/spring-boot-configure-multiple-datasources,我已经成功设置了两个数据源(实际上,这是同一个数据库,但对于一个实体,我需要一个单独的 Hikari 连接池)。
下面的设置会导致错误消息
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaSharedEM_entityManagerFactory': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument
- 尽管它应该使用 @Configuration
类 DefaultJpaConfiguration
/LockingJpaConfiguration
中的定义。
当我禁用 @EnableJpaRepositories
和 @EntityScan
(正如我必须为生产代码所做的那样),错误消息不会更改。
我知道,
@DataJpa
测试会忽略任何手工制作的DataSource
定义,但我在@SpringBootTest
中没有找到任何这样的声明。所以也许我在这里遗漏了一些东西 - 你能帮我吗?
我已尝试申请
我的课程看起来如文章中所述:
一个实体,使用默认连接池:
package com.application.database.model.stock;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Entity
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Vehicle {
@Id
@SequenceGenerator(name = "vehicleSeq", sequenceName = "VEHICLE_ID_SEQ", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "vehicleSeq")
private Long id;
@Column(name = "color")
private String color;
}
默认实体的存储库:
package com.application.database.repository;
import com.application.database.model.stock.Vehicle;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public interface VehicleRepository extends JpaRepository<Vehicle, Long> {
Optional<Vehicle> findVehicleByColor(String color);
}
默认连接池的配置:
package com.application.database.config;
import com.application.database.model.dealer.Dealer;
import com.application.database.model.file.WfsFileImport;
import com.application.database.model.location.Location;
import com.application.database.model.reactivation.ManualReactivation;
import com.application.database.model.stock.Vehicle;
import com.application.database.model.thirdpartyexport.DealerContactDataForThirdPartyExportQueryResult;
import java.util.Objects;
import javax.sql.DataSource;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = {"com.application.database.repository", "com.application.database.model" },
entityManagerFactoryRef = "defaultEntityManagerFactory",
transactionManagerRef = "defaultTransactionManager"
)
@RequiredArgsConstructor
public class DefaultJpaConfiguration {
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean defaultEntityManagerFactory(
@Qualifier("defaultDataSource") DataSource dataSource,
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dataSource)
.packages(Vehicle.class)
.build();
}
@Primary
@Bean
public PlatformTransactionManager defaultTransactionManager(
@Qualifier("defaultEntityManagerFactory") LocalContainerEntityManagerFactoryBean defaultEntityManagerFactory) {
return new JpaTransactionManager(Objects.requireNonNull(defaultEntityManagerFactory.getObject()));
}
@Primary
@Bean
public DataSource defaultDataSource() {
return defaultDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}
@Bean
@ConfigurationProperties("spring.datasource.default")
public DataSourceProperties defaultDataSourceProperties() {
return new DataSourceProperties();
}
}
现在实体,使用其他连接池(“锁定”):
package com.application.database.locking.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.UuidGenerator;
import org.hibernate.annotations.UuidGenerator.Style;
@Table(name = "lock")
@Entity
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Lock {
@Id
@UuidGenerator(style = Style.RANDOM)
@Column(name = "id", updatable = false, nullable = false, columnDefinition = "UUID")
private UUID id;
@Column(name = "vehicle_id")
private Long vehicleId;
}
其他实体的存储库:
package com.application.database.locking.repository;
import com.application.database.locking.model.Lock;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Repository
public interface LockRepository extends JpaRepository<Lock, Long> {
Optional<Lock> findByVehicleId(Long vehicleId);
}
其他连接池的配置:
package com.application.database.config;
import com.application.database.locking.model.Lock;
import java.util.Objects;
import javax.sql.DataSource;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = {"com.application.database.locking.repository", "com.application.database.locking.model" },
entityManagerFactoryRef = "lockingEntityManagerFactory",
transactionManagerRef = "lockingTransactionManager"
)
@RequiredArgsConstructor
public class LockingJpaConfiguration {
@Bean
public LocalContainerEntityManagerFactoryBean lockingEntityManagerFactory(
@Qualifier("lockingDataSource") DataSource dataSource,
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dataSource)
.packages(Lock.class)
.build();
}
@Bean
public PlatformTransactionManager lockingTransactionManager(
@Qualifier("lockingEntityManagerFactory") LocalContainerEntityManagerFactoryBean lockingEntityManagerFactory) {
return new JpaTransactionManager(Objects.requireNonNull(lockingEntityManagerFactory.getObject()));
}
@Bean
public DataSource lockingDataSource() {
return lockingDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}
@Bean
@ConfigurationProperties("spring.datasource.locking")
public DataSourceProperties lockingDataSourceProperties() {
return new DataSourceProperties();
}
}
配置属性:
spring:
datasource:
default:
url: jdbc:postgresql://127.0.0.1/postgres
username: postgres
password: postgres
driver-class-name: org.postgresql.Driver
locking:
url: jdbc:postgresql://127.0.0.1/postgres
username: postgres
password: postgres
driver-class-name: org.postgresql.Driver
我必须调整 Application 类(用于启动应用程序),我删除了
@EnableJpaRepositories
和 @EntityScan
,我能够启动应用程序:
package com.application.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.integration.annotation.IntegrationComponentScan;
@ComponentScan(basePackages = "com.application")
@IntegrationComponentScan(basePackages = "com.application")
//@EnableJpaRepositories(basePackages = "com.application") <- had to be disabled/removed, to get application starting
//@EntityScan(basePackages = "com.application") <- had to be disabled/removed, to get application starting
@SpringBootApplication
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
}
现在,我们有集成和
@DataJpa
测试。 @DataJpa
测试如下所示:
package com.application.database.repository;
import static java.time.ZoneOffset.UTC;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@DataJpaTest
@ActiveProfiles("test")
@TestPropertySource(locations = "classpath:application-test.yml")
class LockRepositoryTest {
/* Test code omitted */
@EnableJpaRepositories(basePackages = "com.application")
@EntityScan(basePackages = "com.application")
@SpringBootApplication
static class TestConfiguration {}
}
此测试启动应用程序并成功结束。
但是,当运行集成测试时,情况有所不同,例如这个:
package com.application.database.repository;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@SpringBootTest
@ExtendWith(SpringExtension.class)
@TestPropertySource(locations = "classpath:application-test.yml")
@ActiveProfiles("test")
//@ContextConfiguration( <- leads to NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder' available
// classes = { DefaultJpaConfiguration.class, LockingJpaConfiguration.class },
// loader= AnnotationConfigContextLoader.class
//)
class QueryRepositoryTest {
/* Test code omitted */
@EnableJpaRepositories(basePackages = "com.application") <- disabling it changes nothing
@EntityScan(basePackages = "com.application") <- disabling it changes nothing
@ComponentScan(basePackages = "com.application")
@SpringBootApplication
static class TestConfiguration {}
}
如果您需要任何进一步的信息/代码和要尝试的东西,请随时与我联系。
谢谢。 刀
我能够解决这个问题。 我们在同一模块中进行了更多集成测试(我们不在模块之间共享测试代码),所有测试都具有以下(可比较的)设置:
@ExtendWith(SpringExtension.class)
@ActiveProfiles("test")
@SpringBootTest
class IntegrationTestClass {
// some tests here...
@ComponentScan(basePackages = "com.application")
@EnableJpaRepositories(basePackages = "com.application")
@EntityScan(basePackages = "com.application")
@SpringBootApplication
@PropertySource("classpath:application-test.yml")
static class TestConfiguration {}
}
此设置是在集成测试期间采用的(因为它搜索了所有
@Configuration
类。
我通过将测试类更改为
解决了这个问题@ExtendWith(SpringExtension.class)
@ActiveProfiles("test")
@SpringBootTest
@Import({ IntegrationTestClass.TestConfiguration.class })
class IntegrationTestClass {
// some tests here...
@TestComponentScan
@SpringBootApplication
@PropertySource("classpath:application-test.yml")
static class TestConfiguration {}
}
并添加 TestComponentScan 注释:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ComponentScan(basePackages = "com.application",
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = {
".*TestConfiguration"
}))
public @interface TestComponentScan {}
重要提示:不能有任何类仍然使用定义上下文的原始方式。
此外,在手动定义ConnectionPools时,SpringBoot似乎没有对新的ConnectionPools使用spring.jpa.generate-ddl设置。因此,我必须使用 Hibernate 6 的配置选项来设置此设置。
JpaConfiguration(两者相同)(只是更改的部分):
@Configuration
@EnableConfigurationProperties({JpaConfigurationProperties.class})
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = {"com.application.database.repository", "com.exxeta.dfstp.geofence.core.database.model" },
entityManagerFactoryRef = "defaultEntityManagerFactory",
transactionManagerRef = "defaultTransactionManager"
)
@RequiredArgsConstructor
public class DefaultJpaConfiguration {
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean defaultEntityManagerFactory(
@Qualifier("defaultDataSource") DataSource dataSource,
JpaConfigurationProperties jpaConfig,
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dataSource)
.packages("com.application.database.repository", "com.application.database.model")
.properties(jpaConfig.getProperties())
.build();
}
使用新类 JpaConfigurationProperties:
@Getter
@ConfigurationProperties(value = "spring.jpa", ignoreInvalidFields = true)
public class JpaConfigurationProperties {
private final Map<String, String> properties = new HashMap<>();
}
以及 application-test.yml 中的配置设置
spring:
jpa:
properties:
jakarta.persistence.schema-generation.database.action: drop-and-create # set to none for productive code
generate-ddl: true
# ignored due to two connection pools, replaced by spring.jpa.properties.jakarta.persistence.schema-generation.database.action
# hibernate:
# ddl-auto: create-drop
也许这可能对其他人有帮助......
感谢您的支持。
刀