这个问题与Hibernate Annotation Placement Question有点相关。
但我想知道哪个更好?通过属性访问还是通过字段访问?各自的优点和缺点是什么?
这个问题与Hibernate Annotation Placement Question有点相关。
但我想知道哪个更好?通过属性访问还是通过字段访问?各自的优点和缺点是什么?
两者都有争论,但大多数都源于某些用户需求“如果需要添加逻辑怎么办”或“xxxx 破坏封装”。然而,没有人真正评论过这个理论,并给出了一个合理的论据。
当 Hibernate/JPA 持久化一个对象时,它实际上在做什么 - 好吧,它是在持久化对象的状态。这意味着以易于复制的方式存储它。
什么是封装?封装意味着使用应用程序/客户端可以用来安全访问数据的接口封装数据(或状态) - 使其保持一致和有效。
把它想象成 MS Word。MS Word 在内存中维护文档的模型 - 文档状态。它提供了一个用户可以用来修改文档的界面 - 一组按钮、工具、键盘命令等。但是,当您选择持久保存(保存)该文档时,它会保存内部状态,而不是一组按键和用于生成它的鼠标点击。
保存对象的内部状态不会破坏封装 - 否则您并不真正了解封装的含义以及它存在的原因。它就像对象序列化一样。
出于这个原因,在大多数情况下,保留 FIELDS 而不是 ACCESSORS 是合适的。这意味着对象可以完全按照其存储方式从数据库中准确地重新创建。它不需要任何验证,因为这是在创建原始文件时完成的,并且在它存储到数据库之前(除非,上帝保佑,你在数据库中存储无效数据!!!!!!)。同样,应该不需要计算值,因为它们在存储对象之前就已经计算过了。对象应该看起来就像它在保存之前的样子。事实上,通过在 getter/setter 中添加额外的东西,你实际上增加了重新创建不是原始副本的东西的风险。
当然,添加此功能是有原因的。可能有一些用于持久化访问器的有效用例,但是它们通常很少见。一个例子可能是你想避免持久化一个计算值,尽管你可能想问为什么你不在值的 getter 中按需计算它,或者在 getter 中懒惰地初始化它。就我个人而言,我想不出任何好的用例,而且这里的答案都没有真正给出“软件工程”的答案。
我更喜欢字段访问,因为这样我就不必为每个属性提供 getter/setter。
通过 Google 进行的一项快速调查表明,字段访问占大多数(例如, http: //java.dzone.com/tips/12-feb-jpa-20-why-accesstype)。
我相信字段访问是 Spring 推荐的成语,但我找不到支持它的参考。
有一个相关的 SO question试图衡量性能并得出“没有区别”的结论。
这是您必须使用属性访问器的情况。想象一下,你有一个 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
}
现在究竟应该如何注释这个类?答案是您根本无法使用字段或属性访问对其进行注释,因为此时您无法指定目标实体。您必须注释具体的实现。但是由于在这个超类中声明了持久属性,因此您必须在子类中使用属性访问。
在具有抽象通用超类的应用程序中,字段访问不是一个选项。
我倾向于更喜欢并使用属性访问器:
foo.getId()
不初始化代理的情况下调用(在使用 Hibernate 时很重要,直到HHH-3718得到解决)。退税:
@Transient
周围是否有。我更喜欢访问器,因为我可以在需要时向访问器添加一些业务逻辑。这是一个例子:
@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)。
我更喜欢使用字段访问,原因如下:
在实现 equals/hashCode 和直接引用字段(而不是通过它们的 getter)时,属性访问可能会导致非常讨厌的错误。这是因为只有在访问 getter 时才初始化代理,而直接字段访问只会返回 null。
属性访问要求您将所有实用程序方法(例如 addChild/removeChild)注释为@Transient
.
通过字段访问,我们可以通过完全不暴露 getter 来隐藏@Version
字段。getter 也可能导致添加 setter,并且version
永远不应该手动设置该字段(这可能会导致非常讨厌的问题)。所有版本增量都应该通过OPTIMISTIC_FORCE_INCREMENT
或PESSIMISTIC_FORCE_INCREMENT
显式锁定来触发。
让我试着总结一下选择基于字段的访问的最重要原因。如果您想深入了解,请阅读我博客上的这篇文章:JPA 和 Hibernate 中的访问策略——字段访问还是属性访问哪个更好?
到目前为止,基于字段的访问是更好的选择。这里有5个原因:
原因 1:更好的代码可读性
如果您使用基于字段的访问,您可以使用映射注释来注释实体属性。通过将所有实体属性的定义放在类的顶部,您可以获得所有属性及其映射的相对紧凑的视图。
原因 2:省略应用程序不应调用的 getter 或 setter 方法
基于字段的访问的另一个优点是您的持久性提供程序,例如 Hibernate 或 EclipseLink,不使用实体属性的 getter 和 setter 方法。这意味着您不需要提供任何不应被您的业务代码使用的方法。对于生成的主键属性或版本列的 setter 方法,这种情况最常见。您的持久性提供程序管理这些属性的值,您不应以编程方式设置它们。
理由三:灵活实现getter和setter方法
因为您的持久性提供程序不调用 getter 和 setter 方法,所以它们不会被迫满足任何外部要求。您可以以任何您想要的方式实现这些方法。这使您能够实现特定于业务的验证规则、触发其他业务逻辑或将实体属性转换为不同的数据类型。
例如,您可以使用它将可选关联或属性包装到 JavaOptional
中。
原因 4:无需将实用程序方法标记为@Transient
基于字段的访问策略的另一个好处是您不需要使用@Transient
. 这个注解告诉你的持久化提供者一个方法或属性不是实体持久状态的一部分。而且由于使用字段类型访问,持久状态由实体的属性定义,因此您的 JPA 实现会忽略实体的所有方法。
原因 5:使用代理时避免错误
Hibernate 使用代理来延迟获取一对一的关联,以便它可以控制这些关联的初始化。这种方法几乎适用于所有情况。但是,如果您使用基于属性的访问,它会引入一个危险的陷阱。
如果使用基于属性的访问,Hibernate 在调用 getter 方法时会初始化代理对象的属性。如果您在业务代码中使用代理对象,情况总是如此。但是相当多的equals 和 hashCode 实现直接访问属性。如果这是您第一次访问任何代理属性,这些属性仍未初始化。
这实际上取决于具体情况——这两种选择都是有原因的。IMO 归结为三种情况:
如果您想在设置器中做更多的事情而不仅仅是设置值(例如加密或计算),我强烈建议您在获取器(属性访问)上进行字段访问而不是注释。
属性访问的问题是在加载对象时也会调用 setter。在我们想要引入加密之前,这对我来说已经好几个月了。在我们的用例中,我们想在 setter 中加密一个字段并在 getter 中解密它。现在属性访问的问题是,当 Hibernate 加载对象时,它还调用 setter 来填充字段,从而再次加密加密的值。这篇文章还提到了这一点: Java Hibernate:不同的属性集函数行为取决于谁调用它
这让我很头疼,直到我记得字段访问和属性访问之间的区别。现在,我已将所有注释从属性访问移至字段访问,并且现在可以正常工作。
我相信属性访问与字段访问在延迟初始化方面略有不同。
考虑以下 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()
而不需要。
我认为注释属性更好,因为更新字段直接破坏封装,即使您的 ORM 这样做。
这是一个很好的例子,说明它会在哪里烧毁你:你可能希望你的注释用于休眠验证器和持久性在同一个地方(字段或属性)。如果要测试在字段上注释的休眠验证器驱动的验证,则不能使用实体的模拟将单元测试隔离到验证器。哎哟。
默认情况下,JPA 提供者访问实体字段的值,并使用实体的 JavaBean 属性访问器(getter)和修改器(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 属性名称匹配。在这种情况下,如果您对字段进行注释,它们不必匹配。最好保持一致并注释 JavaBean 访问器AccessType.PROPERTY
和
AccessType.FIELD
.
切勿在同一实体中混用 JPA 属性注释和 JPA 字段注释,这一点很重要。这样做会导致未指定的行为,并且很可能会导致错误。
这是一个旧的演示文稿,但 Rod 建议对属性访问进行注释会鼓励贫乏的域模型,并且不应该是注释的“默认”方式。
我喜欢字段访问器。代码更干净。所有的注释都可以放在一个类的一个部分中,代码更容易阅读。
我发现了属性访问器的另一个问题:如果你的类上有 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>
支持字段访问的另一点是,否则您将被迫公开集合的设置器,对我来说,这是一个坏主意,因为将持久集合实例更改为不受 Hibernate 管理的对象肯定会破坏您的数据一致性。
因此,我更喜欢将集合作为受保护字段初始化为默认构造函数中的空实现,并且只公开它们的 getter。clear()
然后,只有像,remove()
等这样的托管操作removeAll()
是可能的,它们永远不会让 Hibernate 不知道更改。
我更喜欢字段,但我遇到了一种情况,似乎迫使我将注释放在 getter 上。
使用 Hibernate JPA 实现,@Embedded
似乎不适用于字段。所以这必须在吸气剂上进行。一旦你把它放在 getter 上,那么各种@Column
注释也必须放在 getter 上。(我认为 Hibernate 不希望在这里混合字段和吸气剂。)一旦你@Column
在一个类中使用吸气剂,那么在整个过程中这样做可能是有意义的。
您应该选择通过字段访问而不是通过属性访问。使用字段,您可以限制发送和接收的数据。使用 via 属性,您可以作为主机发送更多数据,并设置 G 面额(工厂设置了大部分属性)。
通常 bean 是 POJO,所以无论如何它们都有访问器。
所以问题不是“哪个更好?”,而只是“何时使用字段访问?”。答案是“当您不需要该领域的 setter/getter 时!”。
我对休眠中的 accesstype 有同样的问题,并在这里找到了一些答案。
我在这里解决了延迟初始化和字段访问Hibernate one-to-one: getId() without fetching entire object
我们创建了实体 bean 并使用了 getter 注释。我们遇到的问题是:某些实体对某些属性的更新时间有复杂的规则。解决方案是在每个 setter 中设置一些业务逻辑,以确定实际值是否更改,如果更改,是否应允许更改。当然,Hibernate 总是可以设置属性,所以我们最终得到了两组 setter。真是丑。
阅读以前的帖子,我还看到从实体内部引用属性可能会导致无法加载集合的问题。
最重要的是,我会倾向于在未来对这些领域进行注释。
我正在考虑这个,我选择了方法访问器
为什么?
因为字段和方法访问器是相同的,但如果以后我需要在加载字段中添加一些逻辑,我保存移动放置在字段中的所有注释
问候
格鲁哈特
为了使您的类更干净,请将注释放在字段中,然后使用 @Access(AccessType.PROPERTY)
两个都 :
EJB3 规范要求您在将被访问的元素类型上声明注释,即,如果您使用属性访问,则为 getter 方法,如果您使用字段访问,则为字段。
https://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping
AccessType.PROPERTY:EJB 持久性实现将通过 JavaBean“setter”方法将状态加载到您的类中,并使用 JavaBean“getter”方法从您的类中检索状态。这是默认设置。
AccessType.FIELD:直接从类的字段中加载和检索状态。您不必编写 JavaBean “getters”和“setters”。