You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Boot 实现UI触发INI文件更新时动态切换@Primary数据源

Got it, let's tackle this dynamic datasource switch problem with a straightforward approach that fits your needs—no overcomplicated routing datasource or external actuator endpoints. Here's how you can make it work with minimal changes to your existing code:

Core Idea

Since Spring manages beans as singletons by default, we need to manually destroy the old datasource-related beans, create new ones with the updated INI config, and register them back into the application context as the new @Primary beans. We'll trigger this process directly from your UI button click event.

Step 1: Refactor Config Class for Reusable Bean Creation

First, let's extract the bean creation logic in your JpaConfig into reusable methods so we can call them when switching datasources:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    entityManagerFactoryRef = "smsEntityManagerFactory",
    transactionManagerRef = "smsTransactionManager",
    basePackages = { "com.conceptualsystems.sms.db.repository" })
public class JpaConfig {

    // Keep the original @Bean method for initial creation
    @Bean(name="SMSX")
    @Primary
    public DataSource getDataSource() {
        return createDataSource();
    }

    @Bean(name = "smsEntityManagerFactory")
    @Primary
    public LocalContainerEntityManagerFactoryBean smsEntityManagerFactory(
        EntityManagerFactoryBuilder builder,
        @Qualifier("SMSX") DataSource dataSource) {
        return createEntityManagerFactory(builder, dataSource);
    }

    @Bean(name = "smsTransactionManager")
    @Primary
    public PlatformTransactionManager smsTransactionManager(
        @Qualifier("smsEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return createTransactionManager(entityManagerFactory);
    }

    // Reusable method to create DataSource with latest INI settings
    public DataSource createDataSource() {
        Logger logger = LoggerFactory.getLogger(this.getClass());
        logger.error("DATABASE INITIALIZING: createDataSource() called!");
        DataSourceBuilder builder = DataSourceBuilder.create();
        builder.driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        IniFile iniFile = IniSettings.getInstance().getIniFile();
        builder.username(iniFile.getDbUser());
        builder.password(iniFile.getDbPassword());
        String host = iniFile.getDbPath();
        String db = iniFile.getDbName();
        String connectionString = "jdbc:sqlserver://" + host + ";databaseName=" + db;
        logger.info("Connecting [" + connectionString +"] as [" + iniFile.getDbUser() + ":" + iniFile.getDbPassword() + "]");
        builder.url(connectionString);
        return builder.build();
    }

    // Reusable method to create EntityManagerFactory
    public LocalContainerEntityManagerFactoryBean createEntityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource) {
        return builder
            .dataSource(dataSource)
            .persistenceUnit("smsEntityManagerFactory")
            .packages("com.conceptualsystems.sms.db.entity")
            .build();
    }

    // Reusable method to create TransactionManager
    public PlatformTransactionManager createTransactionManager(EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

Step 2: Create a Datasource Switcher Service

Next, create a service class that handles destroying old beans and creating/registering new ones. This service will be called from your UI button click:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.sql.SQLException;

@Service
public class DataSourceSwitcher {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private JpaConfig jpaConfig;

    @Autowired
    private EntityManagerFactoryBuilder entityManagerFactoryBuilder;

    public void switchDataSource() throws SQLException {
        // Cast to ConfigurableApplicationContext to access bean factory
        ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ctx.getBeanFactory();

        // 1. Destroy old beans (in reverse order of dependency)
        destroyOldBeans(beanFactory);

        // 2. Create new beans with updated INI settings
        DataSource newDataSource = jpaConfig.createDataSource();
        LocalContainerEntityManagerFactoryBean newEmfBean = jpaConfig.createEntityManagerFactory(entityManagerFactoryBuilder, newDataSource);
        // Initialize the EntityManagerFactory bean
        newEmfBean.afterPropertiesSet();
        EntityManagerFactory newEmf = newEmfBean.getObject();

        PlatformTransactionManager newTxManager = jpaConfig.createTransactionManager(newEmf);

        // 3. Register new beans as primary
        registerBeanAsPrimary(beanFactory, "SMSX", newDataSource, DataSource.class);
        registerBeanAsPrimary(beanFactory, "smsEntityManagerFactory", newEmf, EntityManagerFactory.class);
        registerBeanAsPrimary(beanFactory, "smsTransactionManager", newTxManager, PlatformTransactionManager.class);

        // Optional: Close old datasource to release connections
        DataSource oldDataSource = beanFactory.getBean("SMSX", DataSource.class);
        if (oldDataSource != null) {
            oldDataSource.close();
        }
    }

    private void destroyOldBeans(DefaultListableBeanFactory beanFactory) {
        // Destroy transaction manager first, then entity manager factory, then datasource
        if (beanFactory.containsBean("smsTransactionManager")) {
            beanFactory.destroyBean("smsTransactionManager");
        }
        if (beanFactory.containsBean("smsEntityManagerFactory")) {
            beanFactory.destroyBean("smsEntityManagerFactory");
        }
        if (beanFactory.containsBean("SMSX")) {
            beanFactory.destroyBean("SMSX");
        }
    }

    private <T> void registerBeanAsPrimary(DefaultListableBeanFactory beanFactory, String beanName, T beanInstance, Class<T> beanClass) {
        // Register the new bean instance
        beanFactory.registerSingleton(beanName, beanInstance);
        // Mark it as primary
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
        beanDefinition.setPrimary(true);
    }
}

Step 3: Trigger Switch from UI Button

In your UI button's action listener, inject or get the DataSourceSwitcher bean from the application context, then call the switch method. For example:

// Inside your UI button's action performed method
try {
    // Load the new INI file first (your existing logic to read new INI path from UI)
    IniFile newIniFile = new IniFile(newIniPathFromUI);
    newIniFile.load();
    IniSettings.getInstance().setIniFile(newIniFile);

    // Get the DataSourceSwitcher from Spring context
    DataSourceSwitcher switcher = mApplicationContext.getBean(DataSourceSwitcher.class);
    switcher.switchDataSource();

    // Show success message to user
    JOptionPane.showMessageDialog(null, "Datasource switched successfully!");
} catch (Exception e) {
    // Handle error, show message to user
    JOptionPane.showMessageDialog(null, "Failed to switch datasource: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
    e.printStackTrace();
}

Key Notes & Considerations

  • Transaction Safety: Make sure no database transactions are in progress when switching datasources. You might want to add a check for active transactions or pause operations temporarily.
  • Connection Pool Cleanup: Calling oldDataSource.close() ensures the connection pool is properly shut down and releases resources.
  • Error Handling: Add robust error handling in the switch method—if creating the new datasource fails, you might want to restore the old beans or notify the user clearly.
  • JPA Repository Refresh: Since we're replacing the EntityManagerFactory, existing repository instances should automatically use the new one because they depend on the @Primary EntityManagerFactory. If you run into issues, you might need to refresh the repository beans, but in most cases, this isn't necessary.

This approach keeps things simple, leverages your existing configuration logic, and lets you trigger the switch directly from your UI without external endpoints or complex routing.

内容的提问来源于stack exchange,提问作者moonlightcheese

火山引擎 最新活动