首先是事实,还是为什么需要基于数据库设备来设计系统

哈Ha!

我们将继续在数据库级别探讨JavaSpring主题。今天,我们建议阅读以下内容:为什么在设计大型应用程序时,对于执行此操作的方式以及该规则的例外情况,应该具有决定性的重要性,而不是Java结构。

在这篇迟来的文章中,我将解释为什么我相信在几乎所有情况下,应用程序中的数据模型都应“基于数据库”而不是“基于Java的功能”(或使用的另一种客户端语言)设计。选择第二种方法时,一旦项目开始发展,您就将经历漫长的痛苦历程。

本文基于有关堆栈溢出的一个问题/ r / java/ r / programming

部分中有关reddit的有趣讨论

代码生成


我如此惊讶的是,这么小的用户层对jOOQ有所了解,却对jOOQ在工作时严重依赖于源代码生成这一事实感到愤怒。没有人会打扰您使用jOOQ,并且不会强迫您使用代码生成。但是默认情况下(如手册中所述),使用jOOQ的过程是这样的:从(继承的)数据库模式开始,用jOOQ代码生成器进行反向工程,以便获得代表表的一组类,然后向这些表写入类型安全的查询:

	for (Record2<String, String> record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^      
//     ,    
//   SELECT 
 
       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^  
       .from(ACTOR)
       .orderBy(1, 2)) {
    // ...
}

该代码可以在程序集外部手动生成,也可以在每个程序集手动生成。例如,这种更新可以在迁移Flyway数据库之后立即进行,这也可以手动或自动完成

源代码生成


此类代码生成方法(手动和自动)与各种理念,优缺点有关,在本文中我将不对其进行详细讨论。但是,总的来说,生成的代码的全部要点是它使我们能够在Java中重现我们认为在系统内或系统外的“真相”。从某种意义上说,生成字节码,机器代码或某些其他基于源代码的编译器会执行相同的操作-无论出于何种原因,我们都会用另一种语言表示“真相”。

有许多这样的代码生成器。例如,XJC可以基于XSD或WSDL文件生成Java代码。原理始终相同:

  • 有一定的道理(内部或外部),例如规范,数据模型等。
  • 我们需要在我们的编程语言中以局部方式表示这一事实。

此外,几乎总是建议生成这样的表示,以避免冗余。

类型提供者和注释处理


注意:jOOQ的另一种更现代,更具体的代码生成方法涉及使用类型提供程序,该形式提供程序在F#中实现在这种情况下,代码实际上是在编译阶段由编译器生成的。以源代码的形式,这种代码基本上不存在。Java中也有类似的工具,尽管工具不太优雅-它们是注解处理器,例如Lombok

从某种意义上说,这里发生的情况与第一种情况相同,但以下情况除外:

  • 您看不到生成的代码(也许这种情况在某人看来并不那么令人反感?)
  • , , , «» . Lombok, “”. , .

?


除了要手动或自动启动代码更好的棘手问题外,还有必要提到有些人认为根本不需要代码生成。我最常遇到的这种观点的理由是,那时很难配置组装管道。是的,真的很难。还有额外的基础设施成本。如果您刚开始使用某种产品(例如jOOQ,JAXB或Hibernate等),则需要花费一些时间来设置您想花在学习API本身上的工作环境,然后从中获取价值。

如果与理解生成器设备相关的开销太大,那么API确实在代码生成器的可用性方面做了一点工作(并且将来,事实证明其中的用户配置很复杂)。易用性应是所有此类API的最高优先级。但这只是反对代码生成的一种说法。对于其余的内容,编写内部或外部真实情况的本地表示完全是完全手动的。

许多人会说他们没有时间做所有这些事情。他们有他们的超级产品的截止日期。稍后的某个时候,我们会梳理装配输送机,这将是及时的。我会回答他们:


OriginalAlan O'Rourke,Audience Stack

但是在Hibernate / JPA中,编写“针对Java”的代码是如此容易。

真。对于Hibernate及其用户而言,这既是福也是祸。在Hibernate中,您可以简单地编写几个实体,如下所示:

	@Entity
class Book {
  @Id
  int id;
  String title;
}

几乎一切就绪。现在,Hibernate的命运是生成复杂的“细节”,以详细说明如何在SQL“方言”的DDL上定义此实体:

	CREATE TABLE book (
  id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
  title VARCHAR(50),
 
  CONSTRAINT pk_book PRIMARY KEY (id)
);
 
CREATE INDEX i_book_title ON book (title);

...,我们开始驱动该应用程序。一个非常不错的机会,可以快速入门并尝试不同的方法。

但是,允许。我在骗人

  • Hibernate是否真的应用了此命名主键的定义?
  • Hibernate会在TITLE中创建索引吗?“我确定我们将需要他。”
  • Hibernate是否在身份规范中准确地使此密钥可识别?

可能不是。如果您是从头开始开发项目,则在添加必要的批注后立即删除旧数据库并生成一个新数据库总是很方便的。因此,Book实体最终将采用以下形式:

	@Entity
@Table(name = "book", indexes = {
  @Index(name = "i_book_title", columnList = "title")
})
class Book {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  int id;
  String title;
}

凉。再生。同样,在这种情况下,开始时将非常容易。

但是那你得为此付出代价


迟早您必须投入生产。就在那时,这样的模型将停止工作。因为:

在生产中,如果有必要,将不再可能丢弃旧数据库并从头开始。您的数据库将变成旧数据库。

从现在开始,您将必须编写DDL迁移脚本,例如,使用Flyway。那您的实体又会怎样?您可以手动调整它们(从而使您的工作量加倍),也可以命令Hibernate为您重新生成它们(以这种方式生成的可能性满足您的期望的可能性有多大?)无论如何您都会失败。

因此,一旦投入生产,您将需要热补丁。而且它们需要非常快地投入生产。由于您尚未准备好并组织顺利的迁移流程来进行生产,因此您正在疯狂地修补所有内容。这样您就没有时间做正确的事情。并责备Hibernate,因为总是有人应责怪您,而不是您。……

相反,从一开始,一切都可以用完全不同的方式来完成。例如,将轮毂放在自行车上。

数据库优先


数据库架构中的真实“真相”及其上的“主权”位于数据库内部。仅在数据库本身中定义了一个方案,在其他任何地方都没有定义,并且每个客户端都有该方案的副本,因此完全建议强加该方案及其完整性的兼容性,然后直接在存储信息的数据库中进行。
这甚至是陈旧的智慧。主键和唯一键很好。外键很好。检查限制是好的。声明很好。

而且,这还不是全部。例如,使用Oracle,您可能要指定:

  • 您的表在哪个表空间中?
  • 它的PCTFREE值是多少?
  • 您序列中缓存的大小是多少(在标识符之后)

也许在小型系统中所有这些都不重要,但是不必等待过渡到``大数据''区域-可以并且更早地开始受益于供应商提供的数据存储的优化,例如上面提到的那些。我见过的所有ORM(包括jOOQ)都没有提供对您可能想在数据库中使用的全套DDL选项的访问权限。ORM提供了一些有助于编写DDL的工具。

但是最后,一个精心设计的电路是用DDL手动编写的。任何生成的DDL只是它的近似值。

客户端模型呢?


如上所述,在客户端上,您将需要数据库模式的副本,即客户端视图。不用说,此客户视图必须与真实模型同步。实现此目标的最佳方法是什么?使用代码生成器。

所有数据库都通过SQL提供其元信息。以下是从数据库中获取SQL不同方言中的所有表的方法:

	-- H2, HSQLDB, MySQL, PostgreSQL, SQL Server
SELECT table_schema, table_name
FROM information_schema.tables
 
-- DB2
SELECT tabschema, tabname
FROM syscat.tables
 
-- Oracle
SELECT owner, table_name
FROM all_tables
 
-- SQLite
SELECT name
FROM sqlite_master
 
-- Teradata
SELECT databasename, tablename
FROM dbc.tables

这些查询(或类似的查询,取决于您是否还必须考虑表示形式,实例化表示形式,具有表值的函数)也使用DatabaseMetaData.getTables()来自JDBC 的调用或使用jOOQ元模块来执行。

根据此类查询的结果,无论您的客户端使用什么技术,生成数据库模型的任何客户端视图都相对容易。

  • 如果使用JDBC或Spring,则可以创建一组字符串常量
  • 如果使用JPA,则可以自己生成实体
  • 如果使用jOOQ,则可以生成jOOQ元模型

根据您的客户端API提供多少功能(例如jOOQ或JPA),生成的元模型可以真正丰富和完整。例如出现在jOOQ 3.11中的隐式联接的可能性,它依赖于所生成的有关表之间外键关系的元信息。

现在,数据库的任何增量将自动导致更新客户端代码。想象一下:

ALTER TABLE book RENAME COLUMN title TO book_title;

您真的想两次做这项工作吗?没门。只需修复DDL,通过组装管道运行DDL,然后获取更新的实体:

@Entity
@Table(name = "book", indexes = {
 
  //    ?
  @Index(name = "i_book_title", columnList = "book_title")
})
class Book {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  int id;
 
  @Column("book_title")
  String bookTitle;
}

或更新的jOOQ类。大多数DDL更改也会影响语义,而不仅仅是语法。因此,可以方便地在已编译的代码中查看哪些代码将(或可能)受到数据库增量的影响。

唯一的真理


无论您使用哪种技术,总有一个模型是子系统真实性的唯一来源-至少,我们应该为此而努力,并避免这种“混乱”无处不在的企业混乱。一切都可以简单得多。如果您只是与其他系统交换XML文件,请使用XSD。查看来自jOOQ的XML格式的INFORMATION_SCHEMA元模型:https ://www.jooq.org/xsd/jooq-meta-3.10.0.xsd


  • XSD是众所周知的
  • XSD XML
  • XSD
  • XSD Java XJC

最后一点很重要。使用XML消息与外部系统通信时,我们希望确保消息的有效性。使用JAXB,XJC和XSD非常容易实现。期望在进行“ Java first”设计时,以Java对象的形式生成消息时,可以以某种方式清楚地将它们以XML格式显示并发送到另一个系统以供使用是完全疯狂的。以这种方式生成的XML的质量很差,没有文档记录,并且很难开发。如果在这样的接口上达成服务质量(SLA)级别的协议,我们将立即破坏它。

老实说,这就是从API到JSON的所有时间,但这是另一个故事,我下次发誓...

数据库:是同一回事


在使用数据库时,您了解它们在原则上是相似的。基地拥有其数据,并且必须管理该方案。对电路所做的任何修改都必须直接在DDL上实现,以更新单个事实来源。

当发生源更新时,所有客户端还必须更新其模型副本。某些客户端可以使用jOOQ和Hibernate或JDBC(或同时全部)用Java编写。可以用Perl编写其他客户端(祝他们好运),其他可以使用C#编写。不要紧。主要模型在数据库中。使用ORM生成的模型通常质量较差,但记录较差且难以开发。

因此,不要犯错误。从一开始就没有错误。从数据库工作。建立可以自动化的部署管道。打开代码生成器,以方便复制数据库模型并将其转储到客户端。不必再担心代码生成器。他们很好。有了他们,您将变得更有生产力。您从一开始就只需要花费一点时间来配置它们-然后您将获得数年的生产率提高,这将成为您项目的历史。

在那之前,谢谢。

说明


为了清楚起见:本文绝不主张在数据库模型下需要弯曲整个系统(即主题领域,业务逻辑等)。在本文中,我说与数据库交互的客户端代码必须基于数据库模型起作用,以便它不会以“第一类”状态重现数据库模型。此逻辑通常位于客户端上的数据访问级别。

在仍然保留在某些地方的两层体系结构中,这样的系统模型可能是唯一可能的模型。但是,在大多数系统中,对我来说,数据访问级别似乎是封装数据库模型的“子系统”。

例外情况


任何规则都有例外,我已经说过,具有数据库优先权和源代码生成的方法有时是不合适的。这里有几个例外(可能还有其他例外):

  • 当电路未知时,必须将其断开。例如,您是工具的提供者,可以帮助用户导航任何方案。ew 没有代码生成。但仍然-数据库高于一切。
  • 应在动态生成电路时解决某些问题。这个示例似乎是实体属性值模式的一个稍微有些虚构的版本,即您实际上没有定义明确的架构。在这种情况下,通常根本无法确定RDBMS是否适合您。

本质上例外是例外。在大多数涉及使用RDBMS的情况下,该方案是预先知道的,它位于RDBMS内部,并且是“真相”的唯一来源,所有客户都必须获取从该方案派生的副本。理想情况下,您需要使用代码生成器。

All Articles