检查hibernate映射类中的不变量 [英] Check invariants in hibernate mapped classes

查看:89
本文介绍了检查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();




$ b这些是不符合休眠状态的基本实现要求。在构造函数中检查不变量。 (checkInvariants()方法的内容无关紧要,它仅用于说明类不变量可以依赖于更多的属性。)



可以使用该类如下:

  new A(0,0); 
new A(-1,0); //无效

为了满足休眠需求,一个解决方法是添加一个私有默认构造函数使用字段访问。 (我省略了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);

$ b $ H(){}

private void checkInvariants(int x,int y){
if(x + y«0)throw new IllegalArgumentException();




$ b $ p
$ b

这有两个主要缺点:$ b​​ $ b *您开始实现依赖于客户端(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框架支持构造函数注入?)

解决方案

首先,让我解决您在第一种方法中列出的缺点:
$ b


你开始实现代码
取决于客户端(Hibernate)。
理想情况下,一个类不知道它的
调用者。


您使用依赖这里有点快和松动。 Hibernate不是一个客户端;它是一个框架(作为开发者/架构师/你有什么)选择实现你的持久性。因此,您将在某处使用(并依赖于)Hibernate的代码。也就是说,上面的域对象中有 NO 依赖于Hibernate。如果你愿意,有一个无参数构造函数是一个语义要求;它不会引入实际的依赖关系。将Hibernate切换为JPA / TopLink / raw jdbc /你有什么和你不需要在你的域对象代码中改变一件事情。


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


您不必信任数据(更多内容在下面)。但是,我认为这个论点不具有优点。如果您要在多个应用程序中修改数据,则应该在某个公共底层执行验证,而不是依赖每个单独的应用程序来验证数据。所述通用层可以是数据库本身(简单情况下)或提供通用API的服务层,以供多个应用程序使用。



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



所有这些都说明了,如果您想要在加载时验证对象, ,它的实现是相当简单的。定义一个有效的接口,该接口具有 validate()方法并让每个关注的域对象实现它。您可以通过以下方式调用该方法:


  1. 加载对象后您的DAO / service。

  2. Hibernate Interceptor或Listener - 都是在Hibernate配置中设置;所有你需要做的就是实现其中一个来检查被加载的对象是否实现 Valid ,如果是,则调用该方法。
  3. 或者,您可以使用 Hibernate验证器,但是

最后,就构造函数注入而言 - I不知道任何直接支持的框架。原因很简单 - 它只对不可变的实体有意义(因为一旦你有setter你必须处理验证),因此意味着很多工作(处理构造函数参数映射等) ...)几乎为零的净效应。事实上,如果您 关注的是拥有不可变对象的无参数构造函数,那么您始终不能将它们映射为实体,而是通过支持 的HQL加载它们构造函数注入

 选择新的A(x,y)从... 
$ b

更新(解决Thomas评论中的要点):
$ b $ ol

  • 我只提到拦截器的完整性;听众在这种情况下更合适。 PostLoadEventListener 在实体为完全初始化。

  • 再一次,没有参数构造函数不是依赖项。这是一个合同,是的,但它并不以任何方式将您的代码绑定到Hibernate。就合同而言,它是javabean规范的一部分(事实上,限制较少,因为构造函数不必是公共的),所以在大多数情况下,您都会遵循它。

  • 访问数据库。 数据库重构和迁移很常见 - 是的,它们是。但它只是代码;您编写它,测试它,运行集成测试,将其部署到生产环境。如果您的域模型对象使用某些实用程序类中编写的某些 StringUtil.compare()方法,那么您没有重新检查结果,是吗?同样,域对象不应该检查你的迁移脚本没有破坏任何东西 - 你应该有适当的测试。 做临时查询的能力......是其中的一个特点 - 绝对是。的查询即可。例如,在用于报告的只读查询中(即使如此,在很多情况下,通过API也更合适)。但手动数据操作非紧急 - 绝对不是。
  • 可变性使构造函数注入不相关。我并不是说你不能拥有非默认的构造函数 - 你可以,并且你可以在你的代码中使用它。但是如果你有setter方法,你不能使用你的构造函数进行验证,所以不管它是否真的没有关系。

  • HQL构造函数注入和关联。 嵌套构造函数不会像我所知的那样工作(例如,您不能编写 select new A(x,y,new B(c,d));所以为了获取关联,你需要将它们作为select子句中的实体来检索它们,这意味着它们本身需要没有arg构造函数:-)或者你可以在main实体上有一个构造函数它将所有需要的嵌套属性作为参数,并在内部构造/填充关联,但这很疯狂: - )

    One challenge using hibernate is that manged classes have to have a default constructor. The problem is that there is no explicit point where the class is initialized and invariants can be checked.

    If a class has invariants that depend on more than one property the class design gets complex. Let's start with the hypothetical green-field design:

    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(); 
        } 
    }
    

    This is the base implementation that does not meet the hibernate requirements. The invariant is checked in the constructor. (The content of the checkInvariants() method does not matter it's only presented to illustrate that the class invariants can depend on more that one property.)

    The class can be used as follows:

    new A(0, 0); 
    new A(-1, 0); //invalid 
    

    To meet the hibernate requirements one workaround is to add a private default constructor and use field access. (I omitted the hibernate mappings.)

    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(); 
        } 
    } 
    

    This has two major drawbacks: * You're starting to implement code that depends on the client (Hibernate). Ideally, a class does not know its callers. * A specific issue with this workaround is that instances initiated by hibernate are not checked if the meet the invariants. You're trusting data that is loaded from the database which is problematic. Even if your application is the only one using this specific database schema there is always the possibility of ad-hoc changes by administrators.

    A second workaround is to check invariants in user code:

    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(); 
    

    Obviously, this makes the user code more complex and error-prone. This design does not fulfill the expectation that an instance is consistent after creation and stays consistent after each change of state (method call). Every user has to check the invariants for every instance he creates (maybe indirectly with hibernate).

    Is there a better solution to this problem which is:

    • not overly complex
    • without explicit knowledge about its users
    • without a dependency to the hibernate framework?

    I suppose some of the constraints have to be loosened to get to a pragmatic solution. The only hard constraint is that there is no dependency to the hibernate framework. (Hibernate specific code outside of domain objects is okay).

    (Just out of curiosity: is there a ORM framework that does support "constructor injection"?)

    解决方案

    First, let me address the "drawbacks" you've listed to your first approach:

    You're starting to implement code that depends on the client (Hibernate). Ideally, a class does not know its callers.

    You're using the word "dependency" a bit fast and loose here. Hibernate is not a "client"; it's a framework you (as developer / architect / what have you) chose to implement your persistence with. Therefore, you're going to have some code somewhere that uses (and, thus, depends on) Hibernate. That said, there is NO dependency on Hibernate in your domain object above. Having a no-arg constructor is a semantic requirement if you will; it does not introduce an actual dependency. Switch Hibernate for JPA / TopLink / raw jdbc / what have you and you won't have to change a single thing in your domain object code.

    A specific issue with this workaround is that instances initiated by hibernate are not checked if the meet the invariants. You're trusting data that is loaded from the database which is problematic. Even if your application is the only one using this specific database schema there is always the possibility of ad-hoc changes by administrators.

    You don't have to "trust" the data (more on that below). However, I don't think this argument has merit. If you're modifying your data in multiple applications, you should be performing validations at some common lower layer instead of relying on each individual application to validate the data. Said common layer could be the database itself (in simple cases) or a service layer providing common API to be used by your multiple applications.

    Furthermore, the notion of administrators making changes directly to the database as part of daily routine is utterly ridiculous. If you're talking about special cases (bug fixes, what have you) they should be treated as such (that is, presumably something like this happens very rarely and the burden to validate such "critical" changes lies on whoever makes the changes; not on each of your applications in the stack).

    All that said, if you do want to validate your object when they're loaded, it's reasonably straightforward to achieve. Define a Valid interface that has validate() method and have every concerned domain object implement it. You can invoke that method from:

    1. Your DAO / service after the object has been loaded.
    2. Hibernate Interceptor or Listener - both are set up in Hibernate configuration; all you need to do is to implement either one to check whether the object being loaded implements Valid and, if so, invoke the method.
    3. Or you can use Hibernate Validator, however that WILL tie your domain objects to Hibernate as you'd be annotating them.

    Finally, as far as "constructor injection" goes - I do not know of any framework that supports it directly. The reason for this is quite simple - it only makes sense for immutable entities (because once you have setters you have to deal with validation anyway) and thus means a lot of work (handling constructor parameter mappings, etc...) for almost zero net effect. In fact, if you're that concerned with having a no-arg constructor for immutable objects you can always not map them as entities and instead load them via HQL which does support constructor injection:

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

    Update (to address points from Thomas's comments):

    1. I've only mentioned the interceptor for completeness; listener is much more appropriate in this case. PostLoadEventListener is called after entity is fully initialized.
    2. Once again, having no-arg constructor is not a dependency. It's a contract, yes, but it it doesn't tie your code to Hibernate in any way. And as far as contracts go, it's a part of javabean spec (in fact, it's less restrictive because constructor doesn't have to be public) so in majority of cases you would follow it anyway.
    3. Accessing the database. "Database refactoring and migrations are common" - yes, they are. But it's just code; you write it, you test it, you run the integration tests, you deploy it to production. If your domain model object uses some StringUtil.compare() method you wrote in some utility class, you don't have it recheck the results, do you? Similarly, domain object should not have to check that your migration script didn't break anything - you should have appropriate tests for that. "Ability to do ad-hoc queries ... is one of the features" - absolutely. Queries. As in "read-only" queries used for reporting, for example (even so, in many cases it's more appropriate to go through the API). But manual data manipulation as non-emergency - absolutely not.
    4. Mutability makes constructor injection irrelevant. I'm not saying you can't have non-default constructor - you can, and you can use it in your code. But if you have setter methods you can not use your constructor for validation, so whether it is or isn't there doesn't really matter.
    5. HQL constructor injection and associations. Nested constructors won't work as far as I know (e.g. you can't write select new A(x, y, new B(c, d)); so in order to fetch associations you're going to need to retrieve them as entities in select clause which means they need to have no-arg constructors themselves :-) Or you can have a constructor on "main" entity that takes all needed nested properties as arguments and constructs/populates associations internally but that's borderline crazy :-)

    这篇关于检查hibernate映射类中的不变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆