Spring Boot + Hibernate + схема Oracle для нескольких арендаторов - PullRequest
0 голосов
/ 30 января 2019

Я пытаюсь заставить работать решение для работы с несколькими арендаторами на основе схем, аналогично в этом примере , но с Oracle вместо Postgres.

Например, у меня есть три схемы: FOO,БАР и БАЗ.У каждого БАРА и БАЗА есть стол, который называется СООБЩЕНИЯ.FOO был предоставлен SELECT для BAR.MESSAGES и BAZ.MESSAGES.Поэтому, если я подключусь как FOO, а затем выполню

SELECT * FROM BAR.MESSAGES;

, я получу результат, как и ожидалось.Но если я опускаю имя схемы (например, SELECT * FROM MESSAGES), я получаю ORA-00942: таблица или представление не существует (соединение использует неверную схему).

Вот мой Dao / репозиторий:

@Repository
public interface MessageDao extends CrudRepository<Foo, Long> {
}

Контроллер:

@GetMapping("/findAll")
public List<Message> findAll() {
    TenantContext.setCurrentTenant("BAR");
    var result = messageDao.findAll();
    return result;
}

Конфиг:

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

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
                                                                       MultiTenantConnectionProvider multiTenantConnectionProvider,
                                                                       CurrentTenantIdentifierResolver tenantIdentifierResolver) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan(Message.class.getPackageName());

        em.setJpaVendorAdapter(this.jpaVendorAdapter());

        Map<String, Object> jpaProperties = new HashMap<>();
        jpaProperties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
        jpaProperties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
        jpaProperties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdentifierResolver);
        jpaProperties.put(Environment.FORMAT_SQL, true);

        em.setJpaPropertyMap(jpaProperties);
        return em;
    }

МногопользовательскаяConnectionProvider:

@Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {

    @Autowired
    private DataSource dataSource;

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

    @Override
    public void releaseAnyConnection(Connection connection) throws SQLException {
        connection.close();
    }

    @Override
    public Connection getConnection(String currentTenantIdentifier) throws SQLException {
        String tenantIdentifier = TenantContext.getCurrentTenant();
        final Connection connection = getAnyConnection();
        try (Statement statement = connection.createStatement()) {
            statement.execute("ALTER SESSION SET CURRENT_SCHEMA = BAR");
        } catch (SQLException e) {
            throw new HibernateException("Problem setting schema to " + tenantIdentifier, e);
        }
        return connection;
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
        try (Statement statement = connection.createStatement()) {
            statement.execute("ALTER SESSION SET CURRENT_SCHEMA = FOO");
        } catch (SQLException e) {
            throw new HibernateException("Problem setting schema to " + tenantIdentifier, e);
        }
        connection.close();
    }

    @SuppressWarnings("rawtypes")
    @Override
    public boolean isUnwrappableAs(Class unwrapType) {
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> unwrapType) {
        return null;
    }

    @Override
    public boolean supportsAggressiveRelease() {
        return true;
    }
}

И TenantIdentifierResolver (хотя это и не очень актуально, потому что я жестко кодирую арендаторов прямо сейчас в ConnectionProviderImpl выше):

@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {

    @Override
    public String resolveCurrentTenantIdentifier() {
        String tenantId = TenantContext.getCurrentTenant();
        if (tenantId != null) {
            return tenantId;
        }
        return "BAR";
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

Любые идеи о том, почему базовое соединение неНе переключаете схемы, как ожидалось?

ОБНОВЛЕНИЕ 1

Возможно, это как-то связано с базовым соединением Oracle.В OracleConnection существует свойство с именем CONNECTION_PROPERTY_CREATE_DESCRIPTOR_USE_CURRENT_SCHEMA_FOR_SCHEMA_NAME .Документация гласит:

У пользователя также есть возможность добавить значение CURRENT_USER к имени ADT, чтобы получить полное имя, установив для этого свойства значение true.Обратите внимание, что для извлечения значения CURRENT_SCHEMA требуется обход в сеть.

Но проблема остается, даже если я установил это значение в true (-Doracle.jdbc.createDescriptorUseCurrentSchemaForSchemaName = true).Это может быть из-за того, что «имя пользователя» в Соединении по-прежнему «FOO», даже после изменения sesssion для установки схемы в «BAR» (currentSchema в Соединении - «BAR»).Но это будет означать, что документация OracleConnection неверна, не так ли?

ОБНОВЛЕНИЕ 2 Я также не учел тот факт, что здесь мы используем Spring Data JPA.Может быть, это как-то связано с проблемой?Я обнаружил, что если я включаю имя схемы, жестко закодированное в сущности, то оно работает (например, @Table (schema = "BAR")), но с жестко закодированным значением не является приемлемым решением.

Это может также сработать, если мы переписываем запросы как собственные @Query и затем включаем {h-schema} в SQL, но в Hibernate это схема по умолчанию, а не «текущая» (динамическая) схема, так что это не совсемтоже верно.

1 Ответ

0 голосов
/ 01 февраля 2019

Оказывается, что установка текущего арендатора в первой строке Контроллера подобным образом (TenantContext.setCurrentTenant ("BAR")) "слишком поздняя" (Spring уже создал транзакцию?).Я изменил реализацию, чтобы использовать фильтр сервлета для установки идентификатора клиента из заголовка в атрибут запроса, а затем извлечь этот атрибут в TenantIdentifierResolver вместо использования TenantContext.Теперь он работает как надо, без каких-либо вещей, которые я упомянул в обновлениях.

...