【问题标题】:JPA Change entity table name base on request parameterJPA根据请求参数更改实体表名称
【发布时间】:2018-12-27 06:43:05
【问题描述】:

目前我们的数据库有两种模式,一种用于生产,一种用于测试,表相同但数据不同。

对于服务器端,我们使用 RoutingDatasource 来管理数据。每个请求都有一个参数来知道要访问哪个数据库,例如“prod”或“test”。然后将参数存储在 ThreadLocal 中。我们的 RoutingDatasource 将获取此参数,取决于值并返回正确的数据源。所以我们只需要一个映射实体和一个 CrudRepository 用于两个模式中的同一张表。

但出于某种(愚蠢的)原因,我们被要求将两种模式合并为一个模式,在每个表前都有一个前缀,如 PROD_TABLE1、TEST_TABLE1、PROD_TABLE2、TEST_TABLE2 等。

所以我想问一下,我们如何才能为具有此要求的每个表只保留 1 个实体、1 个存储库?我们不想克隆代码,创建太多类继承现有的只是为了映射到第二个表。 @SecondaryTable 似乎不是我们想要的。

=================================

更新 1:

我正在查看 PhysicalNamingStrategy,能够为表名添加前缀。但这只会在他们初始化 EntityManager 时运行一次。那么有没有其他方法可以为每个请求的表添加前缀?

=================================

更新 2: 我尝试这样的事情:

@Table(name = "##schema##TABLE1")

然后在我们的项目中添加一个 Hibernate 拦截器。在准备语句时,我们从 ThreadLocal 获取参数,然后将“##schema##”部分替换为正确的前缀。使用这种方式,一切正常,但我不太喜欢这种解决方案。

谁有更好的方法?

【问题讨论】:

  • 我会在前一种情况下使用两个带有同义词或指向表的视图的空模式来恢复它。
  • @MarmiteBomber 我们的客户(雇用我们)不会给我们超过 1 个模式。如果我们有 2 个空模式,那么我们宁愿有 2 个模式分别用于生产和测试
  • 好吧,你可能会说你不需要两个模式,而只需要两个数据库用户。
  • @MarmiteBomber 2 数据库用户?每个都可以使用不同的表?当这里的问题是我有不同名称但结构相同的表并且只想使用 1 个对象映射、1 个存储库时,这有什么帮助?你能告诉我更多吗?我也有解决办法,您可以查看我的更新,但仍在等待更好的解决方案

标签: oracle spring-boot jpa


【解决方案1】:

如果需要,可以在这里为任何人提供我自己的解决方法:

首先给每个实体对象加上前缀“##prefix##”:

@Table(name = "##prefix##TEST_TABLE_1")

创建一个休眠拦截器类扩展 EmptyInterceptor 并覆盖方法 onPrepareStatement。这里从 ThreadLocal 中获取前缀(预先收到请求时设置)并将其替换为表名中的“##prefix##”。

public class HibernateInterceptor extends EmptyInterceptor {

    @Override
    public String onPrepareStatement(String sql) {
        String prefix = ThreadLocal.getDbPrefix();
        sql = sql.replaceAll("##schema##", prefix + "_");
        return super.onPrepareStatement(sql);
    }
}

这样,查询将指向正确的表。

并配置到EntityManagerFactory:(你也可以在xml文件中设置)

properties.put("hibernate.ejb.interceptor", "mypackage.HibernateInterceptor");

【讨论】:

    【解决方案2】:

    安全性的角度来看,我绝不建议在一个架构中混合生产数据和测试数据,但无论如何,如果您打算这样做,这里有一个简单的方法来假装您使用不同的所有者访问数据。

    假设您有一个应用程序架构 APP,其中包含两个表 TEST_TAB1PROD_TAB1

    -- connect as APP
    create table app.prod_tab1 as
    select 'prod' col1 from dual;
    
    create table app.test_tab1 as
    select 'test' col1 from dual;
    

    您还有两个用户,例如 PROD_USRTEST_USR,他们访问数据的方式如下:

    --- connect with test_usr
    select * from app.test_tab1;
    COL1
    ----
    test 
    
    --- connect with prod_usr
    select * from app.prod_tab1;
    COL1
    ----
    prod 
    

    所以,现在你遇到了问题,表在不同的环境中具有不同的名称。

    您需要为用户 test_usr 和 prod_usr 提供特权 create synonym 并定义以下同义词:

    -- test_usr
    create synonym test_usr.tab1 for app.test_tab1;
    -- prod_usr
    create synonym prod_usr.tab1 for app.prod_tab1;
    

    现在访问具有唯一的名称和不同的架构:

    --- connect with test_usr
    select * from test_usr.tab1;
    COL1
    ----
    test
    
    --- connect with prod_usr
    select * from prod_usr.tab1;
    COL1
    ----
    prod 
    

    如果您说,不,我们没有两个不同的访问用户,我们使用架构所有者APP 访问两个环境,请重新阅读我的第一句话。

    【讨论】:

    • 哦,所以我们为每个用户创建了一个同义词。我们现在有 RoutingDatasource,所以我们只需要更改每个数据源的帐户设置,一切都应该完成。对我来说似乎很好,但我们需要对每个表和序列都这样做,对吗?我还需要询问我们的客户是否允许为我们创建 2 个用户。如果他们允许,那么我会接受你的回答。抱歉,因为我无法做出决定,所以我现在不能这样做
    • 还有一个问题,有没有其他方法像为 MySQL 创建同义词。正如我们的客户之前所说,他们有可能在几个月内从 Oracle 更改为 MySql。因此,如果不是,那么如果他们真的要更改数据库,我们将不得不找到另一个解决方案,比如坚持在准备语句时为表名添加前缀
    • 参见here,您可以使用views 获得相同的效果。您也可以在 Oracle 中执行此操作。
    • 询问客户,他们拒绝了这个理想,因为创建新的用户/模式流程需要他们几周(他们所说的),而他们只给我们 1 周的时间来完成这项任务。和其他原因等等。但是如果将来发生类似的事情,这仍然是我们的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-18
    • 1970-01-01
    相关资源
    最近更新 更多