休眠注释-字段访问或属性访问哪个更好?


Answers:


33

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

@Entity
public class Person {

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

此外,如果您将其他库(例如一些基于JSON转换的库,BeanMapper或Dozer或其他基于getter / setter属性的Bean映射/克隆库)添加到混合库中,则可以确保该库与持久性同步经理(都使用getter / setter)。


17
请注意,这是关于ORM如何访问您的字段/属性而不是您的应用程序代码的。通过字段访问,您的getNickName()方法将完全按照您的期望工作。如果您在getter / setter之外使用持久性“ properties”,则情况并非如此。那是您可能遇到属性访问和延迟加载问题的地方。因此,不,我总体上不同意这种说法。但是,上次我检查Hibernate时@Id字段的字段访问存在问题。
罗伯·比格雷夫

11
这个答案与问题无关。duffymo的最佳回答s
Janning '22

9
访问器内部不应有任何业务逻辑。这不是显而易见的行为。
iuriisusuk

为什么这个回应标记是正确的?您不能以相同的方式映射字段并为它提供一个setter / getter方法。
幸运

246

两者都有参数,但是它们大多数来自某些用户要求,“如果需要为其添加逻辑”或“ xxxx中断封装”。但是,没有人真正对这一理论发表评论,也没有给出合理的论据。

Hibernate / JPA在持久化一个对象时实际上在做什么-嗯,它持久化了对象的STATE。这意味着以易于复制的方式存储它。

什么是封装?封装是指使用应用程序/客户端可以用来安全访问数据的接口封装数据(或状态),以保持数据的一致性和有效性。

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

保存对象的内部状态不会破坏封装-否则您将无法真正理解封装的含义以及存在的原因。确实就像对象序列化。

因此,在大多数情况下,最好保留字段而不是访问者。这意味着可以完全按照对象的存储方式从数据库中准确地重新创建对象。它不需要任何验证,因为它是在原始文件创建时以及原始文件存储在数据库中之前进行的(除非上帝禁止,您正在数据库中存储无效数据!!!)。同样,也不需要计算值,因为在存储对象之前已经计算了它们。对象应该看起来像保存之前一样。实际上,通过将其他内容添加到getter / setter中,实际上增加了您将重新创建与原始版本不完全相同的内容的风险。

当然,添加此功能是有原因的。可能存在一些用于持久保存访问器的有效用例,但是,它们通常很少见。例如,您可能想问一个问题,为什么不按需在值的getter中计算它,或者在getter中惰性地对其进行初始化,所以您可能想避免保留计算所得的值。就我个人而言,我想不出任何好的用例,这里的答案都没有真正给出“软件工程”的答案。


9
软件工程的答案是:使用访问器违反了DRY。
sourcedelica 2013年

1
@Martin我有一个关于您答案的最后一段的后续问题。您写了“一个例子,就是您想要避免持久保存计算所得的值”。如何通过具有基于属性的访问权限来避免持久保存计算所得的值?我知道您在争辩不这样做,但我在这里不明白这一点。你能解释一下吗?
极客

4
@Geek现在我读回来了,我不太确定自己。我写这个答案已经两年了。我猜一个更好的例子可能是您在使用旧数据库的地方,并且数据以与对象模型不同的方式呈现-访问器可以在两者之间提供映射。
马丁

23
映射访问器的一个很好的用例是当您需要将映射信息添加到与任何持久性实现无关的第三方实体类的子类中。由于字段在这些类中是私有的,因此您必须覆盖访问器并向其添加映射注释。另一个选择是使用XML映射,但是有些事情很难在那做。因此,如果要使用批注并映射第三方类,则可以将它们子类化并在访问器上添加批注。
Elnur Abdurrakhimov 2014年

5
@ElnurAbdurrakhimov,我们去了,一个很好的例子。谢谢。
马丁

79

我更喜欢现场访问,因为这样一来,我就不必为每个属性提供getter / setter方法。

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

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

有一个相关的SO问题试图衡量性能,并得出结论“没有区别”。


如果您未在实体中提供setter getter,那么该字段的用途是什么...您不能在应用程序中使用它,因为字段是私有的
anshulkatta 2014年

1
难道不是在为您的领域提供吸气剂和设置器吗?我想我在这里的评论并不总是正确的,因为我假设是一个公共领域,而它显然可能是一个从未访问过的私有领域。
Mathijs Segers 2014年

3
@anshulkatta我觉得我真的应该解决您的问题,因为这就是封装的全部内容。理想情况下,您所有的字段都应该是私有的,并且如果可能的话,它们应该没有getter或setter-这是您希望的最佳封装级别。考虑一个密码检查器。2个私有字段passwordHash和failedAttempts。两者都可以是私有的,没有getter或setter。它们由bool checkPassword(字符串密码)使用,该方法散列,检查passwordHash,然后更新failAttempts并返回结果。无需其他代码即可访问这两个字段。
马丁

2
@anshulkatta应该注意的是,在OOP中,getter和setter是一种反模式,它们来自过程编程,使用它们的类破坏了封装原理,并生成了许多样板代码,即,代码一遍又一遍地重复。对象应该是不可变的,如果需要修改其属性,则应该通过方法来完成,该方法所要做的不仅仅是返回属性的值。
Uriel Arvizu

不是这样 在这种情况下,“反模式”只是一个见解,而不是教条。话虽如此,现场访问仍然是首选。一个更好的主意是完全避开ORM解决方案。了解如何编写适当的SQL。
duffymo '16

38

在这种情况下,您必须使用属性访问器。想象一下,您有一个具有很多实现优点的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
 }

现在,您究竟应该如何注释该课程?答案是您根本无法使用字段或属性访问来对其进行注释,因为此时您无法指定目标实体。您必须注释具体的实现。但是,由于持久属性是在此超类中声明的,因此您必须在子类中使用属​​性访问。

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


2
感动。我没想到。我敢打赌,Hibernate为此踢出了一些疯狂的SQL。
Joseph Lust

8
在没有注释属性的情况下,这个问题听起来很难解决,但是我从来没有遇到过这样的情况:我需要一个抽象的泛型类,该类具有很多我也想保留的实现。通常,我创建一个类层次结构以使我的对象成为多态的(这使它成为通用的中断),而不仅仅是为了代码重用。而且“很多实现”经常违反SRP,在这种情况下,我可能会将其移到单独的类中。有没有使该用例更加明显的具体示例?
Merlyn Morgan-Graham 2012年

我仅有的一个具体示例是我的应用程序,我无法在500个字符的注释中描述它;-)
HDave 2013年

3
您可以使用abstract T getOneThing()abstract void setOneThing(T thing),并使用字段访问。
Arturo Volpe 2014年

33

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

  • 如果需要,我可以添加逻辑(如已接受的答案中所述)。
  • 它允许我在foo.getId() 不初始化代理的情况下进行调用(使用Hibernate时很重要,直到HHH-3718得到解决)。

退税:

  • 它使代码的可读性降低,例如,您必须浏览整个类,以查看@Transient周围是否有代码。

但是,如果您使用的JPA提供程序没有“代理”,那么您就不会遇到“您的JPA提供程序强加于您”的问题。
尼尔·斯托克顿

13

这实际上取决于具体情况-两种选择都可以使用是有原因的。IMO归结为三种情况:

  1. setter具有一些逻辑,在从数据库加载实例时不应执行这些逻辑;例如,在设置器中会发生一些值验证,但是来自db的数据应该是有效的(否则它将无法到达那里(:);在这种情况下,最适合进行字段访问;
  2. setter具有一些应始终调用的逻辑,即使在从db加载实例的过程中也是如此;例如,正在初始化的属性用于计算某些计算字段(例如,属性-金额,计算的属性-同一实例的多个货币属性的总和);在这种情况下,需要进行属性访问。
  3. 以上情况均不适用-两种选择均适用,只要保持一致即可(例如,如果在这种情况下选择使用现场访问,则在类似情况下始终使用它)。

我是Hibernate的新手,正在为同一问题苦苦挣扎。我认为这篇文章提供了最明确的答案。谢谢。
山姆·莱文

12

如果您要在设置器中做更多的事情,而不是仅仅设置值(例如,加密或计算),我强烈建议在getter上进行字段访问和不添加批注(属性访问)。

属性访问的问题是,在加载对象时也会调用设置器。在我想引入加密之前,这对我来说已经工作了好几个月。在我们的用例中,我们想对设置器中的字段进行加密,然后在获取器中对其进行解密。现在,属性访问的问题在于,当Hibernate加载对象时,它还调用了setter来填充字段,从而再次加密了加密后的值。这篇文章也提到了这一点: Java Hibernate:不同的属性集函数行为取决于谁调用它

这让我头疼,直到我想起了现场访问和属性访问之间的区别。现在,我已将所有注释从属性访问移至字段访问,并且现在可以正常工作。


是的-我发现,如果您使用属性访问,则除了设置字段值外,您实际上无法在设置器中执行任何操作。
HDave 2013年

2
+1远离吸气剂/吸气剂。我使用projectlombok.org 并将其对开发人员隐藏。
bhantol 2014年

11

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

  1. 当实现equals / hashCode并直接引用字段时(通过其getter进行相反),属性访问会导致非常讨厌的错误。这是因为仅在访问getter时才初始化代理,而直接字段访问将仅返回null。

  2. 属性访问要求您注释的所有实用程序方法(如的addChild / removeChild之)作为@Transient

  3. 使用字段访问,我们可以通过根本不公开获取方法来隐藏@Version字段。吸气剂也可能导致添加setter,并且version永远不应手动设置字段(这可能会导致非常麻烦的问题)。所有版本增量都应通过OPTIMISTIC_FORCE_INCREMENTPESSIMISTIC_FORCE_INCREMENT显式锁定来触发。


1.现场访问策略如何防止这种情况发生?无论访问方式如何,这似乎都是代理的普遍陷阱。2.您确定,应该仅仅是实用程序获取者吗?(但无论如何都是一个很好的论点)。3.version在使用DTO而不是分离的实体的情况下,公开字段访问器通常很有用。
Dragan Bozanovic 2015年

1.由于代理初始化的时间。2. 100%确定。3.这是一个正确的观点。
Vlad Mihalcea 2015年

请原谅我的无知和可能缺乏文字解释,(我不是英语母语人士)。需要澄清的是,字段访问是在不需要正确的getter / setter方法的情况下,因此对于一个整体使用的POJO,字段是公共的吗?但是您在第一个主题中说了些什么,而链接的博客文章却相反。我的理解是,在使用代理和现场访问时不能使用均等。
MaikoID

否。字段访问意味着Hibernate通过反射使用字段来读取实体属性。有关更多详细信息,请参阅《Hibernate用户指南》中的“访问类型”部分。
Vlad Mihalcea

7

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

这是一个在哪里烧死您的好例子:您可能希望在同一位置(字段或属性)使用休眠验证程序和持久性的注释。如果要测试在字段上注释的由休眠验证器提供的验证,则不能使用实体的模拟将单元测试仅与验证器隔离。哎哟。


2
这就是为什么您在访问器上放置验证器注释,而在字段上放置持久性注释的原因
fishbone 2014年

6

我认为就懒惰初始化而言,属性访问与现场访问之间存在细微的差异。

考虑2个基本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()需要选择,而不需要pb.getId()


这很好笑!:)但是,我敢肯定,这是特定于实现的行为。I
Vladimir Dyuzhev 09年

是的,我想这是由于仅对持久性类进行了检测。但是,这很麻烦,因为id字段通常是一个没有业务价值并且不需要任何访问器的字段。
莫里斯·佩里

6

让我尝试总结选择基于字段的访问的最重要原因。如果您想更深入,请阅读我的博客上的这篇文章:JPA和Hibernate中的访问策略–现场访问还是财产访问哪个更好?

到目前为止,基于字段的访问是更好的选择。这有5个原因:

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

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

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

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

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

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

例如,您可以使用它将可选的关联或属性包装到Java中Optional

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

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

原因5:使用代理时避免错误

Hibernate将代理用于延迟获取的一对一关联,以便它可以控制这些关联的初始化。这种方法在几乎所有情况下都可以正常工作。但是,如果您使用基于属性的访问,则会带来危险的陷阱。

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


3

默认情况下,JPA提供程序访问实体字段的值,并使用实体的JavaBean属性访问器(getter)和mutator(setter)方法将这些字段映射到数据库列。因此,实体中私有字段的名称和类型与JPA无关。相反,JPA仅查看JavaBean属性访问器的名称和返回类型。您可以使用@javax.persistence.Access批注来更改此设置,从而使您可以显式指定JPA提供程序应采用的访问方法。

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

AccessType枚举的可用选项是PROPERTY(默认)和FIELD。使用PROPERTY,提供程序可以使用JavaBean属性方法获取和设置字段值。FIELD使提供者使用实例字段获取和设置字段值。最佳实践是,除非有充分的理由,否则应坚持默认设置并使用JavaBean属性。

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

重要的是,切勿在同一实体中混合使用JPA属性注释和JPA字段注释。这样做会导致未指定的行为,并且很有可能导致错误。



2

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

因此,我更喜欢将集合作为受保护的字段初始化为默认构造函数中的空实现,并仅公开其getter。像这样,只有管理操作clear()remove()removeAll()等有可能不会让休眠不知情的变化。


您不会被迫公开任何东西,因为可以保护二传手。同样,这些设置器也不是正在实现的接口的一部分,因此即使它们是公共的,也不容易访问。
HDave 2013年

2

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

使用Hibernate JPA实施,@Embedded似乎不适用于字段。因此,这必须继续进行。一旦将其放在吸气剂上,那么各种@Column注释也必须在吸气剂上进行。(我认为Hibernate不想在这里混合使用字段和getter。)一旦您将@Columngetter放在一个类中,就可以在整个过程中这样做。


2

我赞成现场访问者。代码更简洁。所有注释都可以放在类的一个部分中,并且代码更易于阅读。

我发现了属性访问器的另一个问题:如果您的类上有未注释为与持久性属性相关联的getXYZ方法,则hibernate会生成sql尝试获取这些属性,从而导致一些非常令人困惑的错误消息。浪费了两个小时。我没有写这段代码;我过去一直使用字段访问器,并且从未遇到过此问题。

此应用程序中使用的休眠版本:

<!-- 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>

2

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




1

我们创建了实体bean并使用了getter注释。我们遇到的问题是:一些实体对于某些属性具有复杂的规则,涉及何时可以更新它们。解决方案是在每个设置器中都有一些业务逻辑,以确定是否更改了实际值,如果是,则确定是否应允许更改。当然,Hibernate总是可以设置属性,因此我们最终得到了两组设置器。真是丑。

阅读以前的文章,我还看到从实体内部引用属性可能会导致集合无法加载的问题。

最重要的是,我倾向于将来对字段进行注释。


0

通常,bean是POJO,因此它们仍然具有访问器。

因此,问题不是“哪个更好?”,而仅仅是“何时使用现场访问?”。答案是“当您不需要该领域的设置者/获取者时!”。


4
问题是,你不能混用在一个POJO现场访问和属性访问-你必须选择一个
马丁·奥康纳

真?我一定忘了它。无论如何,我总是使用POJO和d访问器。
弗拉基米尔·朱热夫2009年

请注意,使用JPA 2.0(询问此问题时并没有出现),您现在可以使用@AccessType批注混合访问类型。
mtpettyp 2010年

0

我正在考虑这个问题,我选择方法accesor

为什么?

因为field和methos accesor是相同的,但是如果以后我需要在load字段中添加一些逻辑,我保存将所有注释移到字段中

问候

格鲁伯特


0

为了使您的类更整洁,请将注释放在字段中,然后使用@Access(AccessType.PROPERTY)



0

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

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

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.