Hibernate 注释——字段访问和属性访问哪个更好?

这个问题在某种程度上与 Hibernate 注释放置问题有关。

但是我想知道哪个是 好多了? 通过属性访问还是通过字段访问? 每种方法的优点和缺点是什么?

124558 次浏览

我更喜欢字段访问,因为这样我就不必为每个属性提供 getter/setter。

通过谷歌进行的一项快速调查显示,字段访问占了大多数(例如,http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype)。

我相信字段访问是 Spring 推荐的习惯用法,但是我找不到支持它的参考资料。

有一个 相关问题试图衡量绩效,得出的结论是“没有区别”。

我相信属性访问和字段访问在延迟初始化方面有细微的不同。

考虑下面两个基本 bean 的映射:

<hibernate-mapping package="org.nkl.model" default-access="field">
<class name="FieldBean" table="FIELD_BEAN">
<id name="id">
<generator class="sequence" />
</id>
<property name="message" />
</class>
</hibernate-mapping>


<hibernate-mapping package="org.nkl.model" default-access="property">
<class name="PropBean" table="PROP_BEAN">
<id name="id">
<generator class="sequence" />
</id>
<property name="message" />
</class>
</hibernate-mapping>

以下是单元测试:

@Test
public void testFieldBean() {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
FieldBean fb = new FieldBean("field");
Long id = (Long) session.save(fb);
tx.commit();
session.close();


session = sessionFactory.openSession();
tx = session.beginTransaction();
fb = (FieldBean) session.load(FieldBean.class, id);
System.out.println(fb.getId());
tx.commit();
session.close();
}


@Test
public void testPropBean() {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
PropBean pb = new PropBean("prop");
Long id = (Long) session.save(pb);
tx.commit();
session.close();


session = sessionFactory.openSession();
tx = session.beginTransaction();
pb = (PropBean) session.load(PropBean.class, id);
System.out.println(pb.getId());
tx.commit();
session.close();
}

您将看到所需选择中的细微差别:

Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
FIELD_BEAN
(message, id)
values
(?, ?)
Hibernate:
select
fieldbean0_.id as id1_0_,
fieldbean0_.message as message1_0_
from
FIELD_BEAN fieldbean0_
where
fieldbean0_.id=?
0
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
PROP_BEAN
(message, id)
values
(?, ?)
1

也就是说,调用 fb.getId()需要一个 select,而 pb.getId()不需要。

这实际上取决于特定的情况——两种选择都是有原因的。国际海事组织认为,这种情况可归结为三种情况:

  1. Setter 有一些不应该在从数据库加载实例时执行的逻辑; 例如,一些值验证发生在 setter 中,但是来自 db 的数据应该是有效的(否则它不会到达那里(:) ; 在这种情况下,字段访问是最合适的;
  2. Setter 有一些应该总是被调用的逻辑,即使是在从 db 加载一个实例的时候; 例如,被初始化的属性用于计算一些计算字段(例如,属性——货币量,计算属性——同一个实例的几个货币属性的总和) ; 在这种情况下,属性访问是必需的。
  3. 没有上述情况-那么两个选项都适用,只是保持一致(例如,如果字段访问是在这种情况下的选择,那么在类似的情况下一直使用它)。

通常 bean 是 POJO,所以它们有访问器。

所以问题不是“哪个更好?”而仅仅是“何时使用字段访问?”.答案是“当你在这个领域不需要 setter/getter 的时候!”.

我更喜欢访问器,因为我可以在任何需要的时候向访问器添加一些业务逻辑。 这里有一个例子:

@Entity
public class Person {


@Column("nickName")
public String getNickName(){
if(this.name != null) return generateFunnyNick(this.name);
else return "John Doe";
}
}

此外,如果您添加了其他的 lib (比如一些转换 JSON 的 lib 或 BeanMapper 或 Dozer 或其他基于 getter/setter 属性的 bean 映射/克隆 lib) ,您将保证 lib 与持久化管理器同步(两者都使用 getter/setter)。

我认为对属性进行注释更好,因为直接更新字段会破坏封装,即使在 ORM 这样做时也是如此。

这里有一个很好的例子来说明它会在哪里让你抓狂: 你可能希望你的 Hibernate 验证器和持久化的注释放在同一个地方(字段或属性)。如果您想要测试您的 hibernate 验证器支持的、在字段上注释的验证,那么您不能使用实体的模拟来将单元测试与验证器隔离开来。哎哟。

我考虑了一下,然后选择了方法访问器

为什么?

因为字段和方法访问器是相同的 但是,如果以后我需要在加载字段中的一些逻辑,我保存移动所有的注释放在字段中

问候

格鲁布哈特

我有同样的问题,关于访问类型在冬眠和发现 一些答案

我倾向于使用属性访问器:

  • 如果需要,我可以添加逻辑(正如在公认的答案中提到的)。
  • 它允许我调用 foo.getId() 而不初始化代理(在使用 Hibernate 时很重要,直到 HHH-3718得到解析)。

缺点:

  • 它使得代码的可读性降低,例如,你必须浏览一个完整的类,看看是否有 @Transient在那里。

在这种情况下,必须使用属性访问器。假设您有一个 GENERIC 抽象类,它有许多实现优点可以继承到8个具体的子类中:

public abstract class Foo<T extends Bar> {


T oneThing;
T anotherThing;


// getters and setters ommited for brevity


// Lots and lots of implementation regarding oneThing and anotherThing here
}

现在,您到底应该如何注释这个类?答案是,您根本无法使用字段或属性访问来注释它,因为此时您无法指定目标实体。您必须注释具体的实现。但是由于持久化属性是在这个超类中声明的,所以您必须在子类中使用属性访问。

在具有抽象泛型超类的应用程序中,字段访问不是一个选项。

我已经解决了惰性初始化和字段访问这里 Hibernate one-to-one: getId () ,但不获取整个对象

我们到了吗

这是一个老的演示,但 Rod 认为,关于属性访问的注释会鼓励贫血的域模型,不应该成为注释的“默认”方式。

两者都有争议,但大多数争议源于某些用户需求“如果您需要为其添加逻辑”或“ xxxx 打破封装”。然而,没有人真正对这一理论发表评论,也没有人给出合理的论证。

当 Hibernate/JPA 持久化一个对象时,它实际上在做什么——好吧,它持久化该对象的 STATE。这意味着要以一种容易复制的方式来存储它。

什么是封装?封装意味着用一个接口封装数据(或状态) ,应用程序/客户机可以使用这个接口安全地访问数据——保持数据的一致性和有效性。

把它想象成 MS Word。MS Word 在内存中维护文档的模型-文档 STATE。它提供了一个界面,用户可以用来修改文档-一套按钮,工具,键盘命令等。但是,当您选择持久化(保存)该文档时,它保存的是内部状态,而不是用于生成该文档的一组按键和鼠标单击。

保存对象的内部状态并不会破坏封装——否则,您就不会真正理解封装的含义,以及它为什么存在。它真的很像对象序列化。

由于这个原因,在大多数情况下,持久化字段而不是访问者是适当的。这意味着可以从数据库中精确地按照对象的存储方式重新创建对象。它应该不需要任何验证,因为这是在原始数据库创建时和存储在数据库中之前进行的(除非,上帝保佑,您在数据库中存储了无效数据! ! !).同样,应该不需要计算值,因为这些值在存储对象之前已经计算过了。对象看起来应该和保存之前一样。事实上,通过在 getters/setter 中添加额外的内容,您实际上就是 越来越多,因为您可能会重新创建与原始内容不完全相同的内容。

当然,添加这个功能是有原因的。可能存在一些持久化访问器的有效用例,但是,它们通常是罕见的。例如,您可能希望避免持久化计算值,尽管您可能想问为什么不在值的 getter 中按需计算它,或者在 getter 中懒惰地初始化它。就我个人而言,我想不出任何好的用例,而且这里没有一个答案真正给出一个“软件工程”的答案。

要使类更干净,请将注释放在字段中,然后使用@Access (AccessType.PROPERTY)

我喜欢字段访问器。代码更简洁。所有的注释都可以放在一个 部分,而且代码更容易阅读。

我发现了属性访问器的另一个问题: 如果类中的 getXYZ 方法没有被注释为与持久属性相关联,那么 hibernate 会生成 sql 来尝试获取这些属性,从而导致一些非常混乱的错误消息。浪费了两个小时。我没有编写这段代码; 我过去一直使用字段访问器,从来没有遇到过这个问题。

这个应用程序中使用的 Hibernate 版本:

<!-- hibernate -->
<hibernate-core.version>3.3.2.GA</hibernate-core.version>
<hibernate-annotations.version>3.4.0.GA</hibernate-annotations.version>
<hibernate-commons-annotations.version>3.1.0.GA</hibernate-commons-annotations.version>
<hibernate-entitymanager.version>3.4.0.GA</hibernate-entitymanager.version>

我们创建了实体 bean 并使用了 getter 注释。我们遇到的问题是: 一些实体对于某些属性在什么时候可以更新有复杂的规则。解决方案是在每个 setter 中包含一些业务逻辑,用于确定实际值是否发生了更改,以及如果发生了更改,是否应该允许更改。当然,Hibernate 总是可以设置属性,所以我们最终使用了两组 setter。相当难看。

在阅读以前的文章时,我还发现从实体内部引用属性可能会导致集合未加载的问题。

总之,我倾向于在将来对字段进行注释。

我强烈建议在 getter 上使用字段访问和 NOT 注释(属性访问) ,如果你想在 setter 中做更多的事情,而不仅仅是设置值(例如加密或计算)。

属性访问的问题在于,在加载对象时也会调用 setter。直到我们想要引入加密之前,这个方法已经在我身上运行了好几个月了。在我们的用例中,我们希望在 setter 中加密一个字段,并在 getter 中解密它。 现在属性访问的问题是,当 Hibernate 加载对象时,它也调用 setter 来填充字段,因此再次加密加密的值。 这篇文章还提到: JavaHibernate: 根据调用方的不同属性设置函数行为

直到我想起字段访问和属性访问之间的区别,这才让我感到头疼。现在我已经将所有的注释从属性访问移动到字段访问,现在它工作得很好。

支持字段访问的另一点是,否则您将被迫公开集合的 setter,对我来说,这是一个坏主意,因为将持久化集合实例更改为不由 Hibernate 管理的对象肯定会破坏您的数据一致性。

因此,我更喜欢将集合作为受保护的字段初始化为缺省构造函数中的空实现,并只公开它们的 getter。然后,只有像 clear()remove()removeAll()等托管操作才有可能使 Hibernate 无法察觉更改。

我更喜欢字段,但是我遇到了一种情况,似乎迫使我将注释放在 getter 上。

使用 Hibernate JPA 实现,@Embedded似乎不能在字段上工作。所以这个必须放在吸收器上。一旦将它放到 getter 上,那么各种 @Column注释也必须放到 getter 上。(我认为 Hibernate 不希望在这里混淆字段和 getter。)一旦在一个类中将 @Column放在 getter 上,那么在整个过程中这样做可能是有意义的。

我更喜欢使用字段访问,原因如下:

  1. 在实现 equals/hashCode 和 直接引用字段(通过它们的 getter 实现)时,财产权限可能导致非常严重的错误。这是因为只有在访问 getter 时才初始化代理,而直接字段访问只会返回 null。

  2. 财产权限要求您将所有实用程序方法(例如 addChild/RemoveChild)注释为 @Transient

  3. 通过字段访问,我们可以通过根本不暴露 getter 来隐藏 @Version字段。Getter 还可能导致添加 setter,而且不应手动设置 version字段(这可能导致非常棘手的问题)。所有版本增量都应该通过 OPTIMISTIC_FORCE_INCREMENTPESSIMISTIC_FORCE_INCREMENT显式锁定触发。

默认情况下,JPA 提供程序访问实体字段的值并将这些字段映射到数据库列 使用实体的 JavaBean 属性访问器(getter)和 mutator (setter)方法 实体中私有字段的名称和类型与 JPA 无关 JavaBean 属性访问器的名称和返回类型。您可以使用 @javax.persistence.Access注释修改它,这使您能够显式地指定访问方法 JPA 提供者应该使用的。

@Entity
@Access(AccessType.FIELD)
public class SomeEntity implements Serializable
{
...
}

AccessType 枚举的可用选项是 PROPERTY (默认值)和 FIELD.With 属性,提供程序使用 JavaBean 属性方法获取和设置字段值 提供程序使用实例字段获取和设置字段值 并使用 JavaBean 属性,除非您有令人信服的理由不这样做。

可以将这些属性注释放在私有字段或公共访问器方法上 使用 AccessType.PROPERTY(默认)并注释私有字段而不是 JavaBean 访问器,字段名必须与 JavaBean 属性名匹配 同样,如果你使用 AccessType.FIELD和 注释 JavaBean 访问器而不是字段,字段名也必须与 JavaBean 匹配 属性名称。在这种情况下,如果您对字段进行注释,它们就不必匹配 是一致的,并为 AccessType.PROPERTY的 JavaBean 访问器和 AccessType.FIELD.

千万不要将 JPA 属性注释和 JPA 字段注释混在一起,这一点很重要 在同一个实体中。这样做会导致未指定的行为,并且非常 可能导致错误。

两者皆是:

EJB3规范要求您在元素上声明注释 类型,也就是说,如果使用 property,就使用 getter 方法 如果使用字段访问,则为字段。

Https://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping

PROPERTY: EJB 持久性实现将通过 JavaBean“ setter”方法将状态加载到类中,并使用 JavaBean“ getter”方法从类中检索状态。这是默认设置。

FIELD: 状态直接从类的字段中加载和检索。您不必编写 JavaBean“ getters”和“ setter”。

让我试着总结一下选择基于字段的访问的最重要原因。如果你想深入了解,请阅读我博客上的这篇文章: JPA 和 Hibernate 中的访问策略——字段访问和属性访问哪个更好?

到目前为止,实地访问是更好的选择:

原因1: 更好的代码可读性

如果使用基于字段的访问,则使用映射注释对实体属性进行注释。通过将所有实体属性的定义放在类的顶部,可以获得所有属性及其映射的相对紧凑的视图。

原因2: 省略应用程序不应调用的 getter 或 setter 方法

基于字段访问的另一个优点是,您的持久性提供程序(例如 Hibernate 或 EclipseLink)不使用实体属性的 getter 和 setter 方法。这意味着您不需要提供任何不应该由业务代码使用的方法。对于 生成的主键属性或版本列的 setter 方法来说,这种情况最为常见。持久性提供程序管理这些属性的值,不应以编程方式设置它们。

原因3: getter 和 setter 方法的灵活实现

因为持久性提供程序不调用 getter 和 setter 方法,所以它们不必满足任何外部需求。您可以以任何您想要的方式实现这些方法。这使您能够实现特定于业务的验证规则、触发其他业务逻辑或将实体属性转换为不同的数据类型。

例如,您可以将其用于 将可选的关联或属性包装到 JavaOptional中。

原因4: 不需要将实用程序方法标记为 @Transient

基于字段的访问策略的另一个好处是,您不需要使用 @Transient对实用程序方法进行注释。此注释告诉持久性提供程序某个方法或属性不是实体持久性状态的一部分。并且由于使用字段类型访问,持久状态将由实体的属性定义,因此 JPA 实现将忽略实体的所有方法。

原因5: 在使用代理时避免 bug

Hibernate 使用 懒洋洋地牵到一个联合会的代理,因此它可以控制这些关联的初始化。这种方法在几乎所有情况下都很有效。但是如果使用基于属性的访问,则会引入一个危险的陷阱。

如果使用基于属性的访问,Hibernate 会在调用 getter 方法时初始化代理对象的属性。如果在业务代码中使用代理对象,情况总是如此。但是相当多的 Equals 和 hashCode 实现直接访问属性。如果这是您第一次访问任何代理属性,则这些属性仍未初始化。

您应该选择通过字段访问而不是通过属性访问。 使用字段可以限制发送和接收的数据。 通过属性,您可以将更多数据作为主机发送,并且 设置 G 面值(工厂总共设置了大部分属性)。