当前位置: 首页 > 面试题库 >

检查休眠映射类中的不变量

干弘深
2023-03-14
问题内容

使用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框架。(可以在域对象之外hibernate特定于代码的代码)。

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


问题答案:

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

您将开始实现依赖于客户端(hibernate)的代码。理想情况下,一个类不知道其调用者。

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

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

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

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

综上所述,如果您确实想在 加载
对象时对其进行验证,则可以轻松实现。定义一个Valid具有validate()方法的接口,并让所有相关领域对象实现该方法。您可以从以下位置调用该方法:

  1. 加载对象后的DAO /服务。
  2. Hibernate Interceptor或Listener-两者都在Hibernate配置中设置;您需要做的就是实现其中一种,以检查要加载的对象是否实现Valid,如果是,则调用该方法。
  3. 或者,您可以使用Hibernate Validator,但是它将为您的域对象和Hibernate绑定在一起,因为您需要对其进行注释。

最后,就“构造函数注入”而言,我不知道任何 直接 支持它的框架。这样做的原因很简单-它仅对 不可变
实体有意义(因为一旦有了设置器,无论如何都要进行验证),因此意味着大量工作(处理构造函数参数映射等)几乎为零。净效应。事实上,如果你
关注具有不可变对象一个无参数的构造你总是可以不将它们映射为实体,而是通过HQL加载它们该
支持构造器注入:

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

更新 (以解决托马斯评论中的问题):

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


 类似资料:
  • 问题内容: 我以为hibernate只考虑使用注释的类变量。但是今天奇怪的是,当我添加一个变量(该变量未映射到任何列,只是我在类中需要的一个变量)时,它试图将该变量作为列名包含在select语句中并引发错误- “字段列表”中的未知列“ team1_.agencyName” 我的课 - 仅供参考…我在另一个类中使用上述类并进行多对多映射 为什么会这样? 问题答案: JPA将使用该类的 所有 属性,除

  • 问题内容: 我需要预先将没有实现接口的枚举映射到现有数据库,该数据库使用将该枚举存储在与所有者类相同的表中。 在这种情况下应如何处理映射?持久化到数据库不会改变,因为实现该接口的所有枚举都将具有不同的值,但是我不确定应如何从数据库中检索对象(我是否需要自定义映射器,它将尝试实例化一个使用指定的enum类进行枚举吗?Hibernate是否本身支持此功能?)。 问题答案: 可以创建一个自定义(例如th

  • 问题内容: Hibernate提供的注释支持使用或两种类型的映射。当我们使用映射时,它使用的“名称” 而不是Enum 的表示形式。在数据库列仅包含一个字符的情况下,这是一个问题。例如,我有以下枚举: 当我坚持枚举使用,即休眠尝试在数据库中存储的值是开放的。但是,我的数据库列仅包含一个字符,因此会引发异常。 克服这个问题的一个办法是改变枚举类型持有单个字符(如,代替,)。但是,这降低了可读性。有什么

  • 问题内容: 我有一个使用Hibernate在数据库上进行CRUD操作的Web应用程序。我收到一条错误消息,说该表未映射。查看Java文件: 错误信息: 这是我的方法: : 我应该如何修改才能正常工作? 问题答案: 异常消息怎么说?它说: 这告诉你什么?它告诉您未映射。也就是说,没有称为的映射类型。 确实,没有。您的映射类型称为。它映射到名为的表,但类型称为。在编写HQL(或JPQL)查询时,您使用

  • 问题内容: 我有一个颜色枚举 我有包含它的MyEntity。 我已经有一个UserType来映射我的枚举。 您知道如何在Hibernate hbm.xml中映射枚举集吗? 我需要一个UserType还是最简单的方法? 谢谢 编辑: 只是为了说明一下,我正在寻找 hbm.xml 配置而不是@CollectionOfElements注释 问题答案: 我使用EnumSet映射线程中的解决方案,该解决方案

  • 问题内容: 有人可以解释在xml映射文件中使用逆函数的方法吗,我正在阅读本教程,但无法理解在映射文件中的逆函数的用法? 谢谢 问题答案: 逆仅决定关系中的哪个实体负责更新数据库以反映关联。 假设一对多的双向关联。代码A和B中有两个类,A包含一组B,B维护对A的引用。在数据库级别,只有一个外键要更新,B的表包含一个到主键的列的A。 在这种情况下,假设我们将inverse = true放在集合侧。这意