草庐IT

java - 检查 hibernate 映射类中的不变量

coder 2024-03-03 原文

使用 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 要求,一种解决方法是添加私有(private)默认构造函数使用字段访问。 (我省略了 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 框架。 (可以在域对象外部使用特定于 hibernate 的代码)。

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

    最佳答案

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

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



    您在这里使用“依赖”一词时有些过时。 Hibernate不是“客户端”。这是(您作为开发人员/架构师/您拥有什么)选择用来实现持久性的框架。因此,您将在某些使用(因此取决于)Hibernate的地方编写一些代码。就是说,上方的域对象中对Hibernate的没有依赖。如果愿意的话,有一个无参数的构造函数是一个语义要求。它没有引入实际的依赖关系。将Hibernate切换为JPA/TopLink/raw jdbc/您将拥有什么,您将不必在域对象代码中更改任何内容。

    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.



    不必“信任” 数据(以下更多内容)。但是,我认为这种说法没有根据。如果要在多个应用程序中修改数据,则应该在某个通用的较低层执行验证,而不要依赖每个单独的应用程序来验证数据。所述公共(public)层可以是数据库本身(在简单情况下),也可以是提供要由多个应用程序使用的公共(public)API的服务层。

    此外,作为日常工作的一部分,管理员直接将更改为数据库的的想法是完全荒谬的。如果您在谈论特殊情况(错误修复,您拥有什么),则应将其视作此类情况(也就是说,此类情况很少发生,验证此类“关键”变更的责任在于进行变更的人;不在堆栈中的每个应用程序上)。

    综上所述,如果您确实想在加载时验证对象,则可以轻松实现。定义一个具有Valid方法的validate()接口(interface),并让每个相关的域对象实现它。您可以从以下位置调用该方法:
  • 加载对象后的DAO/服务。
  • Hibernate Interceptor or Listener-两者都在Hibernate配置中设置;您需要做的就是实现其中一种,以检查要加载的对象是否实现了Valid,如果是,则调用该方法。
  • 或者您可以使用Hibernate Validator,但是这将把域对象与Hibernate绑定(bind)在一起,因为您需要对其进行注释。

  • 最后,就“构造函数注入(inject)”而言,我不知道任何支持它的框架直接。原因很简单-仅对不可变实体有意义(因为一旦有了setter,就必须处理验证),因此意味着需要进行大量工作(处理构造函数参数映射等)净效应几乎为零。实际上,如果您是,它关心为不可变对象(immutable对象)提供无参数构造函数,则始终不能将它们映射为实体,而是通过HQL加载它们,而支持constructor injection:
    select new A(x, y) from ...
    

    更新(以解决Thomas的评论中的问题):
  • 我只是为了完整性而提到拦截器。在这种情况下,侦听器更为合适。在实体完全初始化之后调用PostLoadEventListener
  • 再一次,拥有no-arg构造函数也不是依赖项。是的,这是一个契约(Contract),但是它不会以任何方式将您的代码与Hibernate绑定(bind)在一起。就契约(Contract)而言,它是javabean规范的一部分(实际上,它的限制较少,因为构造函数不必是公共(public)的),因此在大多数情况下,您还是应该遵循它。
  • 访问数据库。 “数据库重构和迁移很常见”-是的。但这仅仅是代码;您编写,测试,运行集成测试,然后将其部署到生产环境。如果您的域模型对象使用您在某些实用工具类中编写的StringUtil.compare()方法,那么您无需重新检查结果,对吗?同样,域对象也不必检查您的迁移脚本没有破坏任何东西-您应该对此进行适当的测试。 “绝对的查询能力……是其中一项功能”。 查询。例如,与用于报告的“只读”查询一样(即使如此,在许多情况下,更适合使用API​​)。但是手动数据操作是非紧急事件-绝对不是。
  • 可变性使构造函数注入(inject)不相关。我并不是说您不能拥有非默认构造函数-可以,并且可以在代码中使用它。但是,如果有setter方法,则不能使用构造函数进行验证,因此,是否存在并不重要。
  • HQL构造函数注入(inject)和关联。 据我所知,嵌套的构造函数无法正常工作(例如,您无法编写select new A(x, y, new B(c, d));因此,要获取关联,您将需要在select子句中将它们检索为实体,这意味着它们不需要- arg自身的构造函数:-)或者,您也可以在“main”实体上构造一个构造函数,该构造函数将所有需要的嵌套属性作为参数并在内部构造/填充关联,但是这很疯狂:-)
  • 关于java - 检查 hibernate 映射类中的不变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1624392/

    有关java - 检查 hibernate 映射类中的不变量的更多相关文章

    1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    2. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

      作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

    4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

      我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

    6. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

      为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

    7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

      我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

    8. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

      我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

    9. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

      刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

    10. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

      我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

    随机推荐