Служба REST POST для сохранения в двух разных источниках данных с использованием AbstractRoutingDataSource - PullRequest
0 голосов
/ 27 ноября 2018

Привет всем, я следую этому руководству, чтобы реализовать мультитенантное приложение с Spring-Boot:

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

Все отлично работает, я использую перехватчик дляперехватить HTTP-запрос и на основе моей бизнес-логики установить tanant_A или tenant_B.В моем случае использования у меня есть только один сценарий, в котором я должен установить tenant_A, сохранить данные на этом источнике данных в транзакции, и после этого мне нужно сохранить те же данные на клиенте B, используя те же сущности и репозитории (tenant_B - REPLICAof tenant_A).

Пример моего контроллера REST:

    @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_B и после tenant_A данные сохраняются на первом установленном мною арендаторе, но не на втором.

Кажется, что второй транзакционный метод вызывает метод defineCurrentLookupKey моего AbstractRoutingDataSourceреализации, но хранилище внутри службы продолжает всегда использовать первый установленный клиент.

Странно то, что если я повторю этот шаг не внутри контроллера REST, а из простого основного метода, переключателя dataSourceправильно, когда вызывается метод @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;
    }

И этомоя реализация defineCurrentLookupKey

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();
    }

}

My 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 вызов для defineCurrentLookupKey:

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 call

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...]

1 Ответ

0 голосов
/ 04 декабря 2018

Проблема с моим классом репозитория:

@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()});
    }

}

Я сохраняю ту же информацию в 2 базы данныхMASTER и REPLICA в одном вызове REST.

Я не знаю, как заставить JpaRepository повторно вызывать getConnection при обработке второго вызова.

...