检查hibernate映射类中的不变量

使用hibernate的一个挑战是,manged类必须有一个默认的构造函数 。 问题是没有明确的点来初始化类并且可以检查不变量。

如果一个类具有依赖于多个属性的不变量,则类设计变得复杂。 让我们从假设的绿地设计开始:

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)的代码。 理想情况下,一个class级不知道其来电者。 *此解决方法的一个特定问题是,如果满足不变量,则不会检查由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框架没有依赖性。 (域对象之外的Hibernate特定代码是可以的)。

(出于好奇:有没有一个支持“构造函数注入”的ORM框架?)

首先,让我解决您列入第一种方法的“缺点”:

您开始实现依赖于客户端的代码(Hibernate)。 理想情况下,一个class级不知道其来电者。

你在这里使用“依赖”这个词有点快。 Hibernate不是“客户端”; 它是一个框架(作为开发人员/架构师/你有什么)选择实现你的持久性。 因此,您将在某处使用(并因此依赖于)Hibernate。 也就是说,上面的域对象中没有对Hibernate的依赖。 如果你愿意,拥有一个no-arg构造函数是一个语义要求; 它没有引入实际的依赖。 切换Hibernate for JPA / TopLink / raw jdbc /你有什么,你不必在域对象代码中改变一件事。

此解决方法的一个特定问题是,如果满足不变量,则不会检查由hibernate启动的实例。 您信任从数据库加载的数据是有问题的。 即使您的应用程序是唯一使用此特定数据库架构的应用程序,管理员也可能会进行临时更改。

不必“信任”数据(更多内容见下文)。 但是,我认为这个论点没有价值。 如果要在多个应用程序中修改数据,则应在某些常见的较低层执行validation,而不是依赖于每个应用程序来validation数据。 所述公共层可以是数据库本身(在简单情况下)或提供由多个应用程序使用的公共API的服务层。

此外,作为日常工作的一部分,管理员直接对数据库进行更改的概念是完全荒谬的。 如果你在谈论特殊情况(错误修复,你有什么),他们应该被视为这样(也就是说,大概是这样的事情很少发生,validation这种“关键”变化的负担取决于谁做出改变;不在堆栈中的每个应用程序上)。

所有这一切,如果您确实想要在加载对象时validation它们,那么实现起来相当简单。 定义具有validate()方法的Valid接口,并让每个相关的域对象实现它。 您可以从以下位置调用该方法:

  1. 加载对象后的DAO /服务。
  2. Hibernate Interceptor或Listener – 都是在Hibernate配置中设置的; 您需要做的就是实现其中一个来检查正在加载的对象是否实现了Valid ,如果是,则调用该方法。
  3. 或者您可以使用Hibernate Validator ,但是当您注释它们时,它会将您的域对象绑定到Hibernate。

最后,就“构造函数注入”而言 – 我不知道任何直接支持它的框架。 原因很简单 – 它只对不可变实体有意义(因为一旦你有了setter,你必须处理validation),因此意味着很多工作(处理构造函数参数映射等等)几乎为零净效应。 事实上,如果你关心的是为不可变对象设置一个无参数的构造函数,你总是不能将它们映射为实体,而是通过支持构造函数注入的 HQL加载它们:

 select new A(x, y) from ... 

更新 (以解决托马斯评论中的观点):

  1. 我只提到了拦截器的完整性; 在这种情况下,监听器更合适。 实体完全初始化后调用PostLoadEventListener 。
  2. 再一次,拥有no-arg构造函数不是依赖项。 这是一个契约,是的,但它并没有以任何方式将你的代码与Hibernate联系起来。 而且就合同而言,它是javabean规范的一部分(事实上,它的限制性较小,因为构造函数不必公开)所以在大多数情况下,无论如何你都会遵循它。
  3. 访问数据库。 “数据库重构和迁移很常见” – 是的,它们是。 但这只是代码; 你编写它,测试它,运行集成测试,将它部署到生产环境。 如果您的域模型对象使用了您在某个实用程序类中编写的某些StringUtil.compare()方法,那么您没有重新检查结果,是吗? 同样,域对象不应该检查您的迁移脚本是否没有破坏任何东西 – 您应该对此进行适当的测试。 “能够进行临时查询……是其中一项function” – 绝对。 查询 。 例如,在用于报告的“只读”查询中(即使如此,在许多情况下,通过API更合适)。 但手动数据操作非紧急 – 绝对不是。
  4. 可变性使构造函数注入无关紧要 。 我不是说你不能拥有非默认构造函数 – 你可以,你可以在你的代码中使用它。 但是如果你有setter方法你就不能使用你的构造函数进行validation,那么它是否存在并不重要。
  5. HQL构造函数注入和关联。 嵌套的构造函数不会像我所知的那样工作(例如,你不能编写select new A(x, y, new B(c, d)) ;所以为了获取关联,你需要检索它们作为select子句中的实体,这意味着他们需要自己拥有no-arg构造函数:-)或者你可以在“main”实体上有一个构造函数,它将所有需要的嵌套属性作为参数,并在内部构造/填充关联,但这是边缘疯狂的: – )

关系模型的一致性是喘息的至关重要的概念。 由于作为关系数据建模基础的原则的固有数学稳定性,我们可以完全确信任何查询原始数据库的结果确实会产生真正有效的事实。 – 来自“The Art of Sql”一书 – Stephane Faroult

您的数据库应包含有效的事实或事实,您从数据库加载的数据不需要任何额外的validation,您从数据库中取出的数据应该足够好。

但如果您担心数据不好,那么我会建议存在更严重的问题。

我见过的解决方案包括一个临时数据库,其中所有数据都被清理,在进入真实数据库之前进行validation,以便在输入之前检查任何手动修复。

无论哪种方式,一旦您的数据库中的数据或虚假陈述损坏,您还可以信任什么?

一种方法将基于您的“类I”,但是类本身在第一次实际使用字段时调用checkInvariants()。 这允许字段在赋值期间暂时无效(在调用setX()之后但在例如setY()之前)。 但它仍然保证只使用有效数据。

你的示例类从不使用这些值,因此我假设可以通过getX(),getY()和doSomething()来访问值,例如:

 public int getX(){ return x; } public int getY(){ return y; } public void doSomething(){ x = x + y; } 

你会改变它:

 private boolean validated = false; public int getX(){ if (!validated) { checkInvariants(); } return x; } public int getY(){ if (!validated) { checkInvariants(); } return y; } public void doSomething(){ if (!validated) { checkInvariants(); } x = x + y; } public void checkInvariants() { validated = true; if (x + y « 0) throw new IllegalArgumentException(); } 

如果需要,您可以将’if(!validated)’移动到checkInvariants()中。

您也可以查看Hibernate Validator,尽管它可能不符合您的框架独立性要求。 http://docs.huihoo.com/hibernate/annotations-reference-3.2.1/validator.html