gpt4 book ai didi

java - REST POST 服务使用 AbstractRoutingDataSource 持久保存到两个不同的数据源中

转载 作者:太空宇宙 更新时间:2023-11-04 10:01:01 24 4
gpt4 key购买 nike

大家好,我已按照本指南使用 Spring-Boot 实现 Multi-Tenancy 应用程序:

https://www.baeldung.com/spring-abstract-routing-data-source

一切正常,我使用拦截器来拦截http请求并根据我的业务逻辑设置tanant_A或tenant_B。在我的用例中,我只有一种场景,我必须设置tenant_A,在事务中保留此数据源上的数据,然后我必须使用相同的实体和存储库在租户B上保留相同的数据(tenant_B是tenant_A的副本)。

我的 REST Controller 示例:

    @RequestMapping(method = RequestMethod.POST,
path = "/save",
produces = MediaType.APPLICATION_JSON_VALUE)
Optional<StatusMessage> create(@RequestBody MyResource resource){
MyEntity a = mapper.resourceToEntity(resource);
service.saveToA(a); /*Trasactional @Service use default dataSource A */
TenantContext.clearTenantType();
TenantContext.setCurrentTenant(DatasourceType.TENANT_B);
service.saveToB(a); /*Trasactional @Service use dataSource B */
return Optional.of(new StatusMessage("200","saved"));
}

服务:

@Service
public class MyService implements IMyService {

@Autowired
private MyEntityRepository repository;

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveToA(MyEntity a) {
repository.save(a);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveToB(MyEntity a) {
repository.save(e);
}

存储库:

@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity ,String> {}

第一个持久性事务始终有效。如果我更改持久化顺序并在tenant_A之后设置tenant_B,则数据将保留在我设置的第一个租户中,但不会保留在第二个租户中。

似乎第二个事务方法调用了我的 AbstractRoutingDataSource 实现的 certainCurrentLookupKey 方法,但服务内的存储库继续始终使用我设置的第一个租户。

奇怪的是,如果我不在 REST Controller 内重复此步骤,而是从一个简单的 main 方法重复此步骤,则在调用 @Transactional 方法时数据源会正确切换。

你有什么建议吗?

谢谢。

完成信息:

@Configuration
public class MultitenantConfiguration {

@Value("${A.JDBC.USERNAME}")
private String username;

@Value("${A.JDBC.PASSWORD}")
private String password;

@Value("${A.JDBC.CONNECTIONURL}")
private String url;

@Value("${B.JDBC.USERNAME}")
private String username_stg;

@Value("${B.JDBC.PASSWORD}")
private String password_stg;

@Value("${B.JDBC.CONNECTIONURL}")
private String url_stg;


@Primary
@Bean
public DataSource dataSource() {
MultitenantDataSourceRouter dataSource = new MultitenantDataSourceRouter();
Map<Object, Object> resolvedDataSources = new HashMap<>();
resolvedDataSources.put(DatasourceType.TENANT_A, dataSourceMaster());
resolvedDataSources.put(DatasourceType.TENANT_B, dataSourceSlave());
dataSource.setTargetDataSources(resolvedDataSources);
dataSource.setDefaultTargetDataSource(dataSourceMaster());
dataSource.afterPropertiesSet();
return dataSource;
}


public DataSource dataSourceMaster() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
return dataSource;
}


public DataSource dataSourceSlave() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
dataSource.setUsername(username_stg);
dataSource.setPassword(password_stg);
dataSource.setUrl(url_stg);
return dataSource;
}

@Bean
DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager txm = new DataSourceTransactionManager(dataSource());
return txm;
}

这是我的 certainCurrentLookupKey 实现

public class MultitenantDataSourceRouter extends AbstractRoutingDataSource {

private static final Logger log = LoggerFactory.getLogger(MultitenantDataSourceRouter.class);

@Override
public Object determineCurrentLookupKey() {
log.info(">>> determineCurrentLookupKey thread: {},{}",Thread.currentThread().getId(), Thread.currentThread().getName() );
log.info(">>> RoutingDataSource: {}", TenantContext.getCurrentTenant());
return TenantContext.getCurrentTenant();
}

}

我的ThreadLocal

public class TenantContext {
private static ThreadLocal<DatasourceType> currentTenant = new ThreadLocal<>();

public static void setCurrentTenant(DatasourceType tenant) {
currentTenant.set(tenant);
}

public static DatasourceType getCurrentTenant() {
return currentTenant.get();
}

public static void clearTenantType() {
currentTenant.remove();
}
}

如果我运行这个简单的测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class FornitorePaylineInvokerIT{


@Autowired
private CreatorController creatorController;

@Test
public void execute() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();

MyResource resource = objectMapper.readValue(request.toString(), MyResource.class);

Optional<StatusMessage> result = creatorController.create(resource);

System.out.println(result);
}

我在日志 4 中看到对 certainCurrentLookupKey 的调用:

MultitenantDataSourceRouter;28/11/2018 13:05:33,487;>>> determineCurrentLookupKey thread: 1,main
MultitenantDataSourceRouter;28/11/2018 13:05:33,487;>>> RoutingDataSource: TENANT:A
MyService;28/11/2018 13:05:33,597;[BEFORE] com.services.MyService.save[MyEntity...]
MyService;28/11/2018 13:05:33,597;>>> MyService thread: 1,main
MyService;28/11/2018 13:05:33,597;MyService START on: TENANT_A
MultitenantDataSourceRouter;28/11/2018 13:05:33,644;>>> determineCurrentLookupKey thread: 1,main
MultitenantDataSourceRouter;28/11/2018 13:05:33,644;>>> RoutingDataSource: PRODUZIONE
Service;28/11/2018 13:05:34,003;[AFTER] cMyService.save[MyEntity
MultitenantDataSourceRouter;28/11/2018 13:05:34,018;>>> determineCurrentLookupKey thread: 1,main
MultitenantDataSourceRouter;28/11/2018 13:05:34,018;>>> RoutingDataSource: TENANT_B
MyService;28/11/2018 13:05:34,081;[BEFORE] MyService.save[MyEntity..]
MyService;28/11/2018 13:05:34,081;>>> MyService thread: 1,main
MyService;28/11/2018 13:05:34,081;MyService START on: TENANT_B
MultitenantDataSourceRouter;28/11/2018 13:05:34,081;>>> determineCurrentLookupKey thread: 1,main
MultitenantDataSourceRouter;28/11/2018 13:05:34,081;>>> RoutingDataSource: TENANT_B
MyService;28/11/2018 13:05:34,288;[AFTER] com.cervedgroup.viscus.services.MyService.save[MyEntity..]
MyController;28/11/2018 13:05:34,297;[AFTER] MyController.create[MyResource...]

如果我通过 http 调用从 Controller 调用相同的服务

MultitenantDataSourceRouter;28/11/2018 13:05:33,487;>>> determineCurrentLookupKey thread: 1,main
MultitenantDataSourceRouter;28/11/2018 13:05:33,487;>>> RoutingDataSource: TENANT:A
MyService;28/11/2018 13:05:33,597;[BEFORE] com.services.MyService.save[MyEntity...]
MyService;28/11/2018 13:05:33,597;>>> MyService thread: 1,main
MyService;28/11/2018 13:05:33,597;MyService START on: TENANT_A
MultitenantDataSourceRouter;28/11/2018 13:05:33,644;>>> determineCurrentLookupKey thread: 1,main
MultitenantDataSourceRouter;28/11/2018 13:05:33,644;>>> RoutingDataSource: PRODUZIONE
Service;28/11/2018 13:05:34,003;[AFTER] cMyService.save[MyEntity
MultitenantDataSourceRouter;28/11/2018 13:05:34,018;>>> determineCurrentLookupKey thread: 1,main
MultitenantDataSourceRouter;28/11/2018 13:05:34,018;>>> RoutingDataSource: TENANT_B
MyService;28/11/2018 13:05:34,081;[BEFORE] MyService.save[MyEntity..]
MyService;28/11/2018 13:05:34,081;>>> MyService thread: 1,main
MyService;28/11/2018 13:05:34,081;MyService START on: TENANT_B
MyService;28/11/2018 13:05:34,288;[AFTER] com.cervedgroup.viscus.services.MyService.save[MyEntity..]
MyController;28/11/2018 13:05:34,297;[AFTER] MyController.create[MyResource...]

最佳答案

问题出在我的存储库类上:

@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity ,String> {}

如果我使用 JdbcTemplate 进行低级实现,效果很好:

@Repository
public class MyEntityRepository {

private final JdbcTemplate jdbcTemplate;

public MyEntityRepository(DataSource datasource) {
this.jdbcTemplate = new JdbcTemplate(datasource);
}

public void save(MyEntity e) {
jdbcTemplate.update("INSERT INTO TABLE (PARAM_A, PARAM_B) VALUES(?,?)",
new Object[]{e.getParamA(), e.getParamB()});
}

}

我在一次 REST 调用中将相同的信息保存到 2 个数据库 MASTER 和 REPLICA 中。

我不知道如何让 JpaRepository 在处理第二个调用时重新调用 getConnection。

关于java - REST POST 服务使用 AbstractRoutingDataSource 持久保存到两个不同的数据源中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53490700/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com