Spring Boot + Hibernate Multi-tenancy: @Transactional не работает - PullRequest
1 голос
/ 03 мая 2019

У меня есть мультитенантное приложение Spring Boot 2 + Hibernate 5, подключающееся к одной базе данных PostgreSQL. Я настроил это в соответствии с этими инструкциями:

Это работает нормально, пока я устанавливаю tenantId в фильтре или перехватчике до попадания в конечные точки контроллера.

Однако мне нужно установить арендатора внутри контроллера следующим образом:

@RestController
public class CarController {
    @GetMapping("/cars")
    @Transactional
    public List<Car> getCars(@RequestParam(name = "schema") String schema) {
        TenantContext.setCurrentTenant(schema);
        return carRepo.findAll();
    }
}

Но на этом этапе Соединение уже было восстановлено (для общедоступной схемы), и установка TenantContext не имеет никакого эффекта.

Я полагал, что @Transactional должен был заставить метод запускаться в отдельной транзакции, и поэтому создание сеанса Hibernate будет отложено до вызова метода carRepo.findAll(). Похоже, это не так, поскольку @Transactional ничего не делает.

Это приводит меня к 2 вопросам:

  1. Как я могу отложить создание сеанса Hibernate во время запроса до тех пор, пока мне не удастся установить правильный арендатор на основе некоторой логики, недоступной в фильтре / перехватчике? @Transactional похоже ничего не делает.
  2. Как я могу общаться с разными схемами в одном запросе или блоке кода? Представьте, что 1 хранилище доступно только в общедоступной схеме, а 1 - в схеме арендатора.

Другие соответствующие классы (показаны только соответствующие части!)

MultiTenantConnectionProviderImpl.java:

@Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        final Connection connection = getAnyConnection();
        connection.setSchema(tenantIdentifier);
        return connection;
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
        connection.setSchema(null);
        releaseAnyConnection(connection);
    }
}

TenantIdentifierResolver.java

@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {

    @Override
    public String resolveCurrentTenantIdentifier() {
        String tenantId = TenantContext.getCurrentTenant();
        return (tenantId != null) ? tenantId : "public";
    }
    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

HibernateConfig.java:

@Configuration
public class HibernateConfig {
    @Autowired
    private JpaProperties jpaProperties;

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
                                                                       MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
                                                                       CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl) {
        Map<String, Object> properties = new HashMap<>(jpaProperties.getProperties());
        properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
        properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
        properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.example");
        em.setJpaVendorAdapter(jpaVendorAdapter());
        em.setJpaPropertyMap(properties);
        return em;
    }
}

Ответы [ 2 ]

2 голосов
/ 03 мая 2019

Весной транзакция вызывается, когда мы вызываем метод из другого класса бинов. В этом случае, если вы переместите вызов findAll в класс обслуживания и добавите транзакцию для этого метода, тогда поведение будет таким, как вы ожидаете.Транзакция начнется, когда вы вызовете метод сервиса, и тогда значение схемы будет установлено в TenantContext

Примечание. Удалите @Transactional из контроллера.Поскольку вы выполняете чтение, лучше добавить свойство readonly в @Transactional, добавленное в метод сервиса 'getAllCars ()'

@RestController
public class CarController {

    @GetMapping("/cars")
    public List<Car> getCars(@RequestParam(name = "schema") String schema) {
        TenantContext.setCurrentTenant(schema);
        return carService.getAllCars();
    }
}

@Service
public class CarService{

    @Transactional(readOnly=true)
    public List<Car> getAllCars() {
        return carRepo.findAll();
    }
}
1 голос
/ 04 мая 2019

Хотя @ tan-mally четко объясняет мою проблему с @Transaction и как ее решить, настоящая проблема была вызвана другой конфигурацией Spring Boot по умолчанию: spring.jpa.open-in-view=true

При установке на false Мне не нужна аннотация @Transaction.Получение Соединения будет отложено до тех пор, пока оно не достигнет метода findAll() репо, после вызова TenantContext.setCurrentTenant(schema).

Видимо spring.jpa.open-in-view=true всегда охотно создает сеанс Hibernate вокруг всего запроса.

Надеюсь, это поможет следующему человеку, столкнувшемуся с этой проблемой.Я только намекал на это свойство предупреждением, которое выскакивает во время запуска об этой настройке по умолчанию.См. этот выпуск Github для обсуждения этой темы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...