这个问题与Hibernate Annotation Placement Question有关。
但是我想知道哪个更好?通过属性访问还是通过字段访问?每种都有哪些优点和缺点?
这个问题与Hibernate Annotation Placement Question有关。
但是我想知道哪个更好?通过属性访问还是通过字段访问?每种都有哪些优点和缺点?
Answers:
我更喜欢访问器,因为我可以在需要时向访问器添加一些业务逻辑。这是一个例子:
@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)。
两者都有参数,但是它们大多数来自某些用户要求,“如果需要为其添加逻辑”或“ xxxx中断封装”。但是,没有人真正对这一理论发表评论,也没有给出合理的论据。
Hibernate / JPA在持久化一个对象时实际上在做什么-嗯,它持久化了对象的STATE。这意味着以易于复制的方式存储它。
什么是封装?封装是指使用应用程序/客户端可以用来安全访问数据的接口封装数据(或状态),以保持数据的一致性和有效性。
可以将其视为MS Word。MS Word在内存中维护文档的模型-文档STATE。它提供了一个供用户用来修改文档的界面-一组按钮,工具,键盘命令等。但是,当您选择保留(保存)该文档时,它将保存内部状态,而不是一组按键和用来生成它的鼠标单击。
保存对象的内部状态不会破坏封装-否则您将无法真正理解封装的含义以及存在的原因。确实就像对象序列化。
因此,在大多数情况下,最好保留字段而不是访问者。这意味着可以完全按照对象的存储方式从数据库中准确地重新创建对象。它不需要任何验证,因为它是在原始文件创建时以及原始文件存储在数据库中之前进行的(除非上帝禁止,您正在数据库中存储无效数据!!!)。同样,也不需要计算值,因为在存储对象之前已经计算了它们。对象应该看起来像保存之前一样。实际上,通过将其他内容添加到getter / setter中,实际上增加了您将重新创建与原始版本不完全相同的内容的风险。
当然,添加此功能是有原因的。可能存在一些用于持久保存访问器的有效用例,但是,它们通常很少见。例如,您可能想问一个问题,为什么不按需在值的getter中计算它,或者在getter中惰性地对其进行初始化,所以您可能想避免保留计算所得的值。就我个人而言,我想不出任何好的用例,这里的答案都没有真正给出“软件工程”的答案。
我更喜欢现场访问,因为这样一来,我就不必为每个属性提供getter / setter方法。
通过Google进行的一项快速调查显示,现场访问占多数(例如,http : //java.dzone.com/tips/12-feb-jpa-20-why-accesstype)。
我相信字段访问是Spring建议的习惯用法,但是我找不到支持它的参考。
有一个相关的SO问题试图衡量性能,并得出结论“没有区别”。
在这种情况下,您必须使用属性访问器。想象一下,您有一个具有很多实现优点的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
}
现在,您究竟应该如何注释该课程?答案是您根本无法使用字段或属性访问来对其进行注释,因为此时您无法指定目标实体。您必须注释具体的实现。但是,由于持久属性是在此超类中声明的,因此您必须在子类中使用属性访问。
在具有抽象泛型超类的应用程序中,字段访问不是一个选项。
abstract T getOneThing()
和abstract void setOneThing(T thing)
,并使用字段访问。
这实际上取决于具体情况-两种选择都可以使用是有原因的。IMO归结为三种情况:
如果您要在设置器中做更多的事情,而不是仅仅设置值(例如,加密或计算),我强烈建议在getter上进行字段访问和不添加批注(属性访问)。
属性访问的问题是,在加载对象时也会调用设置器。在我想引入加密之前,这对我来说已经工作了好几个月。在我们的用例中,我们想对设置器中的字段进行加密,然后在获取器中对其进行解密。现在,属性访问的问题在于,当Hibernate加载对象时,它还调用了setter来填充字段,从而再次加密了加密后的值。这篇文章也提到了这一点: Java Hibernate:不同的属性集函数行为取决于谁调用它
这让我头疼,直到我想起了现场访问和属性访问之间的区别。现在,我已将所有注释从属性访问移至字段访问,并且现在可以正常工作。
我更喜欢使用字段访问,原因如下:
当实现equals / hashCode并直接引用字段时(通过其getter进行相反),属性访问会导致非常讨厌的错误。这是因为仅在访问getter时才初始化代理,而直接字段访问将仅返回null。
该属性访问要求您注释的所有实用程序方法(如的addChild / removeChild之)作为@Transient
。
使用字段访问,我们可以通过根本不公开获取方法来隐藏@Version字段。吸气剂也可能导致添加setter,并且version
永远不应手动设置字段(这可能会导致非常麻烦的问题)。所有版本增量都应通过OPTIMISTIC_FORCE_INCREMENT或PESSIMISTIC_FORCE_INCREMENT显式锁定来触发。
version
在使用DTO而不是分离的实体的情况下,公开字段访问器通常很有用。
我认为对属性进行注释会更好,因为更新字段会直接破坏封装,即使您的ORM这样做也是如此。
这是一个在哪里烧死您的好例子:您可能希望在同一位置(字段或属性)使用休眠验证程序和持久性的注释。如果要测试在字段上注释的由休眠验证器提供的验证,则不能使用实体的模拟将单元测试仅与验证器隔离。哎哟。
我认为就懒惰初始化而言,属性访问与现场访问之间存在细微的差异。
考虑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()
。
让我尝试总结选择基于字段的访问的最重要原因。如果您想更深入,请阅读我的博客上的这篇文章: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实现直接访问属性。如果这是您第一次访问任何代理属性,则这些属性仍未初始化。
默认情况下,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属性名称匹配。在这种情况下,如果您注释字段,则它们不必匹配。最好保持一致并为的JavaBean访问器AccessType.PROPERTY
和的字段
添加注释AccessType.FIELD
。
重要的是,切勿在同一实体中混合使用JPA属性注释和JPA字段注释。这样做会导致未指定的行为,并且很有可能导致错误。
那是一个古老的演示,但是Rod建议在属性访问上进行注释会鼓励贫乏的领域模型,而不应是“默认”的注释方法。
支持字段访问的另一点是,否则,您将不得不公开集合的设置器,这对我来说也是个坏主意,因为将持久性集合实例更改为非Hibernate管理的对象肯定会破坏您的数据一致性。
因此,我更喜欢将集合作为受保护的字段初始化为默认构造函数中的空实现,并仅公开其getter。像这样,只有管理操作clear()
,remove()
,removeAll()
等有可能不会让休眠不知情的变化。
我更喜欢字段,但遇到一种情况似乎迫使我将注释放在吸气剂上。
使用Hibernate JPA实施,@Embedded
似乎不适用于字段。因此,这必须继续进行。一旦将其放在吸气剂上,那么各种@Column
注释也必须在吸气剂上进行。(我认为Hibernate不想在这里混合使用字段和getter。)一旦您将@Column
getter放在一个类中,就可以在整个过程中这样做。
我赞成现场访问者。代码更简洁。所有注释都可以放在类的一个部分中,并且代码更易于阅读。
我发现了属性访问器的另一个问题:如果您的类上有未注释为与持久性属性相关联的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>
您应该选择通过字段访问而不是通过属性访问。使用字段可以限制发送和接收的数据。使用via属性,您可以将更多数据作为主机发送,并设置G面额(该工厂总共设置了大多数属性)。
我已经在这里解决了延迟初始化和字段访问的休眠,使其一对一地休眠:getId()无需获取整个对象
通常,bean是POJO,因此它们仍然具有访问器。
因此,问题不是“哪个更好?”,而仅仅是“何时使用现场访问?”。答案是“当您不需要该领域的设置者/获取者时!”。
两者:
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”。