使用hibernate的一个挑战是,被管理的类必须具有 默认的构造函数 。问题是没有明确的地方可以初始化类并可以检查不变量。
如果一个类的不变量依赖于多个属性,则该类的设计会变得复杂。让我们从假设的绿地设计开始:
public class A { private int x; private int y; public A(int x, int y) { this.x = x; this.y = y; checkInvariants(this.x, this.y); } private void checkInvariants(int x, int y) { if (x + y « 0) throw new IllegalArgumentException(); } }
这是不符合hibernate要求的基本实现。在构造函数中检查不变量。(checkInvariants()方法的内容并不重要,它只是为了说明类不变量可以依赖于一个以上的属性而已。)
该类可以如下使用:
new A(0, 0); new A(-1, 0); //invalid
为了满足hibernate要求,一种解决方法是添加一个 私有的默认构造函数 并 使用字段访问 。(我省略了hibernate映射。)
public class H { int x; int y; public H(int x, int y) { this.x = x; this.y = y; checkInvariants(this.x, this.y); } H(){} private void checkInvariants(int x, int y) { if (x + y « 0) throw new IllegalArgumentException(); } }
这有两个主要缺点:您将开始实现 依赖于客户端 (hibernate)的代码。理想情况下,一个类不知道其调用者。此解决方法的一个特定问题是,如果满足不变量,则 不会检查 由hibernate模式启动的实例。您信任从数据库加载的有问题的数据。即使您的应用程序是唯一使用此特定数据库模式的应用程序,管理员也始终可以进行临时更改。
第二种解决方法是 检查用户代码中的不变量 :
public class I { private int x; private int y; public I() {} public void checkInvariants() { if (x + y « 0) throw new IllegalArgumentException(); } public void setX(int x){ this.x = x; } public void setY(int y){ this.y = y; } } I i = new I(); i.setX(-1); i.setY(0); i.checkInvariants();
显然,这使用户代码 更加复杂 且 容易出错 。此设计不能满足以下要求:实例在创建后是一致的,并且在每次状态更改(方法调用)后保持一致。每个用户都必须检查他创建的每个实例的不变式(也许使用hibernate方式间接地)。
有没有更好的解决此问题的方法是:
我认为必须放松一些约束才能获得务实的解决方案。唯一的硬约束是不依赖于hibernate框架。(可以在域对象之外hibernate特定于代码的代码)。
(只是出于好奇:是否有一个支持“构造函数注入”的ORM框架?)
首先,让我解决您在第一种方法中列出的“缺点”:
您将开始实现依赖于客户端(hibernate)的代码。理想情况下,一个类不知道其调用者。
您在这里使用“依赖”一词有点麻烦。Hibernate不是“客户端”。 您 (作为开发人员/架构师/您拥有什么)选择了一个框架来实现您的持久性。因此,您将在某些使用(因此取决于)Hibernate的地方编写一些代码。就是说,上面的域对象中 没有 对Hibernate的依赖。如果愿意的话,有一个无参数的构造函数是语义要求。它没有引入实际的依赖关系。将Hibernate切换为JPA / TopLink / raw jdbc /您将拥有什么,您将不必更改域对象代码中的任何内容。
此解决方法的一个特定问题是,如果满足不变量,则不会检查由hibernate启动的实例。您信任从数据库加载的有问题的数据。即使您的应用程序是唯一使用此特定数据库模式的应用程序,管理员也始终可以进行临时更改。
您 不必“信任” 数据(更多内容请参见下文)。但是,我认为这种说法没有根据。如果要在多个应用程序中修改数据,则应在某个常见的较低层执行验证,而不要依赖每个单独的应用程序来验证数据。所述公共层可以是数据库本身(在简单情况下),也可以是提供要由多个应用程序使用的公共API的服务层。
此外,作为 日常工作的 一部分,管理员 直接 对数据库进行更改的想法是完全荒谬的。如果您在谈论特殊情况(错误修复,您拥有什么),则应将其视作此类情况(也就是说,此类情况很少发生,验证此类“关键”变更的责任在于进行变更的人;不在堆栈中的每个应用程序上)。
综上所述,如果您确实想在 加载 对象时对其进行验证,则可以轻松实现。定义一个Valid具有validate()方法的接口,并让所有相关领域对象实现该方法。您可以从以下位置调用该方法:
Valid
validate()
最后,就“构造函数注入”而言,我不知道任何 直接 支持它的框架。这样做的原因很简单-它仅对 不可变 实体有意义(因为一旦有了设置器,无论如何都要进行验证),因此意味着大量工作(处理构造函数参数映射等)几乎为零。净效应。事实上,如果你 是 关注具有不可变对象一个无参数的构造你总是可以不将它们映射为实体,而是通过HQL加载它们该 做 支持构造器注入:
select new A(x, y) from ...
更新 (以解决托马斯评论中的问题):
StringUtil.compare()
select new A(x, y, new B(c, d))