【问题标题】:Database independence through JDBC in javajava中通过JDBC实现数据库独立性
【发布时间】:2017-11-02 08:08:46
【问题描述】:

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

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

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

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

【问题讨论】:

  • ORM 正是这样做的(除了许多其他方便的功能)。你试过JPA吗?
  • id say CriteriaQuery 非常通用,但它需要 JPA,它是一个 ORM
  • 我认为您不应该使用覆盖特定 SQL 语法的 CRUD 操作的类。有已建立的工具,JPA(接口),Hibernate(一种实现),Spring-data(默认的 CRUD 操作很容易)。不要重新发明轮子。在简单的情况下,您可以尝试使用 JDBC,但仅使用您部署的所有数据库都支持的 SQL 子集。 (ANSI sql-92 可以作为起点)。
  • 你好!我只能通过 JDBC 来实现它。
  • 你能详细说明什么是 ANSI SQL 吗?我的意思是我知道正常的 SQL 语句,并且我知道如何在 MySQL 上执行 CRUD 操作(包括连接),但什么是 ANSI SQL ?是不是一组规则或标准,我们可以根据这些规则或标准生成适用于任何 SQL DBMS 的通用 SQL??

标签: java sql jdbc


【解决方案1】:

作为jOOQ 的作者,我认为我有资格回答,another answer 已经提出了建议。正如我所展示的,完全有可能实现你想要做的事情,但如果你想自己动手,你还有很长的路要走。

我们来谈谈 JDBC

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

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

正如您所看到的,即使 JDBC 对大多数人来说都做得很好(并且对于上述每种情况,总是有一个 hacky 解决方法适用于单个数据库。但是您想编写一个适用于所有数据库的 API,因此您必须修复/解决上述所有问题。相信我。这会让您忙一阵子!

说说SQL

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

  • LIMIT n OFFSET m 很好,嗯?还是LIMIT m, n?还是TOP n START AT m?还是OFFSET m ROWS FETCH NEXT n ROWS ONLY?如果您想支持较旧的数据库怎么办?你会推出自己的ROW_NUMBER() 过滤吗? Here, I've documented it for you
  • 有些数据库支持SELECT,但不支持FROM。在其他数据库中,您需要类似DUAL 表。 There you go, all documented
  • 一些数据库假装他们不需要那个 DUAL 表,直到他们的解析器中断并且你仍然需要它(你好 MySQL)
  • 一些数据库支持SELECT,但不支持FROM,但对于WHERE/HAVING/GROUP BY,它们确实需要FROM
  • 您对此有何看法:(SELECT 1 UNION SELECT 2) UNION ALL SELECT 3。它适用于所有数据库吗? (我的意思是括号内的嵌套)
  • 是否支持EXCEPT ALL?甚至支持EXCEPT 吗?
  • 是否支持FULL OUTER JOIN
  • 派生表是否需要别名或者没有别名也可以生存?
  • 派生表是否允许使用关键字AS
  • ORDER BY 子句能否包含引用来自SELECT 子句的别名的表达式?还是仅引用 FROM 子句中的列的表达式?
  • ORDER BY 子句可以包含表达式吗?
  • 派生表可以包含ORDER BY 子句吗?
  • 让我们谈谈函数。是调用SUBSTRING()SUBSTR()INSTR() 还是什么?
  • 提示,this is how to emulate the REPEAT() function on SQLite
  • 您将如何模拟VALUES() 构造函数,例如SELECT * FROM (VALUES (1), (2)) t(a)?很少有数据库具有原生支持
  • 事实上,如果派生列列表不受支持,您将如何模拟它(一次性使用别名table(column))? Here's a funky idea
  • 事实上,让我们讨论一下行值表达式和用它们构建的谓词。此:(a, b) > (x, y) 与此相同:a > x OR a = x AND b > y。并非所有地方都支持前者
  • PostgreSQL 的 UPDATE .. RETURNING 可以使用 Oracle 12c 中的 PL/SQL 块进行模拟:

    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 will feature a parser,可以translate from a SQL string (in any dialect) to another SQL string (in a specific dialect),这是供应商不可知性的下一个级别。

但要到达这里还有很长的路要走。在我做 jOOQ(从 2009 年开始)之前,我曾深入研究过 Oracle SQL 和内部基于 JDBC 的框架(就像您计划编写的那样)。我写了 jOOQ 是因为我见过很多内部框架正在编写,但没有一个能很好地完成这项工作。开发人员总是处理SELECT .. FROM .. WHERE - 这是最简单的部分。有些人设法在游戏中获得了JOIN,也许还有GROUP BY,仅此而已。然后他们放弃了这项任务,因为他们有更重要的事情要做,而不是维护无聊和有漏洞的基础设施软件。

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

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

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

【讨论】:

    【解决方案2】:

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

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

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

    说了这么多……

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

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

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

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

    【讨论】:

    • +1 - 使用普通的 jdbc,这是我推荐的方式,人们应该具备各种 RDBM 方面的专业知识,这对于任何团队来说都不是真的。
    • “简单性和可维护性将受到影响”[需要引用]。就我个人而言,我发现 jOOQ 和 QueryDSL 都是可维护性的福音,因为它们使编译器能够进行类型检查查询,并使 IDE 支持重构。
    • 嗨@meriton - 我同意 JOOQ 和 QueryDSL 很棒 - 它们确实增强了可维护性。但是,问题是“不使用 ORM 或任何其他工具”。如果你自己动手,我认为可以说你牺牲了简单性和可维护性......
    【解决方案3】:

    解决数据库可移植性的方法有两种:

    1. 减去不常见的功能 - 您可以删除所有特定于数据库的功能,这将为您留下 SQL-92 功能集。这就是 ORM 工具所做的,因为所有 RDBMS 几乎相同地处理常见的 CRUD 语句。但是,当您需要执行特定于数据库的查询时,您需要使用原生 SQL。
    2. 专业化 - 您可以使用特定于数据库的方言并模拟从一个数据库到另一个数据库的功能,例如 PIVOT。这是jOOQ使用的方法。

    但是,既然您明确表示:

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

    你需要使用 JDBC API 来解决这个问题,对吧?

    不采用 ORM 或 jOOQ 方法(实际上意味着实现您自己的数据访问框架)的唯一方法是为每个受支持的数据库使用自定义 DAO。

    因此,您可以定义服务层使用的 DAO 接口,例如 ProductDAO,并为每个支持的数据库实现一个和一个:

    • OracleProductDAOImpl
    • MySQLProductDAOImpl
    • PostgreSQLProductDAOImpl
    • SQLServerProductDAOImpl

    或者,您可以使用单个ProductDAOImpl,但是您需要使用存储过程并确保每个存储过程都在数据库中实现。

    因此,尽管有可能,但您基本上处于 2000 年代初期我们没有 Hibernate 或 jOOQ 的情况。根据我的经验,这比使用成熟的数据访问框架要多得多。

    【讨论】:

      【解决方案4】:

      在我看来,您实际上试图通过创建一个允许您一般访问数据库的库来在那里构建的东西与许多 ORM 已经在做的非常相似。

      你试过jOOQ吗?它与那里的其他 ORM 完全不同,并且可以满足您的需求。可以将其称为“工具”,但我也可以将您尝试构建的内容称为“工具”。

      jOOQ 致力于成为一种用于进行可移植数据库调用的 Java 原生语言,所以听起来它正是您正在寻找的。​​p>

      https://www.jooq.org/

      【讨论】:

      • 感谢您的回答 :) 但我只需要通过 JDBC 来完成。截至目前,我不允许使用任何 ORM 工具或框架。我正在寻找一种仅通过 JDBC 实现此目的的方法。
      • 好的,但请记住,您在这里所说的几乎正是构建 jOOQ 的人的起点。因此,最终您将使用工具或框架,它只是您构建的工具或框架,而不是预先存在的工具或框架。
      • 没有什么比重新发明轮子更能说明绝妙的想法了。
      【解决方案5】:

      虽然(非常)迟到并且没有真正带来任何新东西,但我想支持一下并扩展 Neville'sVlad's 的答案(作为一个合理的选择)。

      像 jOOQ 和 Hibernate 这样的框架可能是更好的选择,尤其是 IF 您想要获得的是完全通用的东西(即处理最可以想象的数据库操作)而不是一些涉及 SQL 的特定任务。但是,由于它们似乎不是您的选择,以一种或另一种方式外部化 SQL 实际上可能会让您走得很远(从 SQL 方言的角度来看,不一定从 JDBC 绑定问题之前在Lukas' answer 中很好地概述了)。

      我以前曾开发过一个在 Oracle 和 DB2 上运行的基于 Java 的自定义 ETL 引擎(成功地)。您可以想象,我们需要在速度方面可以获得的最佳查询,而我们的 DBA 正在使用书中的每一个技巧对它们进行优化,然后是一些技巧!因此,我们不仅要适应 2 个 SQL 方言,而且我们必须执行的查询也是高度定制的(提示等)......我们确定的解决方案是 在运行时使用模板生成 SQL引擎(当时是 Velocity)和 自定义模板,为目标数据库量身定制(可能类似于 Hibernate 或 jOOQ 构建最终 SQL 的方式)。诚然,我们有自己的“背景”,具有特定且明确定义的需求……

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-02
        • 2010-09-09
        • 2013-10-11
        • 1970-01-01
        • 1970-01-01
        • 2012-02-12
        • 2012-12-04
        • 1970-01-01
        相关资源
        最近更新 更多