【问题标题】:How to extract used table from SelectConditionStep<Record>如何从 SelectConditionStep<Record> 中提取使用过的表
【发布时间】:2021-11-11 14:05:49
【问题描述】:

我正在扩展我的last question I asked about jOOQ。在 Hibernate 模型中,使用了 @Filter 注释,我想将这个相同的“默认过滤器”应用于 jOOQ 查询。当我将 jOOQ 查询传递给 nativeQuery(org.jooq.Query query, Class&lt;E&gt; type) 时,我想知道是否可以从 jOOQ 查询 (org.jooq.Query) 中的 FROM 子句中提取使用的表 (TableImpl&lt;?,?&gt;)。

这是我尝试过的:

private static <E> SelectConditionStep<Record> applyDefaultFilters(Class<E> type, SelectConditionStep<Record> query)
    {
      if (BaseOrganizationModel.class.isAssignableFrom(type)) {
        query
            .getQuery()
            .addConditions(
                query
                    .getQuery()
                    .asTable()
                    .field("organization_id", Long.class)
                    .eq(currentOrganization().id));
        if (SoftDeletableModel.class.isAssignableFrom(type)) {
          query
              .getQuery()
              .addConditions(query.getQuery().asTable().field("deleted", Boolean.class).eq(false));
        }
      }
      return query;
    }

结果就是这个 SQL,这不是我想要的。我想让它过滤对应的表。

select distinct `EventGroup`.*
from `EventGroup`
where (
  ...
  and `alias_100341773`.`organization_id` = ?
  and `alias_17045196`.`deleted` = ?
)

我想要这个

select distinct `EventGroup`.*
from `EventGroup`
where (
  ...
  and `EventGroup`.`organization_id` = ?
  and `EventGroup`.`deleted` = ?
)

这可能吗?如果没有,还有哪些可能的其他路线? (除了明显的将表格传递给函数)

【问题讨论】:

    标签: java sql jooq


    【解决方案1】:

    使用 jOOQ 3.16 查询对象模型 API

    jOOQ 3.16 引入了new, experimental (as of 3.16) query object model API, which can be traversed

    在任何Select 上,只需调用Select.$from() 即可访问包含的表格列表的不可修改视图。

    针对临时案例的另一种动态 SQL 方法

    每次您尝试改变现有查询时,问问自己,有没有更优雅的方法可以使用a more functional, immutable approach do dynamic SQL 来做到这一点?与其将额外的谓词附加到查询中,不如从函数中生成谓词?

    private static Condition defaultFilters(Class<?> type, Table<?> table) {
        Condition result = noCondition();
    
        if (BaseOrganizationModel.class.isAssignableFrom(type)) {
            result = result.and(table.field("organization_id", Long.class)
                                     .eq(currentOrganization().id));
    
            if (SoftDeletableModel.class.isAssignableFrom(type))
                result = result.and(not(table.field("deleted", Boolean.class)))
        }
    
        return result;
    }
    

    现在,当您构建查询时,您可以添加过滤器:

    ctx.select(T.A, T.B)
       .from(T)
       .where(T.X.eq(1))
       .and(defaultFilters(myType, T))
       .fetch();
    

    转换 SQL 的通用方法

    如果您真的想改变您的查询(例如,在所有查询的实用程序中),那么转换方法可能更适合。有不同的方法可以解决这个问题。

    使用视图

    一些 RDBMS 可以访问视图中的会话变量。在 Oracle 中,您将在视图内将一些 SYS_CONTEXT 变量设置为您的 organization_id,然后只查询(可能可更新的)视图而不是直接查询表。不幸的是,MySQL 不能做同样的事情,请参阅 Is there any equivalent to ORACLE SYS_CONTEXT('USERENV', 'OS_USER') in MYSQL?

    I've described this approach here in this blog post。这种方法的优点是您永远不会忘记设置谓词(您可以使用 CI/CD 测试验证您的视图源代码),并且如果您忘记设置会话上下文变量,视图将不会返回任何数据,所以这是一种相当安全的方法。

    结合WITH CHECK OPTION 子句,您甚至可以防止插入错误的organization_id,从而提高安全性。

    在 jOOQ 中使用 VisitListener

    这是在 jOOQ 中执行此操作的最强大的方法,并且正是您想要的,但对于所有边缘情况来说也是一个相当棘手的方法。 See this post about implementing row level security in jOOQ。从 jOOQ 3.16 开始,将有更好的方法通过https://github.com/jOOQ/jOOQ/issues/12425 转换您的 SQL。

    注意,它不适用于不使用任何 jOOQ 查询部分的普通 SQL 模板,也不适用于基于 JDBC 的查询或您系统中可能拥有的其他查询,因此请小心使用这种方法,因为您可能会泄漏数据来自其他组织。

    当然,您也可以在 JDBC 层上实现此步骤,使用 jOOQ 的ParsingConnection or ParsingDataSource,这样您也可以拦截第三方 SQL 并附加您的谓词。

    这适用于所有 DML 语句,包括 UPDATEDELETEINSERT 有点困难,因为您必须将 INSERT .. VALUES 转换为 INSERT .. SELECT,或者如果有人想插入错误的 organization_id,则抛出异常。

    在 jOOQ 中使用 ExecuteListener

    比上面的VisitListener 方法更老套,但通常更容易正确,只需将所有语句的WHERE 子句正则表达式替换为WHERE organization_id = ... AND 中的ExecuteListener

    为了安全起见,您可以拒绝所有没有 WHERE 子句的查询,或者做一些额外的技巧来在正确的位置添加 WHERE 子句以防万一。

    使用 jOOQ 等效于 Hibernate 的 @Filter

    jOOQ 相当于 Hibernate 的 @FilterTable.where(Condition) 子句。这不是完全等价的,您必须防止在代码库中直接访问T,并确保用户只能通过将T 替换为T.where(defaultFilters(myType, T)) 的方法访问T.where(defaultFilters(myType, T))

    这种方法目前失去了T 表的类型安全性,请参阅:https://github.com/jOOQ/jOOQ/issues/8012

    【讨论】:

    • 感谢您的快速回复!我想我想对过滤器进行一些混淆,因此其他开发人员(包括我自己!)不必考虑几乎所有模型上的 organization_iddeleted 列。如果过滤器被附加到他们使用的函数中,它会产生不必要的额外工作。
    • 从广义上看,函数式方法更加优雅。我想我想尝试一些更时髦的方法来解决 Hibernate 模型中默认 @Filter 的问题。感谢您的建议!
    • @Bram:是的,我就是这么想的——因此是第二部分。我将对此进行一些扩展,因为这是您的主要兴趣。
    猜你喜欢
    • 2019-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-13
    相关资源
    最近更新 更多