You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot多数据源、EntityManager与事务管理异常问题求助

兄弟,我之前折腾过几乎一模一样的场景——Spring Boot多数据源+共享DAO/Service+事务失效,结合你给的代码片段,问题大概率出在事务管理器没和动态数据源绑定上:你现在用@Primary标注了主数据源,但切换数据源后,事务管理器还是会默认绑定主数据源的EntityManagerFactory,不会跟着动态切换,导致切换后的数据源操作要么不在事务里,要么事务逻辑跑错了数据源,最终出现事务失效、数据不一致这类问题。

下面给你一步步拆解解决方案:

1. 先搞定动态数据源的核心路由类

首先需要一个能根据ThreadLocal上下文切换数据源的路由类,替代你现在的dataSource() Bean:

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 这里用ThreadLocal存储当前要使用的数据源名称
        return DynamicDataSourceContextHolder.getDataSource();
    }
}

配套的ThreadLocal上下文工具类:

public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    // 设置当前数据源
    public static void setDataSource(String dataSourceName) {
        CONTEXT_HOLDER.set(dataSourceName);
    }

    // 获取当前数据源
    public static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    // 清除上下文,避免线程池复用导致的数据源混乱
    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }
}

2. 为每个数据源创建独立的EntityManagerFactory

JPA的EntityManagerFactory是和数据源强绑定的,你不能只用一个Factory对应多个数据源。需要遍历你的toLoadDatasources列表,为每个数据源创建专属的EntityManagerFactory:

@Configuration
@EnableJpaRepositories(basePackages = "org.foo")
@EnableTransactionManagement
public class DataSourceConfiguration {
    @Value("#{'${load.datasources}'.split(',')}")
    private List<String> toLoadDatasources;
    @Value("${default.datasource}")
    private String defaultDatasource;
    @Autowired
    private Environment env;

    @Bean
    @ConfigurationProperties("spring.jpa")
    public JpaProperties jpaProperties() {
        return new JpaProperties();
    }

    // 存储所有数据源的Map,key为数据源名称
    @Bean
    public Map<String, DataSource> dataSourceMap() {
        Map<String, DataSource> dataSourceMap = new HashMap<>();
        for (String dsName : toLoadDatasources) {
            // 读取对应数据源的配置(假设你的配置是spring.datasource.{dsName}.xxx格式)
            DataSource dataSource = DataSourceBuilder.create()
                    .driverClassName(env.getProperty("spring.datasource." + dsName + ".driver-class-name"))
                    .url(env.getProperty("spring.datasource." + dsName + ".url"))
                    .username(env.getProperty("spring.datasource." + dsName + ".username"))
                    .password(env.getProperty("spring.datasource." + dsName + ".password"))
                    .build();
            dataSourceMap.put(dsName, dataSource);
        }
        return dataSourceMap;
    }

    // 动态数据源Bean,作为主数据源
    @Bean
    @Primary
    public DataSource dynamicDataSource(Map<String, DataSource> dataSourceMap) {
        DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
        dynamicDataSource.setDefaultTargetDataSource(dataSourceMap.get(defaultDatasource));
        dynamicDataSource.setTargetDataSources(new HashMap<>(dataSourceMap));
        // 初始化数据源路由
        dynamicDataSource.afterPropertiesSet();
        return dynamicDataSource;
    }

    // 存储所有EntityManagerFactory的Map,key为数据源名称
    @Bean
    public Map<String, EntityManagerFactory> entityManagerFactoryMap(Map<String, DataSource> dataSourceMap, JpaProperties jpaProperties) {
        Map<String, EntityManagerFactory> emfMap = new HashMap<>();
        LocalContainerEntityManagerFactoryBean factoryBean;
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

        for (String dsName : toLoadDatasources) {
            factoryBean = new LocalContainerEntityManagerFactoryBean();
            factoryBean.setDataSource(dataSourceMap.get(dsName));
            factoryBean.setPackagesToScan("org.foo");
            // 给每个持久化单元起唯一名字
            factoryBean.setPersistenceUnitName(dsName + "-persistence-unit");
            factoryBean.setJpaVendorAdapter(vendorAdapter);
            factoryBean.setJpaProperties(jpaProperties.getProperties());
            factoryBean.afterPropertiesSet();
            emfMap.put(dsName, factoryBean.getObject());
        }
        return emfMap;
    }
}

3. 自定义动态事务管理器,绑定EntityManagerFactoryMap

默认的JpaTransactionManager只会绑定一个EntityManagerFactory,我们需要重写它,让它能根据当前数据源名称获取对应的Factory:

public class DynamicJpaTransactionManager extends JpaTransactionManager {
    private Map<String, EntityManagerFactory> entityManagerFactoryMap;

    @Override
    protected EntityManagerFactory obtainEntityManagerFactory() {
        String currentDs = DynamicDataSourceContextHolder.getDataSource();
        if (currentDs != null && entityManagerFactoryMap.containsKey(currentDs)) {
            return entityManagerFactoryMap.get(currentDs);
        }
        // 默认用主数据源的EntityManagerFactory
        return super.obtainEntityManagerFactory();
    }

    // 注入EntityManagerFactoryMap
    public void setEntityManagerFactoryMap(Map<String, EntityManagerFactory> entityManagerFactoryMap) {
        this.entityManagerFactoryMap = entityManagerFactoryMap;
    }
}

然后在配置类里注册这个事务管理器:

// 在DataSourceConfiguration里添加
@Bean
@Primary
public PlatformTransactionManager transactionManager(Map<String, EntityManagerFactory> entityManagerFactoryMap) {
    DynamicJpaTransactionManager transactionManager = new DynamicJpaTransactionManager();
    transactionManager.setEntityManagerFactoryMap(entityManagerFactoryMap);
    // 设置默认的EntityManagerFactory
    transactionManager.setEntityManagerFactory(entityManagerFactoryMap.get(defaultDatasource));
    return transactionManager;
}

4. 用AOP实现数据源切换的自动绑定

写一个自定义注解,用来标记方法要使用的数据源,再配合AOP切面自动切换:

// 自定义数据源注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    String value(); // 数据源名称
}

AOP切面类:

@Aspect
@Component
public class DataSourceSwitchAspect {
    // 拦截所有标注了@DataSource的方法
    @Before("@annotation(dataSource)")
    public void switchDataSource(JoinPoint point, DataSource dataSource) {
        String dsName = dataSource.value();
        DynamicDataSourceContextHolder.setDataSource(dsName);
    }

    // 方法执行完后清除上下文
    @After("@annotation(org.foo.annotation.DataSource)")
    public void restoreDataSource() {
        DynamicDataSourceContextHolder.clearDataSource();
    }
}

5. 验证事务是否生效

在你的Service方法上同时标注@DataSource和@Transactional,测试事务回滚:

@Service
public class CustomerService {
    @Autowired
    private CustomerRepository customerRepository;

    @DataSource("client1")
    @Transactional(rollbackFor = Exception.class)
    public void createCustomer(Customer customer) {
        customerRepository.save(customer);
        // 故意抛出异常,验证是否回滚
        if (true) {
            throw new RuntimeException("测试事务回滚");
        }
    }
}

执行后检查client1的数据库,如果没有新增数据,说明事务已经正常工作了。


关键注意点

  • 不要在同一个@Transactional方法里切换多个数据源:一个事务只能绑定一个EntityManagerFactory,强行切换会导致事务逻辑混乱。
  • 线程池场景一定要清除ThreadLocal:如果你的项目用了线程池(比如@Async),必须保证方法执行完后调用DynamicDataSourceContextHolder.clearDataSource(),否则线程复用会拿错数据源。
  • 如果有多个事务管理器,可以在@Transactional里指定transactionManager = "transactionManager"(对应你注册的Bean名称),不过我们已经用@Primary标记了动态事务管理器,所以不用指定也会优先使用它。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:17:20