首页 > 解决方案 > 在基于 Spring Boot 的应用程序中使用 REST API 输入在运行时设置模式名称

问题描述

postgres数据库中,模式名称设置为买家 id(因此,如果买家 id 是buy_2213 ,那么模式名称将是buy_2213)。这些模式有多个表,并且这些表具有所有模式的通用结构。

现在,我正在使用 Spring Boot 编写 REST API 以从这些模式中获取买家数据,但由于模式名称依赖于买家 ID ,因此我必须编写手动查询并且无法使用 JPA 功能。

有没有办法使用 REST API 请求参数将架构名称设置为实体。因此,在下面的实体中,我们可以使用在BuyerController中定义的 API 调用中传递的买家 ID 来设置模式:

  @Entity
  @Table(name="buyer_table", schema="set_it_using_API_input")
  public class BuyerTable{
    ...
  }


  @RestController
  @RequestMapping("/buyer")
  public class BuyerController{
    
    @GetMapping("/{buyerId}")
    public void getBuyerData(@PathVariable(required = true) String buyerId){
      ...
    }
  }

此外,买家 ID 与登录用户不同(将这种情况视为试图获取买家详细信息的管理员用户)并且将仅作为 API 请求参数提供(或任何其他方式作为 API 输入)。因此我找不到与此相关的

标签: javaspringpostgresqlspring-boot

解决方案


我终于找到了一个可行的解决方案。该解决方案主要使用此处的配置,但更具体到我的问题要求。

这个想法显然是使用AbstractDataSource和数据源配置与这里显示的非常相似,只是模式名称将使用可以从内部 API 逻辑调用的setter来设置。
首先,我们需要编写一个AbstractDataSource的实现,它看起来像这样:

public class BuyerSchemaDataSource extends AbstractDataSource {

    private LoadingCache<String, DataSource> dataSources = createCache();

    public void setSchemaName(String schemaName){
        this.schemaName = schemaName;
    }

    @Override public Connection getConnection() throws SQLException {
        try {
            return determineTargetDataSource().getConnection();
        } catch (ExecutionException e) {
            //print exception
            return null;
        }
    }

    @Override public Connection getConnection(String username, String password)
        throws SQLException {
        try {
            return determineTargetDataSource().getConnection(username,password);
        } catch (ExecutionException e) {
            //print exception
            return null;
        }
    }

    private DataSource determineTargetDataSource() throws ExecutionException {
        if(!utils.isNullOrEmpty(schemaName)){
            return dataSources.get(schemaName);
        }
        return buildDataSourceFromSchema(null);
    }

    private LoadingCache<String, DataSource> createCache(){
        return CacheBuilder.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build(new CacheLoader<String, DataSource>() {
                @Override public DataSource load(String key) throws Exception {
                    return buildDataSourceFromSchema(key);
                }
            });
    }

    private DataSource buildDataSourceForSchema(String schema) {
        // e.g. of property: "jdbc:postgresql://localhost:5432/mydatabase?currentSchema="
        String url = env.getRequiredProperty("spring.datasource.url") + schema;
        return DataSourceBuilder.create()
            .driverClassName(env.getRequiredProperty("spring.datasource.driverClassName"))
            [...]
            .url(url)
            .build();
    }
}


现在,就像任何其他数据源一样,它可以在 spring 配置文件中使用,如下所示:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "schemaSpecificEntityManagerFactory",
    transactionManagerRef = "schemaSpecificTransactionManager")
public class SchemaSpecificConfig {

    @Bean(name = "schemaSpecificDataSource")
    public DataSource schemaSpecificDataSource(){
        return new BuyerSchemaDataSource();
    }

    @Bean(name = "schemaSpecificEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean schemaSpecificEntityManagerFactory(
        EntityManagerFactoryBuilder builder, @Qualifier("schemaSpecificDataSource") DataSource dataSource) {

        HashMap<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
        return builder.dataSource(dataSource).properties(properties)
            .persistenceUnit("SchemaSpecific").build();
    }

    @Bean(name = "schemaSpecificTransactionManager")
    public PlatformTransactionManager schemaSpecificTransactionManager(
        @Qualifier("schemaSpecificEntityManagerFactory") EntityManagerFactory schemaSpecificEntityManagerFactory) {
        return new JpaTransactionManager(schemaSpecificEntityManagerFactory);
    }
}


现在,BuyerSchemaDataSource 中定义的 setSchema() 方法可以从控制器的 API 逻辑内部调用。

这看起来是一种不好的解决方法,但我没有找到比这更好的方法,所有建议/编辑都表示赞赏。

推荐阅读