通过 Java 中的 JDBC 实现数据库独立性

2022-09-04 05:09:31

使用 JDBC 有一些限制,数据库依赖性就是这些限制之一。

在JDBC中是否有任何模式或方法可以实现数据库独立性(不使用任何其他ORM框架或工具)。

我试图通过动态多态性(为不同的DBMS创建特定的类并根据特定的SQL语法覆盖常见的CRUD操作)来实现这一点。

例如,有没有办法编写通用的SQL语句,以便它们可以在几乎每个与SQL相关的DBMS中执行?


答案 1

我认为我有资格回答,作为jOOQ的作者,这在另一个答案中已经提出过。正如我所展示的,完全有可能实现你想要做的事情,但如果你想自己动手,你面前还有很长的路要走。

我们来谈谈 JDBC

JDBC是一个优秀的网络协议抽象,所以它是一个很好的起点。但是,当您继续在API中解决更复杂的问题(例如您尝试构建的问题)时,有相当多的警告。例如:

  • 获取生成的密钥非常困难。很少有 JDBC 驱动程序可以正确理解这一点
  • 您确定正确处理了 LOB 吗?提示:您不是
  • 还有什么比 LOB 更糟糕的呢?Oracle OBJECT TYPES 中的 LOB
  • 我有没有提到Oracle对象类型?它们可以放在数组(和数组的数组)中。那时东西变得非常毛茸茸的
  • 但是与PostgreSQL的TYPE类型相比,Oracle的OBJECT类型非常棒。JDBC驱动程序根本没有帮助你,在那里
  • 尝试绑定 DB2 值。有时它有效,有时它不起作用。NULL
  • 想要支持 ?祝你好运!java.sql.Datejava.time.LocalDate
  • 说到日期,您知道数据类型有多少种不同的解释吗?TIMESTAMP WITH TIME ZONE
  • 想要支持类型?真?INTERVAL
  • 如果数据库引发多个异常,该怎么办?
  • 如果数据库通过与异常不同的 API 引发错误,该怎么办(hello SQL Server)
  • 如果您需要在获取异常之前收集警告,该怎么办?
  • 您是否知道某些数据库首先向您发送更新计数,然后才向您发送实际结果集(例如,当触发器触发时)
  • 您是否考虑过处理多个结果集?
  • 现在将上述内容与形式参数相结合OUT
  • 我们来谈谈类型BOOLEAN
  • ...我可以继续几个小时。本网站上的更多示例
  • 您是否知道某些 PostgreSQL 语句在设置为 true 时不起作用?autoCommit
  • 不是每个人都支持保存点
  • 想要使用 JDBC 对您的模式进行反向工程?没关系!DatabaseMetaData
  • 想要用于发现限定列名?井。。。ResultSetMetaData

正如你所看到的,即使JDBC对大多数人来说都做得很好(而且对于上述每个人来说,总有一个黑客解决方法适用于单个数据库。但是你想编写一个适用于所有数据库的API,所以你必须修复/解决上述所有问题。相信我。这会让你忙碌一段时间!

我们来谈谈 SQL

但到目前为止,我们只讨论了绑定到JDBC的难度。我们还没有讨论标准化SQL有多难。因此,让我们讨论一下:

  • LIMIT n OFFSET m很好,嗯?还是?或?或?如果要支持较旧的数据库,该怎么办?你会滚动自己的过滤吗?在这里,我已经为您记录了它LIMIT m, nTOP n START AT mOFFSET m ROWS FETCH NEXT n ROWS ONLYROW_NUMBER()
  • 某些数据库支持没有 .在其他数据库中,您需要类似表的内容。你去吧,一切都有记录SELECTFROMDUAL
  • 一些数据库假装他们不需要该表,直到他们的解析器中断并且您仍然需要它(你好MySQL)DUAL
  • 有些数据库支持没有 ,但它们确实需要SELECTFROMFROMWHERE / HAVING / GROUP BY
  • 您对此有何看法: .它是否适用于所有数据库?(我的意思是括号嵌套)(SELECT 1 UNION SELECT 2) UNION ALL SELECT 3
  • 是否受支持?甚至是否受支持?EXCEPT ALLEXCEPT
  • 是否受支持?FULL OUTER JOIN
  • 派生表是否需要别名,或者它们是否可以没有别名?
  • 是否允许在派生表上使用关键字?AS
  • 子句是否可以包含引用子句别名的表达式?还是仅引用子句中列的表达式?ORDER BYSELECTFROM
  • 子句可以包含表达式吗?ORDER BY
  • 派生表可以包含子句吗?ORDER BY
  • 我们来谈谈函数。是叫还是叫什么?SUBSTRING()SUBSTR()INSTR()
  • 提示,这是如何在SQLite上模拟REPEACH()函数
  • 您将如何模拟构造函数,如 ?很少有数据库具有本机支持VALUES()SELECT * FROM (VALUES (1), (2)) t(a)
  • 实际上,如果不支持派生列列表(一次性别名),您将如何模拟它?这是一个时髦的想法table(column)
  • 实际上,让我们讨论行值表达式和使用它们构建的谓词。这个: 和这个一样: .前者并非在所有地方都受支持(a, b) > (x, y)a > x OR a = x AND b > y
  • PostgreSQL可以使用Oracle 12c中的PL / SQL块进行模拟:UPDATE .. RETURNING

    declare
      t0 dbms_sql.number_table;
      t1 dbms_sql.date_table;
      c0 sys_refcursor;
      c1 sys_refcursor;
    begin
      update "TEST"."T_2155"
      set "TEST"."T_2155"."D1" = date '2003-03-03'
      returning 
        "TEST"."T_2155"."ID", 
        "TEST"."T_2155"."D1"
      bulk collect into t0, t1;
      ? := sql%rowcount; // Don't forget to fetch the row count
      open c0 for select * from table(t0);
      open c1 for select * from table(t1);
      ? := c0; // These need to be bound as OracleTypes.CURSOR OUT params
      ? := c1; // These need to be bound as OracleTypes.CURSOR OUT params
    end;
    

结论

如您所见,它完全可以做到。我已经做到了,它被称为jOOQ。这可能是我职业生涯中最大的挑战,而且很有趣。jOOQ 3.10将具有解析器,它可以从SQL字符串(在任何方言中)转换为另一个SQL字符串(在特定方言中),这是下一个级别的供应商不可知性。

但要走到这一步还有很长的路要走。在我做jOOQ(始于2009年)之前,我已经深入使用Oracle SQL和内部基于JDBC的框架(就像你计划编写的框架一样)。我写了jOOQ,因为我看到许多内部框架被编写出来,但没有一个能很好地完成这项工作。开发人员总是解决.. .. - 这是容易的部分。有些人设法进入了游戏,也许就是这样。然后他们放弃了这项任务,因为他们有更重要的事情要做,而不是维护无聊和有缺陷的基础设施软件。SELECTFROMWHEREJOINGROUP BY

现在,我不知道你自己做这件事的动机是什么,但我的建议是:

  • 如果要编写与供应商无关的 SQL,请使用 jOOQ
  • 如果要实现与供应商无关的对象图持久性,请使用休眠

你可以尝试构建自己的jOOQ(或Hibernate)。这是一个有趣的挑战。但是如果你有截止日期,我真的建议你回顾一下上述选项。


答案 2

首先,数据库独立性很难。真的,真的很难;要在不使用ORM或其他工具的情况下实现它,您将不得不交易解决方案设计的其他方面。简单性和可维护性将受到影响,实现解决方案的努力也将受到影响。

所以,我真的非常非常确定这是一个高优先级的要求 - 一个假设的“如果我们想从Oracle切换到SQL Server怎么办”的问题 - 在我看来 - 没有足够的理由来产生额外的成本......

如果你必须提供这个功能,ORM是迄今为止最简单的方法。ORM框架在设计时特别考虑了数据库独立性(它们的复杂性至少部分是由于该要求)。他们通过将数据库实现抽象到更高的级别来做到这一点;而不是在SQL语句中思考,鼓励你考虑域对象。

话虽如此...

我已经提供了一个解决方案(不是在Java中,而是原则),它允许数据库独立性。我们将 SQL 语句存储为资源,并通过资源文件加载它们。默认值为 ANSI SQL,它应该适用于任何与 SQL 兼容的现代数据库。

我们为支持的每种数据库风格(在我们的例子中是MySQL和Oracle)提供了资源文件,并使用覆盖来加载特定于数据库的SQL语句(如果它们存在)。

这类似于大多数语言中的国际化 - 首先查找特定于区域设置的字符串,如果找不到,则回退到默认值。

Java的资源包将使这变得非常容易。不在应用程序中对SQL进行硬编码还有其他好处 - 在不更改应用程序代码的情况下修复数据库问题要容易得多,您可以将修复程序部署为“资源”更新,而不是发布新的二进制文件等。


推荐