diff --git a/backend/src/main/java/com/magistr/app/Application.java b/backend/src/main/java/com/magistr/app/Application.java index 3464603..4165468 100755 --- a/backend/src/main/java/com/magistr/app/Application.java +++ b/backend/src/main/java/com/magistr/app/Application.java @@ -2,8 +2,9 @@ package com.magistr.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -@SpringBootApplication +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class Application { public static void main(String[] args) { diff --git a/backend/src/main/java/com/magistr/app/config/tenant/TenantDataSourceConfig.java b/backend/src/main/java/com/magistr/app/config/tenant/TenantDataSourceConfig.java index 6208446..171493d 100644 --- a/backend/src/main/java/com/magistr/app/config/tenant/TenantDataSourceConfig.java +++ b/backend/src/main/java/com/magistr/app/config/tenant/TenantDataSourceConfig.java @@ -5,16 +5,21 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +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.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.PlatformTransactionManager; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import jakarta.persistence.EntityManagerFactory; import javax.sql.DataSource; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * Конфигурация мультитенантного DataSource. @@ -38,6 +43,7 @@ public class TenantDataSourceConfig implements WebMvcConfigurer { private String defaultDbPassword; @Bean + @Primary public TenantRoutingDataSource tenantRoutingDataSource() { TenantRoutingDataSource routingDataSource = new TenantRoutingDataSource(); @@ -45,7 +51,7 @@ public class TenantDataSourceConfig implements WebMvcConfigurer { List tenants = loadTenantsFromFile(); // Если нет тенантов и есть дефолтный datasource — создаём "default" тенант - if (tenants.isEmpty() && !defaultDbUrl.isBlank()) { + if (tenants.isEmpty() && defaultDbUrl != null && !defaultDbUrl.isBlank()) { TenantConfig defaultTenant = new TenantConfig( "Default", "default", defaultDbUrl, defaultDbUsername, defaultDbPassword ); @@ -62,18 +68,44 @@ public class TenantDataSourceConfig implements WebMvcConfigurer { } } - if (tenants.isEmpty()) { - log.warn("No tenants configured! Backend will fail on DB queries."); + if (routingDataSource.getTenantConfigs().isEmpty()) { + log.warn("=== НЕТ НАСТРОЕННЫХ ТЕНАНТОВ ==="); + log.warn("Backend запустится, но API не будет работать без подключения к БД."); + log.warn("Добавьте тенант через POST /api/database/tenants или настройте tenants.json"); } return routingDataSource; } @Bean + @Primary public DataSource dataSource(TenantRoutingDataSource tenantRoutingDataSource) { return tenantRoutingDataSource; } + @Bean + @Primary + public LocalContainerEntityManagerFactoryBean entityManagerFactory( + DataSource dataSource, EntityManagerFactoryBuilder builder) { + + Map jpaProps = new HashMap<>(); + jpaProps.put("hibernate.hbm2ddl.auto", "update"); + jpaProps.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); + + return builder + .dataSource(dataSource) + .packages("com.magistr.app.model") + .persistenceUnit("default") + .properties(jpaProps) + .build(); + } + + @Bean + @Primary + public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { + return new JpaTransactionManager(emf); + } + @Bean public TenantInterceptor tenantInterceptor() { return new TenantInterceptor(); diff --git a/backend/src/main/java/com/magistr/app/config/tenant/TenantRoutingDataSource.java b/backend/src/main/java/com/magistr/app/config/tenant/TenantRoutingDataSource.java index 03d656a..438c4d9 100644 --- a/backend/src/main/java/com/magistr/app/config/tenant/TenantRoutingDataSource.java +++ b/backend/src/main/java/com/magistr/app/config/tenant/TenantRoutingDataSource.java @@ -8,6 +8,7 @@ import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -21,6 +22,13 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource { private final Map tenantConfigs = new ConcurrentHashMap<>(); private final Map dataSources = new ConcurrentHashMap<>(); + private boolean initialized = false; + + public TenantRoutingDataSource() { + // Устанавливаем пустой map чтобы afterPropertiesSet не падал + setTargetDataSources(new HashMap<>()); + setLenientFallback(false); + } @Override protected Object determineCurrentLookupKey() { @@ -41,6 +49,7 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource { // Обновляем target data sources setTargetDataSources(dataSources); afterPropertiesSet(); + initialized = true; log.info("Added tenant '{}' -> {}", domain, config.getUrl()); } @@ -108,6 +117,10 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource { return tenantConfigs.containsKey(domain.toLowerCase()); } + public boolean isInitialized() { + return initialized && !dataSources.isEmpty(); + } + private HikariDataSource createDataSource(TenantConfig config) { HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl(config.getUrl()); @@ -119,6 +132,8 @@ public class TenantRoutingDataSource extends AbstractRoutingDataSource { ds.setConnectionTimeout(10000); ds.setIdleTimeout(300000); ds.setMaxLifetime(600000); + // Не падать при инициализации если БД недоступна + ds.setInitializationFailTimeout(-1); return ds; } } diff --git a/backend/tenants.json b/backend/tenants.json index 0fb6e00..4641650 100644 --- a/backend/tenants.json +++ b/backend/tenants.json @@ -2,8 +2,8 @@ { "name": "Default (dev)", "domain": "default", - "url": "jdbc:postgresql://db:5432/app_db", + "url": "jdbc:postgresql://192.168.1.87:5432/app_db", "username": "myuser", "password": "supersecretpassword" } -] +] \ No newline at end of file