如何在Spring Boot中使用环境变量动态设置表名?

14

我正在使用AWS ECS托管我的应用程序,并使用DynamoDB进行所有的数据库操作。因此,我将在不同的环境中使用相同的数据库名称但具有不同的表名称。例如,“dev_users”(用于Dev环境),“test_users”(用于Test环境)等。(这是我们公司使用相同的Dynamo账户用于不同环境的方式)

因此,我希望通过“AWS ECS任务定义”环境参数传递的环境变量来更改模型类的“tableName”。

例如:

我的模型类是:

@DynamoDBTable(tableName = "dev_users")
public class User {

现在当我在测试环境中部署容器时,我需要将“dev”替换为“test”。我知道我可以使用

@Value("${DOCKER_ENV:dev}")

访问环境变量可以使用。但是我不确定如何在类外部使用变量。有没有办法使用Docker环境变量来选择我的表前缀?

我的意图是这样使用:

enter image description here

我知道这样不可能。但还有其他方法或解决方法吗?

编辑1:

我正在使用Rahul的答案并遇到一些问题。在写出问题之前,我将解释我遵循的过程。

过程:

  1. 我在我的配置类(com.myapp.users.config)中创建了beans。
  2. 由于我没有repositories,我将我的Model类包名称作为“basePackage”路径。(请检查图片)

enter image description here

  • 对于1),我已替换了“表名覆盖器bean注入”以避免错误。
  • 对于2),我打印了传递到此方法的名称。但它为Null。因此,请检查所有可能的传递值的方式。

检查错误的图像:

enter image description here

我没有在用户模型类中更改任何内容,因为当执行bean时,beans将替换DynamoDBTable的名称。但是表名覆盖正在发生。数据仅从Model Class级别给出的表名中提取。

我在这里缺少什么?

4个回答

9
表格名称可以通过修改DynamoDBMapperConfig bean来更改。

对于您需要每个表格都添加前缀的情况,您可以按以下方式添加bean。在这种情况下,前缀可以是环境名称。

 @Bean
public TableNameOverride tableNameOverrider() {
    String prefix = ... // Use @Value to inject values via Spring or use any logic to define the table prefix
    return TableNameOverride.withTableNamePrefix(prefix);
}

更多详情请查看这里的完整信息: https://github.com/derjust/spring-data-dynamodb/wiki/Alter-table-name-during-runtime


谢谢您的回复,我正在处理这个问题。请检查我的修改版本。 - Kiran
表名在任何情况下都没有被替换。即使我使用了方法“withTableNamePrefix(prefix)”或“withTableNameReplacement(singleTableName)”。数据仅从模型类tableName参数中定义的表中获取。 - Kiran
对于那些在使用Spring存储库尝试检索表数据时遇到空指针异常的人,应该使用DynamoDBMapperConfig.DEFAULT来获取所有属性,并将其设置为DynamoDBMapperConfig.Builder的相应属性以修复空异常。例如:configBuilder.setConversionSchema(DynamoDBMapperConfig.DEFAULT.getConversionSchema()); - azm882

3
我可以将表名前缀与活动配置文件名称相匹配。
首先添加如下的TableNameResolver类:
@Component
public class TableNameResolver extends DynamoDBMapperConfig.DefaultTableNameResolver {

private String envProfile;

public TableNameResolver() {}

public TableNameResolver(String envProfile) {
    this.envProfile=envProfile;
}

 @Override
 public String getTableName(Class<?> clazz, DynamoDBMapperConfig config) {
  String stageName = envProfile.concat("_");
  String rawTableName = super.getTableName(clazz, config);
  return stageName.concat(rawTableName);
 }
}

然后我设置了以下DynamoDBMapper bean:

@Bean
@Primary
public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB) {

    DynamoDBMapper mapper = new DynamoDBMapper(amazonDynamoDB,new DynamoDBMapperConfig.Builder().withTableNameResolver(new TableNameResolver(envProfile)).build());
     return mapper;
}

新增变量 envProfile,它是从 application.properties 文件中获取的活动配置属性值。

@Value("${spring.profiles.active}")
private String envProfile;

TableNameResolver 的重大优点是还能进行其他形式的更改(例如添加后缀、将表名转换为小写/大写等)。 - Martin Prebio

2
我们在运行时需要更改表名,这与IT技术有关。我们使用的是Spring-data-dynamodb 5.0.2,以下配置似乎提供了我们所需的解决方案。
首先,我注释了我的bean访问器。
@EnableDynamoDBRepositories(dynamoDBMapperConfigRef = "getDynamoDBMapperConfig", basePackages = "my.company.base.package")

我还设置了一个名为ENV_PREFIX的环境变量,通过SpEL进行Spring有线连接。
@Value("#{systemProperties['ENV_PREFIX']}")
private String envPrefix;

然后我设置了一个TableNameOverride bean:

@Bean
public DynamoDBMapperConfig.TableNameOverride getTableNameOverride() {
    return DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix(envPrefix);
}

最后,我使用TableNameOverride注入设置了DynamoDBMapperConfig bean。在5.0.2版本中,我们必须在DynamoDBMapperConfig构建器中设置标准的DynamoDBTypeConverterFactory以避免NPE。

@Bean
public DynamoDBMapperConfig getDynamoDBMapperConfig(DynamoDBMapperConfig.TableNameOverride tableNameOverride) {
    DynamoDBMapperConfig.Builder builder = new DynamoDBMapperConfig.Builder();
    builder.setTableNameOverride(tableNameOverride);
    builder.setTypeConverterFactory(DynamoDBTypeConverterFactory.standard());
    return builder.build();
}

回过头来看,我本可以设置一个DynamoDBTypeConverterFactory bean,它返回一个标准的DynamoDBTypeConverterFactory,并使用DynamoDBMapperConfig构建器将其注入getDynamoDBMapperConfig()方法中。但这样也能完成任务。


0

我赞同其他答案,但这里有一个想法:

创建一个包含所有用户详细信息的基类:

@MappedSuperclass
public abstract class AbstractUser {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private String firstName;
    private String lastName;

创建两个实现,使用不同的表名和Spring配置文件:

@Profile(value= {"dev","default"})
@Entity(name = "dev_user")
public class DevUser extends AbstractUser {
}

@Profile(value= {"prod"})
@Entity(name = "prod_user")
public class ProdUser extends AbstractUser {
}

创建一个使用映射的父类的单个 JPA 存储库
public interface UserRepository extends CrudRepository<AbstractUser, Long> {
}

然后使用Spring配置文件切换实现

@RunWith(SpringJUnit4ClassRunner.class)
@DataJpaTest
@Transactional
public class UserRepositoryTest {

    @Autowired
    protected DataSource dataSource;

    @BeforeClass
    public static void setUp() {
        System.setProperty("spring.profiles.active", "prod");
    }

    @Test
    public void test1() throws Exception {

        DatabaseMetaData metaData = dataSource.getConnection().getMetaData();
        ResultSet tables = metaData.getTables(null, null, "PROD_USER", new String[] { "TABLE" });
        tables.next();
        assertEquals("PROD_USER", tables.getString("TABLE_NAME"));
    }
}

感谢Essex的想法。我目前正在使用Rahul提供的方法进行工作。我不考虑Profiles概念,因为我希望尽可能减少容器级别的代码更改。因此,如果我使用Profiles,并且由于某种原因需要更改表格,则必须更改容器中的代码并再次推送它。我想避免这种情况的发生。 - Kiran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接