Mar*_*nor 129 java orm annotations hibernate jpa
这个问题与Hibernate Annotation Placement Question有些相关.
但我想知道哪个更好?通过属性访问或通过字段访问?各有哪些优缺点?
Mar*_*tin 236
两者都存在争议,但大多数都源于某些用户要求"如果需要添加逻辑",或"xxxx打破封装".然而,没有人真正对这一理论发表评论,并给出了合理的理由.
什么是Hibernate/JPA在持久化对象时实际做的事情 - 好吧,它是持久化对象的状态.这意味着以一种可以轻松复制的方式存储它.
什么是封装?封装意味着使用应用程序/客户端可以用来安全访问数据的接口封装数据(或状态) - 保持数据的一致性和有效性.
把它想象成MS Word.MS Word在内存中维护文档的模型 - 文档STATE.它提供了一个用户可以用来修改文档的界面 - 一组按钮,工具,键盘命令等.但是,当您选择保留(保存)该文档时,它会保存内部状态,而不是一组按键和鼠标点击用于生成它.
保存对象的内部状态不会破坏封装 - 否则您并不真正理解封装意味着什么,以及它存在的原因.它就像对象序列化一样.
因此,在大多数情况下,坚持FIELDS而不是ACCESSORS是合适的.这意味着可以从数据库中精确地重新创建对象,就像存储它一样.它不应该需要任何验证,因为这是在原始文件创建时,在它存储在数据库之前完成的(除非,上帝禁止,你在DB中存储无效数据!!!!).同样,应该不需要计算值,因为它们是在存储对象之前已经计算的.该对象应该看起来像它保存之前的方式.事实上,通过在getter/setter中添加额外的东西,实际上增加了重新创建不是原始副本的东西的风险.
当然,添加此功能是有原因的.可能存在一些用于持久访问者的有效用例,但是,它们通常很少见.一个例子可能是你想要避免持久计算一个值,尽管你可能想问一个问题,为什么你不在值的getter中按需计算它,或懒惰地在getter中初始化它.我个人认为没有任何好的用例,这里的答案都没有给出"软件工程"的答案.
duf*_*ymo 77
我更喜欢字段访问,因为这样我就不会被迫为每个属性提供getter/setter.
通过Google进行的快速调查表明,现场访问占大多数(例如,http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype).
我认为现场访问是Spring推荐的习惯用法,但是我无法找到支持它的参考.
有一个相关的SO问题,试图衡量绩效,并得出结论,"没有差异".
HDa*_*ave 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
}
Run Code Online (Sandbox Code Playgroud)
现在究竟该如何注释这个类?答案是您无法使用字段或属性访问来注释它,因为此时您无法指定目标实体.您必须注释具体实现.但是由于持久化属性是在这个超类中声明的,所以你必须在子类中使用属性访问.
在具有抽象通用超类的应用程序中,字段访问不是一个选项.
Mig*_*ing 32
我更喜欢访问器,因为我可以随时在我的访问器中添加一些业务逻辑.这是一个例子:
@Entity
public class Person {
@Column("nickName")
public String getNickName(){
if(this.name != null) return generateFunnyNick(this.name);
else return "John Doe";
}
}
Run Code Online (Sandbox Code Playgroud)
此外,如果你将另一个libs放入混合中(比如一些JSON转换的lib或BeanMapper或Dozer或其他基于getter/setter属性的bean映射/克隆lib),你将保证lib与持久性同步.经理(都使用getter/setter).
Tho*_*sen 16
让我尝试总结选择基于字段的访问的最重要原因。如果您想深入了解,请阅读我博客上的这篇文章:JPA 和 Hibernate 中的访问策略 – 字段访问和属性访问哪个更好?
迄今为止,基于现场的访问是更好的选择。这里有5个原因:
原因 1:更好的代码可读性
如果您使用基于字段的访问,您可以使用映射注释来注释实体属性。通过将所有实体属性的定义放在类的顶部,您可以获得所有属性及其映射的相对紧凑的视图。
原因 2:省略不应由您的应用程序调用的 getter 或 setter 方法
基于字段的访问的另一个优点是您的持久性提供程序(例如,Hibernate 或 EclipseLink)不使用实体属性的 getter 和 setter 方法。这意味着您不需要提供任何不应由您的业务代码使用的方法。对于生成的主键属性或版本列的setter 方法来说,这是最常见的情况。您的持久性提供程序管理这些属性的值,您不应以编程方式设置它们。
理由三:灵活实现getter和setter方法
因为您的持久性提供程序不调用 getter 和 setter 方法,所以它们不会被迫满足任何外部要求。您可以以任何您想要的方式实现这些方法。这使您能够实现特定于业务的验证规则、触发额外的业务逻辑或将实体属性转换为不同的数据类型。
例如,您可以使用它来将可选关联或属性包装到 Java 中Optional
。
理由 4:无需将实用方法标记为 @Transient
基于字段的访问策略的另一个好处是您不需要使用@Transient
. 这个注解告诉你的持久化提供者一个方法或属性不是实体持久化状态的一部分。并且因为使用字段类型访问持久状态由您的实体的属性定义,您的 JPA 实现会忽略您的实体的所有方法。
原因 5:避免使用代理时出现错误
Hibernate 为延迟获取的一对一关联使用代理,以便它可以控制这些关联的初始化。这种方法几乎适用于所有情况。但是如果您使用基于属性的访问,它会引入一个危险的陷阱。
如果您使用基于属性的访问,当您调用 getter 方法时,Hibernate 会初始化代理对象的属性。如果您在业务代码中使用代理对象,情况总是如此。但是相当多的equals 和 hashCode 实现直接访问属性。如果这是您第一次访问任何代理属性,这些属性仍未初始化。
01e*_*1es 13
这实际上取决于具体案例 - 两种选择都是有原因的.IMO归结为三种情况:
Chr*_*oph 12
如果你想在setter中做更多的事情而不仅仅是设置值(例如加密或计算),我强烈建议对getter(属性访问)进行字段访问和NOT注释.
属性访问的问题是在加载对象时也会调用setter.在我们想要引入加密之前,这对我来说很有用.在我们的用例中,我们想要加密setter中的字段并在getter中解密它.现在属性访问的问题是,当Hibernate加载对象时,它还调用setter来填充字段,从而再次加密加密值.这篇文章还提到了这一点: Java Hibernate:不同的属性设置函数行为取决于谁调用它
这让我头疼,直到我记得现场访问和财产访问之间的区别.现在我已将所有注释从属性访问移至现场访问,现在工作正常.
我更喜欢使用字段访问,原因如下:
在实现equals/hashCode并直接引用字段(而不是通过它们的getter)时,属性访问可能会导致非常讨厌的错误.这是因为代理仅在访问getter时初始化,而直接字段访问只返回null.
该属性访问要求您注释的所有实用程序方法(如的addChild/removeChild之)作为@Transient
.
通过字段访问,我们可以通过不暴露getter来隐藏@Version字段.一个getter也可以导致添加一个setter,并且version
永远不应该手动设置该字段(这可能导致非常讨厌的问题).应通过OPTIMISTIC_FORCE_INCREMENT或PESSIMISTIC_FORCE_INCREMENT显式锁定来触发所有版本增量.
我认为对属性进行注释会更好,因为更新字段会直接破坏封装,即使您的ORM执行此操作也是如此.
这是一个很好的例子,说明它会烧掉你的位置:你可能希望在同一个地方(字段或属性)中使用hibernate验证器和持久性的注释.如果要测试在字段上注释的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>
Run Code Online (Sandbox Code Playgroud)
以下单元测试:
@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();
}
Run Code Online (Sandbox Code Playgroud)
您将看到所需选择的细微差别:
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
Run Code Online (Sandbox Code Playgroud)
也就是说,调用fb.getId()
需要选择,而pb.getId()
不是.
默认情况下,JPA 提供程序访问实体字段的值,并使用实体\xe2\x80\x99s JavaBean 属性访问器(getter)和修改器(setter)方法将这些字段映射到数据库列。因此,实体中私有字段的名称和类型对于 JPA 来说并不重要。相反,JPA 仅查看\nJavaBean 属性访问器的名称和返回类型。您可以使用以下命令更改此设置@javax.persistence.Access
,这使您能够显式指定 JPA 提供程序\n应采用的访问方法。
@Entity\n@Access(AccessType.FIELD)\npublic class SomeEntity implements Serializable\n{\n...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\nAccessType 枚举的可用选项是 PROPERTY(默认)和 FIELD。对于PROPERTY,提供程序可以使用JavaBean 属性方法获取和设置字段值。FIELD 使提供者使用实例字段获取和设置字段值。作为最佳实践,您应该坚持\n默认值并使用 JavaBean 属性,除非您有令人信服的理由不这样做。
\n\n您可以将这些属性注释放在私有字段或公共访问器方法上。如果\n您使用AccessType.PROPERTY
(默认)并注释私有字段而不是 JavaBean\n访问器,则字段名称必须与 JavaBean 属性名称匹配。但是,如果您对 JavaBean 访问器进行注释,则名称\n不必匹配。同样,如果您使用AccessType.FIELD
并注释 JavaBean 访问器而不是字段,则字段名称也必须与 JavaBean 属性名称匹配。在这种情况下,如果您对字段进行注释,则它们不必匹配。最好保持一致并注释 JavaBean 访问器AccessType.PROPERTY
和字段AccessType.FIELD
。
重要的是,切勿在同一个实体中混合使用 JPA 属性注释和 JPA 字段注释。这样做会导致未指定的行为,并且很可能\n导致错误。
\n 归档时间: |
|
查看次数: |
107387 次 |
最近记录: |