使用REST API输入在基于Spring Boot的应用程序中在运行时设置架构名称

huangapple go评论112阅读模式
英文:

Set schema name at run time using REST API input in spring boot based application

问题

  1. **postgres** 数据库中**模式名称** 被设定为 **买家 ID**因此如果买家 ID *buyer_2213*那么模式名称将是 *buyer_2213*)。这些模式拥有多个表而且这些表在所有模式中都有共同的结构
  2. 现在我正在使用 Spring Boot 编写 REST API以从这些模式中获取买家数据但由于 *模式名称* 取决于 *买家 ID*我不得不编写手动查询无法使用 JPA 的功能
  3. 是否有任何方法可以使用 REST API 请求参数来设置实体的 **模式名称**因此在下面的实体中是否可以通过在 BuyerController 中定义的 API 调用时使用传递的 *buyerId* 来设置 *schema*
  4. ```java
  5. @Entity
  6. @Table(name="buyer_table", schema="使用_API输入设置")
  7. public class BuyerTable{
  8. ...
  9. }
  10. @RestController
  11. @RequestMapping("/buyer")
  12. public class BuyerController{
  13. @GetMapping("/{buyerId}")
  14. public void getBuyerData(@PathVariable(required = true) String buyerId){
  15. ...
  16. }
  17. }

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

  1. <details>
  2. <summary>英文:</summary>
  3. In a **postgres** database, the **schema name** is set as the **buyer id**(so, if buyer id is *buyer_2213* then schema name will be *buyer_2213*). These schemas have multiple tables and these tables have common structure for all schemas.
  4. Now, I am writing REST API using spring boot to get buyer data from these schema, but since *schema name* is *buyer id* dependent, I have to write manual queries and could not use JPA features for that.
  5. Is there any way to set **schema name** to entity using REST API request parameters. So, in below entity, can we set *schema* using *buyerId* passed at API call defined in BuyerController:

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

@RestController
@RequestMapping("/buyer")
public class BuyerController{

  1. @GetMapping(&quot;/{buyerId}&quot;)
  2. public void getBuyerData(@PathVariable(required = true) String buyerId){
  3. ...
  4. }

}

  1. Also, the buyer Id is not same as logged in user(consider this situation as admin user who is trying to get buyer&#39;s details) and will be provided as API request param only(or any other way as API input). Hence I could not find relevance to [this][1]
  2. [1]: https://stackoverflow.com/questions/39357367/change-database-schema-during-runtime-based-on-logged-in-user
  3. </details>
  4. # 答案1
  5. **得分**: 1
  6. 我终于找到了一个可行的解决方案。此解决方案主要使用[这里][1]的配置,但更符合我的问题要求。&lt;br/&gt;&lt;br/&gt;
  7. 显然的想法是使用[AbstractDataSource][2],数据源配置与[这里][1]显示的几乎相同,只需使用可以从API逻辑内部调用的 *setter* 来设置模式名称。&lt;br/&gt;
  8. 首先,我们需要编写一个**AbstractDataSource**的实现,它将几乎看起来像这样:
  9. ```java
  10. public class BuyerSchemaDataSource extends AbstractDataSource {
  11. private LoadingCache&lt;String, DataSource&gt; dataSources = createCache();
  12. public void setSchemaName(String schemaName){
  13. this.schemaName = schemaName;
  14. }
  15. @Override public Connection getConnection() throws SQLException {
  16. try {
  17. return determineTargetDataSource().getConnection();
  18. } catch (ExecutionException e) {
  19. //打印异常
  20. return null;
  21. }
  22. }
  23. @Override public Connection getConnection(String username, String password)
  24. throws SQLException {
  25. try {
  26. return determineTargetDataSource().getConnection(username,password);
  27. } catch (ExecutionException e) {
  28. //打印异常
  29. return null;
  30. }
  31. }
  32. private DataSource determineTargetDataSource() throws ExecutionException {
  33. if(!utils.isNullOrEmpty(schemaName)){
  34. return dataSources.get(schemaName);
  35. }
  36. return buildDataSourceFromSchema(null);
  37. }
  38. private LoadingCache&lt;String, DataSource&gt; createCache(){
  39. return CacheBuilder.newBuilder()
  40. .maximumSize(100)
  41. .expireAfterWrite(10, TimeUnit.MINUTES)
  42. .build(new CacheLoader&lt;String, DataSource&gt;() {
  43. @Override public DataSource load(String key) throws Exception {
  44. return buildDataSourceFromSchema(key);
  45. }
  46. });
  47. }
  48. private DataSource buildDataSourceForSchema(String schema) {
  49. // 例如属性的示例: "jdbc:postgresql://localhost:5432/mydatabase?currentSchema="
  50. String url = env.getRequiredProperty("spring.datasource.url") + schema;
  51. return DataSourceBuilder.create()
  52. .driverClassName(env.getRequiredProperty("spring.datasource.driverClassName"))
  53. [...]
  54. .url(url)
  55. .build();
  56. }
  57. }

<br/>
现在,就像任何其他数据源一样,这可以在Spring配置文件中使用,如下所示:<br/>

  1. @Configuration
  2. @EnableTransactionManagement
  3. @EnableJpaRepositories(entityManagerFactoryRef = "schemaSpecificEntityManagerFactory",
  4. transactionManagerRef = "schemaSpecificTransactionManager")
  5. public class SchemaSpecificConfig {
  6. @Bean(name = "schemaSpecificDataSource")
  7. public DataSource schemaSpecificDataSource(){
  8. return new BuyerSchemaDataSource();
  9. }
  10. @Bean(name = "schemaSpecificEntityManagerFactory")
  11. public LocalContainerEntityManagerFactoryBean schemaSpecificEntityManagerFactory(
  12. EntityManagerFactoryBuilder builder, @Qualifier("schemaSpecificDataSource") DataSource dataSource) {
  13. HashMap<String, Object> properties = new HashMap<>();
  14. properties.put("hibernate.hbm2ddl.auto", "update");
  15. properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
  16. return builder.dataSource(dataSource).properties(properties)
  17. .persistenceUnit("SchemaSpecific").build();
  18. }
  19. @Bean(name = "schemaSpecificTransactionManager")
  20. public PlatformTransactionManager schemaSpecificTransactionManager(
  21. @Qualifier("schemaSpecificEntityManagerFactory") EntityManagerFactory schemaSpecificEntityManagerFactory) {
  22. return new JpaTransactionManager(schemaSpecificEntityManagerFactory);
  23. }
  24. }

<br/>
现在,在控制器的API逻辑内部可以调用BuyerSchemaDataSource中定义的setSchema()方法。<br/><br/>
这看起来像是一个解决方法不太好的问题,但我没有找到比这更好的方法,欢迎所有建议/编辑。

英文:

I've finally found a working solution for this. This solution uses configurations mostly from here but is more specific to the my question requirements. <br/><br/>
The idea is obviously to use AbstractDataSource and data source configurations are pretty much similar to what shown in here with just the schema name will be set using a setter which can be called from inside API logic.<br/>
First, we need to write an implementation of AbstractDataSource which will pretty much look like this:

  1. public class BuyerSchemaDataSource extends AbstractDataSource {
  2. private LoadingCache&lt;String, DataSource&gt; dataSources = createCache();
  3. public void setSchemaName(String schemaName){
  4. this.schemaName = schemaName;
  5. }
  6. @Override public Connection getConnection() throws SQLException {
  7. try {
  8. return determineTargetDataSource().getConnection();
  9. } catch (ExecutionException e) {
  10. //print exception
  11. return null;
  12. }
  13. }
  14. @Override public Connection getConnection(String username, String password)
  15. throws SQLException {
  16. try {
  17. return determineTargetDataSource().getConnection(username,password);
  18. } catch (ExecutionException e) {
  19. //print exception
  20. return null;
  21. }
  22. }
  23. private DataSource determineTargetDataSource() throws ExecutionException {
  24. if(!utils.isNullOrEmpty(schemaName)){
  25. return dataSources.get(schemaName);
  26. }
  27. return buildDataSourceFromSchema(null);
  28. }
  29. private LoadingCache&lt;String, DataSource&gt; createCache(){
  30. return CacheBuilder.newBuilder()
  31. .maximumSize(100)
  32. .expireAfterWrite(10, TimeUnit.MINUTES)
  33. .build(new CacheLoader&lt;String, DataSource&gt;() {
  34. @Override public DataSource load(String key) throws Exception {
  35. return buildDataSourceFromSchema(key);
  36. }
  37. });
  38. }
  39. private DataSource buildDataSourceForSchema(String schema) {
  40. // e.g. of property: &quot;jdbc:postgresql://localhost:5432/mydatabase?currentSchema=&quot;
  41. String url = env.getRequiredProperty(&quot;spring.datasource.url&quot;) + schema;
  42. return DataSourceBuilder.create()
  43. .driverClassName(env.getRequiredProperty(&quot;spring.datasource.driverClassName&quot;))
  44. [...]
  45. .url(url)
  46. .build();
  47. }
  48. }

<br/>
Now, just like any other data source this can be used in a spring configuration file like this:<br/>

  1. @Configuration
  2. @EnableTransactionManagement
  3. @EnableJpaRepositories(entityManagerFactoryRef = &quot;schemaSpecificEntityManagerFactory&quot;,
  4. transactionManagerRef = &quot;schemaSpecificTransactionManager&quot;)
  5. public class SchemaSpecificConfig {
  6. @Bean(name = &quot;schemaSpecificDataSource&quot;)
  7. public DataSource schemaSpecificDataSource(){
  8. return new BuyerSchemaDataSource();
  9. }
  10. @Bean(name = &quot;schemaSpecificEntityManagerFactory&quot;)
  11. public LocalContainerEntityManagerFactoryBean schemaSpecificEntityManagerFactory(
  12. EntityManagerFactoryBuilder builder, @Qualifier(&quot;schemaSpecificDataSource&quot;) DataSource dataSource) {
  13. HashMap&lt;String, Object&gt; properties = new HashMap&lt;&gt;();
  14. properties.put(&quot;hibernate.hbm2ddl.auto&quot;, &quot;update&quot;);
  15. properties.put(&quot;hibernate.dialect&quot;, &quot;org.hibernate.dialect.PostgreSQLDialect&quot;);
  16. return builder.dataSource(dataSource).properties(properties)
  17. .persistenceUnit(&quot;SchemaSpecific&quot;).build();
  18. }
  19. @Bean(name = &quot;schemaSpecificTransactionManager&quot;)
  20. public PlatformTransactionManager schemaSpecificTransactionManager(
  21. @Qualifier(&quot;schemaSpecificEntityManagerFactory&quot;) EntityManagerFactory schemaSpecificEntityManagerFactory) {
  22. return new JpaTransactionManager(schemaSpecificEntityManagerFactory);
  23. }
  24. }

<br/>
Now, the setSchema() method defined in BuyerSchemaDataSource can be called from inside API logic in controller.<br/><br/>
This looks like a bad way to solve it but I have not find anything better than this and all suggestions/edits are appreciated.

huangapple
  • 本文由 发表于 2020年8月23日 18:29:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/63545896.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定