如何使实体为只读?


76

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

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

Answers:


47

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

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


19
您忘记了:“并确保获取器仅返回不可变的值”。例如,收集字段
Daniel Alexiuc 2011年

基于字段的注释是什么意思?
MyTitle

1
它有效,但我讨厌它。使得测试变得不必要地困难,因为您必须提出一些破坏封装的方法。我提出了一个自定义批注,如果调用了setter方法并且未检测到测试框架,则该批注会导致异常。所说的注释在您的IDE中也类似于@Deprecated。或者,您可以编写注释作为您最喜欢的测试框架的扩展。
user447607 2013年

5
您应该使用私有而不是受保护的。使其受到保护,您仍然可以通过同一包中的其他类访问该属性。经验法则实例变量始终是私有的。
乔丹·席尔瓦

3
使用这种方法,您仍然可以添加新实体(INSERT)
andy

52

在您的实体中添加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标准-不是休眠专用
  • PRO:非常安全
  • CON:仅显示运行时的写入尝试。如果要进行编译时检查,则不应实现setter。

1
几年后,我看到了这一点,它仍然是一个很好的解决方案。:)
drognisep

29

Hibernate还具有一个org.hibernate.annotations.Immutable注释,可以在类型,方法或字段上添加注释。


10
请注意@Immutable。这不是很有帮助。如果您尝试更新,它不会出错:它将只是默默地失败。从Hibernate的文档中:“对不可变实体的更新将被忽略,但不会引发任何异常”。
大卫·拉文德

2
您也可以使用@Immutable注释实体。在我们的案例中,当我们想要更新某些实体而不是其他实体时,我们需要一个无声的故障,而应该使用存储过程来更新它们。
Kt Mack

24

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

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

显然,这将使您的模型休眠。


54
实体在jpa2休眠模式中已弃用,请改用@Immutable
Juan Rojas

15

IIRC,您可以将每个字段都设置为insertable = falseupdatable = false中的@Column注释,但是我确定必须有更好的方法... :)

我不认为这有帮助吗?


13

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


12

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


我现在在2016
6

8

这可能会使我不满意,因为我总是因为建议而被否决,但是您可以使用AspectJ多种方式来实现这一点:

使Mac解决方案自动化(使AspectJ注入@Column注解):

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

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

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

缺点:您需要在构建中添加AspectJ编译,但是如果您使用antmaven则很容易


如果您对所有对set方法的访问都声明了编译器错误,那么为什么要声明它们呢?
Nicolas

2
为了允许框架使用它们,编译器错误仅适用于本地代码,JPA提供程序的代码通常不使用方面进行编织
Sean Patrick Floyd

那么...通过Jar使用实体的客户端可以访问setter吗?
尼古拉斯

@Nicolas使用这种declare error方法,是的。仅当调用代码可用于AspectJ编译器时,此方法才有效。但是第一种方法仍然有效,因为它修改了实际的Entity类文件。
肖恩·帕特里克·弗洛伊德

请注意,这对我没有用,但是我还是反驳了。不是因为您抱怨,而是因为它实际上可能对其他人有用。:)感谢您提到这一点。
glmxndr 2011年

5

如果您正在使用spring-data或以其他方式使用存储库模式,则不要在该存储库中为该特定实体包括任何save / update / create / insert / etc方法。可以通过具有只读实体的基类/接口和扩展可更新实体的只读类的可更新对象来概括这一点。正如其他发布者所指出的那样,二传手也可以设为非公开,以避免开发人员意外地设置他们随后无法保存的值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.