Hibernate的注释 – 哪个更好,是字段还是属性?

这个问题与Hibernate Annotation Placement Question有些相关。

但是我想知道哪个更好 ? 通过属性访问或通过字段访问? 每个的优点和缺点是什么?

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

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

另外,如果你把另外一个库放在一起(比如一些基于getter / setter属性的JSON转换库,BeanMapper或者Dozer或者其他的bean映射/克隆库),你可以保证lib与持久化同步经理(都使用getter / setter)。

有两个参数,但其中大部分源于某些用户需求“如果需要添加逻辑”或“xxxx中断封装”。 然而,没有人真正评论这个理论,并给出了一个合理的论据。

Hibernate / JPA在持久化对象时究竟在做什么 – 它是持久化对象的状态。 这意味着以易于复制的方式进行存储。

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

想想像MS Word一样。 MS Word维护内存中的文档模型 – 文档状态。 它提供了一个界面,用户可以使用它来修改文档 – 一组button,工具,键盘命令等。但是,当您select保留(保存)该文档时,它将保存内部状态,而不是按键组和用鼠标点击生成它。

保存对象的内部状态不会破坏封装 – 否则你不会真正理解封装是什么意思,为什么它存在。 这就像对象序列化一样。

因此,在大多数情况下,坚持FIELDS而不是ACCESSORS是合适的。 这意味着一个对象可以从数据库准确地重新创build它的存储方式。 它不应该需要任何validation,因为这是在原始数据创build时以及数据库被存储之前完成的(除非上帝保佑您将无效数据存储在数据库中!!!!)。 同样,应该不需要计算值,因为它们在对象被存储之前已经被计算出来了。 该对象应该看起来像它被保存之前的方式。 事实上,通过在getter / setter中添加额外的东西,实际上增加了您将重新创build不是原始副本的东西的风险。

当然,这个function是有原因的。 持久访问者可能有一些有效的用例,但是,它们通常很less见。 一个例子可能是你想要避免保存一个计算的值,尽pipe你可能想问一个问题,为什么你不要在值的getter中按需计算,或者懒惰地在getter中初始化它。 就我个人而言,我想不出有什么好的用例,这里的答案都没有给出“软件工程”的答案。

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

通过Google进行的快速调查表明,字段访问是大多数(例如http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype )。

我相信现场访问是春季推荐的成语,但我找不到一个参考。

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

以下是您必须使用属性访问器的情况。 想象一下,你有一个GENERIC抽象类,有很多实现的好处,可以inheritance8个具体的子类:

 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

这真的取决于具体情况 – 两个选项都是有原因的。 IMO归结为三种情况:

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

如果你想在setter中做更多的事情,比强调值(例如encryption或者计算),我强烈推荐field访问,而不是getters注解(property access)。

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

这让我头痛,直到我想起现场访问和财产访问之间的区别。 现在我已经把所有的注解从属性访问移到了字段访问,现在它工作正常。

我认为注释属性更好,因为更新字段直接破坏封装,即使您的ORM执行它。

下面是一个很好的例子,它会烧你:你可能想要在同一个地方(字段或属性)的hibernatevalidation器和持久性的注释。 如果你想testing一个在字段上注释的hibernate validatordynamicvalidation,你不能使用你的实体的模拟来把你的unit testing隔离到validation器上。 哎哟。

我相信,对于懒惰的初始化,属性访问和字段访问是微妙的不同的。

考虑下面的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> 

下面的unit testing:

 @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(); } 

您将会看到所需select的细微差别:

 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. 当实现equals / hashCode和直接引用字段 (而不是通过它们的getter)时, 属性访问会导致非常讨厌的错误。 这是因为代理只在获取getters时被初始化,直接字段访问只会返回null。

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

  3. 通过字段访问,我们可以隐藏@Version字段,根本不暴露一个getter。 一个getter也可以导致添加一个setter,而version字段不应该手动设置(这可能会导致非常讨厌的问题)。 所有版本增量应该通过OPTIMISTIC_FORCE_INCREMENT或PESSIMISTIC_FORCE_INCREMENT显式locking来触发。

我们到了吗

这是一个旧的介绍,但罗德build议,财产访问注释鼓励贫血域模型,不应该是“默认”的方式来注释。

另一个有利于字段访问的观点是,否则你不得不暴露set的集合,对于我来说,将持久集合实例改变为不受Hibernatepipe理的对象肯定会破坏数据的一致性。

所以我更喜欢将集合作为受保护的字段初始化为默认构造函数中的空实现,并只公开他们的getter。 然后,只有像clear()remove()removeAll()等pipe理操作是可能的,永远不会让Hibernate不知道更改。

我更喜欢田野,但是我遇到了一种似乎迫使我把注释放在吸气者身上的情况。

使用Hibernate JPA实现,@ @Embedded似乎不适用于字段。 所以这必须吸气。 一旦你把它放在getter上,那么各种@Column注解也必须放在getter上。 (我认为Hibernate不希望混合字段和getter在这里)。一旦你把@Column放在一个类中的getter上,这样做可能是有意义的。

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

我发现了另一个属性访问器的问题:如果你的类没有注明与持久属性关联的XYZ方法,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> 

我有关于在hibernateaccesstype相同的问题,并在这里find一些答案 。

我已经解决了懒惰的初始化和字段访问这里hibernate一对一:getId()没有获取整个对象

我们创build了实体bean并使用了getter注解。 我们遇到的问题是:有些实体对于某些属性何时可以更新有复杂的规则。 解决方法是在每个设置器中都有一些业务逻辑,用于确定实际值是否发生变化,如果是,是否允许更改。 当然,Hibernate总是可以设置属性,所以我们结束了两组设置器。 很难看。

阅读以前的post,我也看到,从实体内引用属性可能会导致与集合不加载的问题。

底线,我会倾向于未来注解领域。

默认情况下,JPA提供程序访问实体字段的值,并使用实体的JavaBean属性访问器(getter)和mutator(setter)方法将这些字段映射到数据库列。 因此,实体中私有字段的名称和types与JPA无关。 相反,JPA只查看JavaBean属性访问器的名称和返回types。 您可以使用@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.PROPERTYAccessType.FIELD字段进行一致性和注释的JavaBean访问AccessType.FIELD

请勿将JPA属性注释和JPA字段注释混合在同一个实体中,这一点非常重要。 这样做会导致不确定的行为,很可能会导致错误。

通常豆是POJO,所以他们有访问者。

所以问题不是“哪一个更好?”,而是“什么时候使用现场访问?”。 答案是“当你不需要领域的一个二传手/吸气剂!”。

我想这个,我select方法accesor

为什么?

因为字段和方法accesor是相同的,但如果后来我需要在加载领域的一些逻辑,我保存移动所有注释放在字段

问候

Grubhart

为了使你的类更清洁,把注释放在字段中,然后使用@Access(AccessType.PROPERTY)