spring AbstractRoutingDataSource不更改连接

dfuffjeb  于 2023-04-28  发布在  Spring
关注(0)|答案(5)|浏览(134)

我使用AbstractRoutingDataSource动态更改数据源,并使用ThreadLocal设置currentLookupKey。当每个http请求只使用一个数据源时,它工作得很好。我用JpaRepository

@Component
@Primary
public class RoutingDataSource extends AbstractRoutingDataSource {

    @Autowired
    private DatabaseMap databaseMap;

    @Override
    public void afterPropertiesSet() {
        setTargetDataSources(databaseMap.getSourcesMap());
        setDefaultTargetDataSource(databaseMap.getSourcesMap().get("DEFAULT"));
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getDatabaseType();
    }

}

public class DatabaseContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public static void setDatabaseType(String string) {
        contextHolder.set(string);
    }

    public static String getDatabaseType() {
        return (String) contextHolder.get();
    }

    public static void clearDatabaseType() {
        contextHolder.remove();
    }
}

当我尝试在REST控制器中获取数据时,我只从一个数据库获取数据。
我的REST控制器中的一些代码

DatabaseContextHolder.setDatabaseType("db1");
//here I get data from db1 as expected
//I use JpaRepository
DatabaseContextHolder.clearDatabaseType();
DatabaseContextHolder.setDatabaseType("db2");
//here I should get data from db2 but get from db1

我试着调试,看起来Spring在http请求中只获得一次数据源。
此方法仅调用一次。

@Override
public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
}

有没有办法强制Spring更改数据源。

2w3kk1z5

2w3kk1z51#

您的问题可能与事务定界有关。
当您在代码中定义@Transactional注解时,Spring将代表您创建开始和结束事务所需的所有内容,并在需要时提交或回滚事务。
正如你在DataSourceTransactionManager类的source code中的doBegin方法中看到的--这同样适用于其他事务管理器--Spring在初始化事务时获得一个Connection--这就是为什么方法getConnection只被调用一次--并且它将在该事务中对数据库的所有底层操作中重用该连接(这对于ACID保存是有意义的)。
因此,如果你需要在同一个请求处理中连接到多个数据源,你可以在服务代码中定义不同的方法,每个方法都用@Transactional注解,并在调用它们之前根据需要更改底层数据源:

DatabaseContextHolder.setDatabaseType("db1");
// Invoke a service method annotated with @Transactional
// It can use the underlying JpaRepositories that you need
DatabaseContextHolder.clearDatabaseType();
DatabaseContextHolder.setDatabaseType("db2");
// Invoke again another (or the same, what you need) service method
// annotated with @Transactional, You should get data from db2 this time
lb3vh1jj

lb3vh1jj2#

  • 我怀疑你有一个用@Transactional注解注解的方法。在调用该事务性方法之前,首先指定一个数据源键,然后调用事务性方法。在transactional方法中,您首先调用repository,它会按照您设置的datasource查找键的预期工作。但是,您在事务方法中设置了不同的键,并调用了另一个存储库,它仍然使用您第一次设置的键。
  • transaction启动时,DataSource将被框架选择,所以**如果你使用的是@Transactional注解,无论你在方法内部做什么切换都是无用的。**因为数据源将由为@Transactional注解创建的代理选择。最好的选择是在非事务性服务中使用分支逻辑,或者使用TransactionTemplate而不是@Transactional
  • 例如,确保YourRestController没有类级别@Transactional,并且在这个yourRestControllerMethod中没有@Transactional注解,您将把它们保留到您的服务中。
@RestController
     public class YourRestController {

      @Autowired
      TransactionalService transactional

      public void yourRestControllerMethod(){
        //set the datasource look up key to A1 and then
        transactional.methodA1();
        //change datasource look up key to A2 and then
        transactional.methodA2();
      }

     }
@Service
    public class TransactionalService {

       @Transactional
       public void methodA1(){

       }
       
       @Transactional
       public void methodA2() {

       }

    }
hts6caw3

hts6caw33#

我遇到了同样的问题,上面的解决方案都不能解决。. but making my Service methodfinal(在我的REST控制器中)

public final Response
ca1c2owp

ca1c2owp4#

spring.jpa.open-in-view设置为false

pqwbnv8z

pqwbnv8z5#

在我的例子中,我遵循了jccampanero的suggestion,它工作了:将服务类中的两个方法分开,每个方法都有一个与不同数据源的数据库连接,通过AbstractRoutingDataSource切换数据源。
我认为这里的关键点是数据库配置,我们将AbstractRoutingDataSource指定为EntityManagerFactoryTransactionManager,而不是正常的DataSource

public class RoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return ReadOnlyContext.isReadOnly() ? DSType.READ_ONLY : DSType.WRITE;
    }
}

和DatabaseConfiguration:

@Bean
public RoutingDataSource actualDataSource(
    @Qualifier("dataSource") DataSource readWriteDataSource,
    @Qualifier("dataSourceReadOnly") DataSource readOnlyDataSource
) {
    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put(DSType.READ_ONLY, readOnlyDataSource);
    targetDataSources.put(DSType.WRITE, readWriteDataSource);

    RoutingDataSource routingDataSource = new RoutingDataSource();
    routingDataSource.setTargetDataSources(targetDataSources);
    routingDataSource.setDefaultTargetDataSource(readOnlyDataSource);

    return routingDataSource;
}

@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
    EntityManagerFactoryBuilder builder,
    RoutingDataSource actualDataSource
) {
    return builder.dataSource(actualDataSource).packages("the.domain.package").persistenceUnit("persistenUnitName").build();
}

@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(RoutingDataSource actualDataSource) {
    return new DataSourceTransactionManager(actualDataSource);
}

通过上面的配置,JPA Repositories将使用entityManagerFactory bean来获取DB连接(在每个服务方法中,Spring将调用RoutingDataSource中的方法determineCurrentLookupKey来获取我们之前指定的数据源)。

相关问题