Spring JPA - замещающий источник данных во время выполнения - PullRequest
0 голосов
/ 10 октября 2019

Возможно ли переключение источника данных jpa во время выполнения? В моих приложениях зарегистрированы некоторые источники данных, но мне нужно переключить источник данных во время выполнения.

Я создаю веб-приложение для отображения сводной информации о нескольких приложениях (white-label). Каждое приложение имеет свою собственную базу данных с одинаковой структурой. У меня есть доступ на чтение ко всей этой базе данных.

Тогда у этого веб-приложения есть собственная база данных для хранения авторизации. Какая база данных доступна каждому пользователю. У некоторых пользователей есть доступ к нескольким базам данных.

Когда пользователи получают доступ к информации из определенной базы данных, сначала это веб-приложение должно проверить, есть ли у этого пользователя полномочия для этой базы данных. Если авторизовано, то хранилище будет выбирать данные из этой базы данных. Но, конечно, для этого нужно сначала изменить источник данных.

Примечание. У меня нет разрешения на создание таблицы для каждой базы данных приложений, только для чтения. И я хочу авторизацию базы данных, хранящейся в одной таблице. Таким образом, проще будет модифицировать или создать форму для управления ею.

Это мой фрагмент кода

Класс CurrentTenantIdentifierResolverImpl: для получения текущего идентификатора арендатора. Ранее я использовал поддомен, чтобы определить, какой идентификатор источника данных будет использоваться.

@Component
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {

private static final String DEFAULT_TENANT_ID = "test";

/*
 * (non-Javadoc)
 * 
 * @see org.hibernate.context.spi.CurrentTenantIdentifierResolver#
 * resolveCurrentTenantIdentifier()
 */
@Override
public String resolveCurrentTenantIdentifier() {

    String tenant = DEFAULT_TENANT_ID;

    /*
     * RequestAttributes requestAttributes =
     * RequestContextHolder.getRequestAttributes(); if (requestAttributes != null) {
     * ServletRequestAttributes attr = (ServletRequestAttributes)
     * RequestContextHolder.currentRequestAttributes(); HttpServletRequest request =
     * attr.getRequest(); tenant = UrlParser.getIlibrary(request);
     * TenantContextHolder.setTenantId(tenant); }
     */

    // The tenant is stored in a ThreadLocal before the end user's login information
    // is submitted for spring security authentication mechanism. Refer to
    // CustomAuthenticationFilter

    return StringUtils.isNotBlank(tenant) ? tenant : DEFAULT_TENANT_ID;
}

/*
 * (non-Javadoc)
 * 
 * @see org.hibernate.context.spi.CurrentTenantIdentifierResolver#
 * validateExistingCurrentSessions()
 */
@Override
public boolean validateExistingCurrentSessions() {
    return true;
}
}

Этот класс предназначен для получения соответствующих источников данных

@Component
public class DataSourceBasedMultiTenantConnectionProviderImpl
    extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {

private static final long serialVersionUID = 1L;

@Autowired
private Map<String, DataSource> dataSourcesMtApp;

/*
 * (non-Javadoc)
 * 
 * @see org.hibernate.engine.jdbc.connections.spi.
 * AbstractDataSourceBasedMultiTenantConnectionProviderImpl#selectAnyDataSource(
 * )
 */
@Override
protected DataSource selectAnyDataSource() {
    return this.dataSourcesMtApp.values().iterator().next();
}

/*
 * (non-Javadoc)
 * 
 * @see org.hibernate.engine.jdbc.connections.spi.
 * AbstractDataSourceBasedMultiTenantConnectionProviderImpl#selectDataSource(
 * java.lang.String)
 */
@Override
protected DataSource selectDataSource(String tenantIdentifier) {
    return this.dataSourcesMtApp.get(tenantIdentifier);
}
}

Этот класс предназначен для хранения всех источников данных

@Configuration
@EnableConfigurationProperties({ MultitenancyProperties.class, JpaProperties.class })
@EnableTransactionManagement
public class MultiTenancyJpaConfiguration {

@Autowired
private JpaProperties jpaProperties;

@Autowired
private MultitenancyProperties multitenancyProperties;

/**
 * Builds a map of all data sources defined in the application.yml file
 * 
 * @return
 */
@Primary
@Bean(name = "dataSourcesMtApp")
public Map<String, DataSource> dataSourcesMtApp() {
    Map<String, DataSource> result = new HashMap<String, DataSource>();
    for (DataSourceProperties dsProperties : this.multitenancyProperties.getDataSources()) {

        DataSourceBuilder<?> factory = DataSourceBuilder.create().url(dsProperties.getUrl())
                .username(dsProperties.getUsername()).password(dsProperties.getPassword())
                .driverClassName(dsProperties.getDriverClassName());

        result.put(dsProperties.getTenantId(), factory.build());
    }
    return result;
}

/**
 * Autowires the data sources so that they can be used by the Spring JPA to
 * access the database
 * 
 * @return
 */
@Bean
public MultiTenantConnectionProvider multiTenantConnectionProvider() {
    // Autowires dataSourcesMtApp
    return new DataSourceBasedMultiTenantConnectionProviderImpl();
}

/**
 * Since this is a multi-tenant application, Hibernate requires that the current
 * tenant identifier is resolved for use with
 * {@link org.hibernate.context.spi.CurrentSessionContext} and
 * {@link org.hibernate.SessionFactory#getCurrentSession()}
 * 
 * @return
 */
@Bean
public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
    return new CurrentTenantIdentifierResolverImpl();
}

/**
 * org.springframework.beans.factory.FactoryBean that creates a JPA
 * {@link javax.persistence.EntityManagerFactory} according to JPA's standard
 * container bootstrap contract. This is the most powerful way to set up a
 * shared JPA EntityManagerFactory in a Spring application context; the
 * EntityManagerFactory can then be passed to JPA-based DAOs via dependency
 * injection. Note that switching to a JNDI lookup or to a
 * {@link org.springframework.orm.jpa.LocalEntityManagerFactoryBean} definition
 * is just a matter of configuration!
 * 
 * @param multiTenantConnectionProvider
 * @param currentTenantIdentifierResolver
 * @return
 */
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
        MultiTenantConnectionProvider multiTenantConnectionProvider,
        CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {

    Map<String, Object> hibernateProps = new LinkedHashMap<>();
    hibernateProps.putAll(this.jpaProperties.getProperties());
    hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
    hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
    hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);

    // No dataSource is set to resulting entityManagerFactoryBean
    LocalContainerEntityManagerFactoryBean result = new LocalContainerEntityManagerFactoryBean();
    result.setPackagesToScan(new String[] { Instansi.class.getPackage().getName() });
    result.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    result.setJpaPropertyMap(hibernateProps);

    return result;
}

/**
 * Interface used to interact with the entity manager factory for the
 * persistence unit.
 * 
 * @param entityManagerFactoryBean
 * @return
 */
@Bean
public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
    return entityManagerFactoryBean.getObject();
}

/**
 * Creates a new
 * {@link org.springframework.orm.jpa.JpaTransactionManager#JpaTransactionManager(EntityManagerFactory emf)}
 * instance.
 * 
 * {@link org.springframework.transaction.PlatformTransactionManager} is the
 * central interface in Spring's transaction infrastructure. Applications can
 * use this directly, but it is not primarily meant as API: Typically,
 * applications will work with either TransactionTemplate or declarative
 * transaction demarcation through AOP.
 * 
 * @param entityManagerFactory
 * @return
 */
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    return new JpaTransactionManager(entityManagerFactory);
}
}

Для сопоставления конфигурации базы данных

@Configuration
@ConfigurationProperties("multitenancy.mtapp")
public class MultitenancyProperties {

private List<DataSourceProperties> dataSourcesProps;

public List<DataSourceProperties> getDataSources() {
    return this.dataSourcesProps;
}

public void setDataSources(List<DataSourceProperties> dataSourcesProps) {
    this.dataSourcesProps = dataSourcesProps;
}

public static class DataSourceProperties extends org.springframework.boot.autoconfigure.jdbc.DataSourceProperties {

    private String tenantId;

    public String getTenantId() {
        return tenantId;
    }

    public void setTenantId(String tenantId) {
        this.tenantId = tenantId;
    }
}
}
...