Spring JPA-在运行时替换数据源

时间:2019-10-10 03:45:07

标签: spring-boot jpa spring-data-jpa spring-data

是否可以在运行时切换jpa数据源? 我已经在我的应用程序上注册了一些数据源,但是我需要在运行时切换数据源。

我正在构建一个Web应用程序以显示多个应用程序的摘要信息(白色标签)。每个应用程序都有其自己的具有相同结构的数据库。我具有对所有此数据库的读取权限。

然后,此Web应用程序具有其自己的数据库来存储授权。每个用户都可以访问哪个数据库。某些用户可以访问多个数据库。

当用户从某些数据库访问信息时,此Web应用程序首先需要检查该用户是否对该数据库具有授权。如果获得授权,则存储库将从该数据库中选择数据。但是,当然,这件事需要首先更改数据源。

注意:我无权在每个应用程序数据库上创建表,只读。我想授权数据库存储在一个表中。因此,进行修改或创建表单来管理它会更容易。

这是我的代码段

Class CurrentTenantIdentifierResolverImpl:获取当前租户ID。以前,我使用子域来定义将使用的数据源ID。

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

0 个答案:

没有答案