此问题与Hibernate Annotation Placement Question有些相关。
但我想知道更好哪个?通过属性访问或通过字段访问? 每种方法的优点和缺点是什么?
答案 0 :(得分:232)
两者都存在争论,但大多数都源于某些用户要求“如果需要添加逻辑”,或“xxxx打破封装”。然而,没有人真正对这一理论发表评论,并给出了合理的理由。
Hibernate / JPA在持久化对象时实际上做了什么 - 好吧,它是持久化对象的状态。这意味着以一种可以轻松复制的方式存储它。
什么是封装?封装意味着使用应用程序/客户端可以用来安全访问数据的接口封装数据(或状态) - 保持数据的一致性和有效性。
将此视为MS Word。 MS Word在内存中维护文档的模型 - 文档STATE。它提供了一个用户可以用来修改文档的界面 - 一组按钮,工具,键盘命令等。但是,当您选择保留(保存)该文档时,它会保存内部状态,而不是一组按键和鼠标点击用于生成它。
保存对象的内部状态不会破坏封装 - 否则你并不真正理解封装意味着什么,以及它存在的原因。它就像对象序列化一样。
出于这个原因,在大多数情况下,坚持FIELDS而不是ACCESSORS是合适的。这意味着可以从数据库中精确地重新创建对象,就像存储它一样。它不应该需要任何验证,因为这是在原始文件创建时,在它存储在数据库之前完成的(除非,上帝禁止,你在DB中存储无效数据!!!!)。同样,应该不需要计算值,因为它们是在存储对象之前已经计算的。该对象应该看起来像它保存之前的方式。事实上,通过在getter / setter中添加额外的东西,你实际上增加你将重新创建一些不是原始副本的东西的风险。
当然,添加此功能是有原因的。可能存在一些用于持久访问者的有效用例,但是,它们通常很少见。一个例子可能是你想要避免持久计算一个值,尽管你可能想问一个问题,为什么你不在值的getter中按需计算它,或懒惰地在getter中初始化它。我个人认为没有任何好的用例,这里的答案都没有给出“软件工程”的答案。
答案 1 :(得分:76)
我更喜欢字段访问,因为这样我就不会被迫为每个属性提供getter / setter。
通过Google进行的快速调查表明,现场访问占多数(例如http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype)。
我认为现场访问是Spring推荐的习惯用法,但是我无法找到支持它的参考。
有一个related SO question试图衡量绩效,并得出结论“没有区别”。
答案 2 :(得分:37)
这是您必须使用属性访问器的情况。想象一下,你有一个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
}
现在究竟该如何注释这个类?答案是您无法使用字段或属性访问来注释它,因为此时您无法指定目标实体。您必须注释具体实现。但由于持久化属性是在这个超类中声明的,因此必须在子类中使用属性访问。
在具有抽象通用超类的应用程序中,字段访问不是一个选项。
答案 3 :(得分:33)
我更倾向于使用属性访问器:
foo.getId()
(在使用Hibernate时非常重要,直到HHH-3718得到解决)。缺点:
@Transient
。答案 4 :(得分:31)
我更喜欢访问者,因为我可以在需要时为我的访问者添加一些业务逻辑。 这是一个例子:
@Entity
public class Person {
@Column("nickName")
public String getNickName(){
if(this.name != null) return generateFunnyNick(this.name);
else return "John Doe";
}
}
此外,如果你将另一个libs放入混合中(比如一些JSON转换的lib或BeanMapper或Dozer或其他基于getter / setter属性的bean映射/克隆lib),你可以保证lib是同步的使用持久性管理器(都使用getter / setter)。
答案 5 :(得分:13)
这实际上取决于具体案例 - 两种选择都是有原因的。 IMO归结为三个案例:
答案 6 :(得分:12)
如果你想在setter中做更多的事情而不仅仅是设置值(例如加密或计算),我强烈建议对getter(属性访问)进行字段访问和NOT注释。
属性访问的问题是在加载对象时也会调用setter。在我们想要引入加密之前,这对我来说很有用。在我们的用例中,我们想要加密setter中的字段并在getter中解密它。 现在属性访问的问题是,当Hibernate加载对象时,它还调用setter来填充字段,从而再次加密加密值。 这篇文章还提到了这个: Java Hibernate: Different property set function behavior depending on who is calling it
这让我感到头疼,直到我记得现场访问和财产访问之间的区别。现在我已将所有注释从属性访问移至现场访问,现在工作正常。
答案 7 :(得分:8)
我更喜欢使用字段访问,原因如下:
属性访问在实现equals / hashCode和referencing fields directly时会导致非常讨厌的错误(而不是通过它们的getter)。这是因为只有在访问getter时才会初始化代理,而直接访问只会返回null。
属性访问要求您将所有utility methods(例如addChild / removeChild)注释为@Transient
。
通过字段访问,我们可以通过不暴露getter来隐藏@Version字段。 getter也可以导致添加setter,并且永远不应该手动设置version
字段(这可能会导致非常讨厌的问题)。应通过OPTIMISTIC_FORCE_INCREMENT或PESSIMISTIC_FORCE_INCREMENT显式锁定来触发所有版本增量。
答案 8 :(得分:7)
我认为对属性进行注释会更好,因为更新字段会直接破坏封装,即使你的ORM会这样做。
这是一个很好的例子,它会烧掉你:你可能想要你的hibernate验证器和注释的注释。持久性在同一个地方(字段或属性)。如果要测试在字段上注释的hibernate验证器驱动的验证,则不能使用实体的模拟将单元测试与验证器隔离。哎哟。
答案 9 :(得分: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()
则不需要。
答案 10 :(得分:2)
这是一个陈旧的陈述,但Rod建议对财产访问的注释鼓励贫血的域模型,不应该是注释的“默认”方式。
答案 11 :(得分:2)
我更喜欢现场存取器。代码更清晰。所有注释都可以放在一个中 类的一部分和代码更容易阅读。
我发现了属性访问器的另一个问题:如果您的类上有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>
答案 12 :(得分:2)
支持字段访问的另一点是否则你不得不公开集合的setter以及对我来说这是一个坏主意,因为将持久集合实例更改为不受Hibernate管理的对象肯定会破坏你的数据一致性。
所以我更喜欢将集合作为受保护字段初始化为默认构造函数中的空实现,并且只公开它们的getter。然后,只有clear()
,remove()
,removeAll()
等托管操作才有可能永远不会让Hibernate意识到变化。
答案 13 :(得分:2)
我更喜欢字段,但我遇到了一种似乎迫使我将注释放在getter上的情况。
使用Hibernate JPA实现,@Embedded
似乎不适用于字段。所以必须继续吸气。一旦你把它放在吸气剂上,那么各种@Column
注释也必须放在吸气剂上。 (我认为Hibernate不希望在这里混合使用字段和getter。)一旦你将@Column
放在一个类的getter中,那么在整个过程中这样做是有意义的。
答案 14 :(得分:2)
默认情况下,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.FIELD
和
注释JavaBean访问器而不是字段,字段名称也必须与JavaBean匹配
财产名称。在这种情况下,如果您注释字段,则不必匹配。这是最好的
保持一致并注释AccessType.PROPERTY
的JavaBean访问器及其字段
AccessType.FIELD
。
重要的是,不要混用JPA属性注释和JPA字段注释 在同一个实体中。这样做会导致未指定的行为,并且非常 可能会导致错误。
答案 15 :(得分:2)
您应该选择通过字段进行访问,而不是通过属性进行访问。 使用字段可以限制发送和接收的数据。 通过via属性,您可以发送更多数据作为主机,并且 设置G面额(该工厂总共设置了大多数属性)。
答案 16 :(得分:1)
我在Hibernate one-to-one: getId() without fetching entire object
解决了延迟初始化和字段访问问题答案 17 :(得分:1)
我对hibernate中的accesstype有同样的问题,并找到了some answers here。
答案 18 :(得分:1)
我们创建了实体bean并使用了getter注释。我们遇到的问题是:一些实体对某些属性有关于何时可以更新的复杂规则。解决方案是在每个setter中有一些业务逻辑,用于确定实际值是否发生变化,如果是,则确定是否允许更改。当然,Hibernate总是可以设置属性,所以我们最终得到了两组setter。非常难看。
阅读以前的帖子,我也看到从实体内部引用属性可能会导致集合无法加载的问题。
最重要的是,我会倾向于在将来注释这些字段。
答案 19 :(得分:1)
让我尝试总结选择基于字段的访问的最重要原因。如果您想更深入,请阅读我的博客上的这篇文章:Access Strategies in JPA and Hibernate – Which is better, field or property access?
到目前为止,基于字段的访问是更好的选择。这有5个原因:
原因1:更好的代码可读性
如果使用基于字段的访问,则使用映射注释对实体属性进行注释。通过将所有实体属性的定义放在类的顶部,您可以相对紧凑地查看所有属性及其映射。
原因2:省略应用程序不应该调用的getter或setter方法
基于字段的访问的另一个优势是持久性提供程序(例如Hibernate或EclipseLink)不使用实体属性的getter和setter方法。这意味着您不需要提供您的业务代码不应该使用的任何方法。 generated primary key attributes或版本列的设置方法通常是这种情况。持久性提供程序管理这些属性的值,因此您不应以编程方式设置它们。
原因3:灵活地实现getter和setter方法
由于您的持久性提供程序未调用getter和setter方法,因此不会强制它们满足任何外部要求。您可以以任何所需的方式实现这些方法。这样一来,您就可以实施特定于业务的验证规则,触发其他业务逻辑或将实体属性转换为其他数据类型。
例如,您可以将其用于wrap an optional association or attribute into a Java Optional
.
原因4:无需将实用程序方法标记为@Transient
基于字段的访问策略的另一个好处是,您不需要使用@Transient
注释实用程序方法。该注释告诉持久性提供者方法或属性不属于实体持久状态。而且由于使用字段类型访问,持久状态由实体的属性定义,因此JPA实现将忽略实体的所有方法。
原因5:使用代理时避免错误
Hibernate使用lazily fetched to-one associations的代理,以便它可以控制这些关联的初始化。这种方法在几乎所有情况下都可以正常工作。但是,如果您使用基于属性的访问,则会带来危险的陷阱。
如果使用基于属性的访问,则Hibernate在调用getter方法时会初始化代理对象的属性。如果您在业务代码中使用代理对象,情况总是如此。但是很多equals and hashCode implementations直接访问属性。如果这是您第一次访问任何代理属性,则这些属性仍未初始化。
答案 20 :(得分:0)
要使您的类更清晰,请将注释放在字段中,然后使用@Access(AccessType.PROPERTY)
答案 21 :(得分:0)
我在想这个,我选择方法访问
为什么呢?
因为field和methos访问者是相同的 但如果以后我在加载字段中需要一些逻辑,我保存移动放置在字段中的所有注释
问候
Grubhart
答案 22 :(得分:0)
通常bean是POJO,所以无论如何它们都有访问器。
所以问题不是“哪一个更好?”,而只是“何时使用现场访问?”。答案是“当你不需要场上的二传手/吸气器时!”。
答案 23 :(得分:0)
两者:
EJB3规范要求您在元素上声明注释 将被访问的类型,即如果您使用property的getter方法 访问,如果您使用字段访问,则为字段。
https://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping
答案 24 :(得分:0)
AccessType.PROPERTY:EJB持久性实现将通过JavaBean“ setter”方法将状态加载到您的类中,并使用JavaBean“ getter”方法从类中检索状态。这是默认设置。
AccessType.FIELD:直接从类的字段中加载和检索状态。您不必编写JavaBean的“ getters”和“ setters”。