【问题标题】:How to make an Entity read-only?如何使实体只读?
【发布时间】:2011-07-04 04:26:36
【问题描述】:

使用 JPA 使实体只读的正确方法是什么?我希望我的数据库表永远不会以编程方式被修改。

我想我明白我应该用LockModeType.READ 锁定我的对象。从数据库中检索后,是否可以使用注释使我的实体直接锁定?还是我必须为那个特定实体搞乱并覆盖我的通用 DAO?

【问题讨论】:

    标签: java jpa


    【解决方案1】:

    在您的实体中添加EntityListener,如下所示:

    @Entity
    @EntityListeners(PreventAnyUpdate.class)
    public class YourEntity {
        // ...
    }
    

    实现您的EntityListener,以在发生任何更新时抛出异常:

    public class PreventAnyUpdate {
    
        @PrePersist
        void onPrePersist(Object o) {
            throw new IllegalStateException("JPA is trying to persist an entity of type " + (o == null ? "null" : o.getClass()));
        }
    
        @PreUpdate
        void onPreUpdate(Object o) {
            throw new IllegalStateException("JPA is trying to update an entity of type " + (o == null ? "null" : o.getClass()));
        }
    
        @PreRemove
        void onPreRemove(Object o) {
            throw new IllegalStateException("JPA is trying to remove an entity of type " + (o == null ? "null" : o.getClass()));
        }
    }
    

    这将为您的具有 JPA 生命周期侦听器的实体创建一个防弹安全网。

    • PRO:JPA 标准 - 不是特定于休眠的
    • 专业版:非常安全
    • CON:仅显示运行时的写入尝试。如果你想要一个编译时间检查,你不应该实现setter。

    【讨论】:

    • 几年后我看到了这个,它仍然是一个很好的解决方案。 :)
    【解决方案2】:

    一种解决方案是使用基于字段的注释,将您的字段声明为 protected 并仅建议公共 getter。这样做,您的对象将无法更改。

    (此解决方案不是实体特定的,它只是构建不可变对象的一种方式)

    【讨论】:

    • 您忘记了:“并确保 getter 只返回不可变的值”。例如集合字段
    • 什么是基于字段的注释?
    • 它有效,但我讨厌它。使测试变得不必要地困难,因为您必须想出一些东西来打破封装。我提出了一个自定义注释,如果调用 setter 方法并且未检测到测试框架,则会导致异常。所述注释在您的 IDE 中也看起来像 @Deprecated。或者,您可以编写注释作为您最喜欢的测试框架的扩展。
    • 您应该使用私有而不是受保护。使其受到保护,您仍然可以通过同一包中的其他类访问该属性。经验法则实例变量始终是私有的。
    • 使用这种方法,您仍然可以添加新实体 (INSERT)
    【解决方案3】:

    Hibernate 还有一个org.hibernate.annotations.Immutable 注释,您可以将它放在类型、方法或字段上。

    【讨论】:

    • 小心@Immutable。这不是很有帮助。如果您尝试更新,它不会出错:它只会默默地失败。来自 Hibernate 的文档:“对不可变实体的更新将被忽略,但不会引发异常”。
    • 您还可以使用@Immutable 注释实体。在我们的例子中,我们需要一个静默失败,因为我们想更新某些实体而不是其他实体,而应该使用存储过程来更新。
    【解决方案4】:

    如果您的 JPA 实现是休眠的 - 您可以使用休眠实体注释

    @org.hibernate.annotations.Entity(mutable = false)
    

    显然,这会将您的模型绑定到休眠状态。

    【讨论】:

    • 实体在 jpa2 hibernate 中被弃用,而是使用 @Immutable
    【解决方案5】:

    IIRC 您可以在@Column 注释中将每个字段设置为insertable = falseupdatable = false,但我相信一定有更好的方法... :)

    我不认为 this 有帮助吗?

    【讨论】:

      【解决方案6】:

      我认为你正在寻找的是你的实体是不可变的。 Hibernate 支持这一点; JPA(至少 JPA 1.0)没有。我想你只能通过只提供 getter 来控制这一点,并确保 getter 只返回不可变的值。

      【讨论】:

        【解决方案7】:

        Eclipselink 实现还为您提供了实体级别的 @ReadOnly 注释

        【讨论】:

        • 我在 2016 年 6 月找不到的参考文献在哪里
        【解决方案8】:

        这可能会让我不赞成,因为我总是因为提出建议而遭到反对,但您可以通过多种方式使用 AspectJ 来强制执行此操作:

        要么自动化Mac的解决方案(让AspectJ注入@Column注解):

        declare @field : (@Entity *) *.* : @Column(insertable=false);
        

        或者为所有对 set 方法的访问声明编译器错误:

        declare error : execution((@Entity *) *.set*(*) );
        

        缺点:您需要将 AspectJ 编译添加到您的构建中,但如果您使用 antmaven,这很容易

        【讨论】:

        • 如果你声明所有访问set方法的编译器错误,你为什么要声明它们?
        • 为了允许框架使用它们编译器错误只针对本地代码,JPA提供者的代码通常不使用方面编织
        • 那么...通过 Jar 使用实体的客户端可以访问 setter?
        • @Nicolas 使用declare error 方法,是的。这仅在调用代码可用于 AspectJ 编译器时才有效。但是第一种方法仍然有效,因为它修改了实际的实体类文件。
        • 只是让你知道,这对我没有用,但我还是赞成。不是因为你抱怨,而是因为它实际上可能对其他人有用。 :) 感谢您提及这一点。
        【解决方案9】:

        如果您正在使用 spring-data 或以其他方式使用存储库模式,请不要在该特定实体的存储库中包含任何保存/更新/创建/插入/等方法。这可以通过为只读实体提供一个基类/接口和一个为可更新实体扩展只读的可更新实体来概括。正如其他发帖者所指出的,setter 也可能是非公开的,以避免开发人员意外设置他们无法保存的值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-06-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多