原文:JPA implementation patterns: Field access vs. property access
作者:Vincent Partington
出处:
http://blog.xebia.com/2009/06/13/jpa-implementation-patterns-field-access-vs-property-access/
上周我的同事Albert Sikkema写了一篇关于使用UUID作为主键的博客,很有意思的一篇东西,再次谢谢你,Albert!本周我将继续这一JPA实施模式系列,讨论域访问(field access)和属性访问(property access)两者的优缺点比较。
JPA规范为持久性提供程序(persistence provider)提供两种访问实体的持久内容的方式,持久性提供程序既可以调用JavaBean风格的属性访问方法(getter和setter方法),也可以直接访问实体的实例域,使用哪一种方法则取决于你是否已经为实体的属性或者域设定了注解。
JPA 1.0规范不允许在一个实体内,或甚至是实体的层次结构内混合使用访问类型,如果域和属性这两种注解都被设定了的话,那么行为方式会是不确定的。JPA 2.0规范使用@Access注解来使得在一个实体或者实体层次结构中混合使用访问类型变成可能的。
不过令人感兴趣的问题依然存在:使用哪一种访问类型?这是一个以前已经讨论过的问题,但也是我无法抗拒进行评论的一个问题。
封装性—属性访问据说是为了提供更好的封装性,因为直接访问域是不好的,真是这样吗?实际的情况是,使用属性访问迫使你为所有的持久属性编写getter和setter方法。这些方法不仅允许JPA提供程序设置域,而且允许实体类的任何其他用户这样做!使用域访问则允许你只编写想要的getter和setter方法(它们是邪恶的,还记得吗?),以及还可以把它们编写成你想要的样子,例如,在setter方法中进行验证或者在getter方法中进行一些计算。相反,在使用属性访问时,使这些方法变得更聪明则只是在自找麻烦。
性能—有些人宁愿选择域访问,因为它看起来比属性访问提供了更好的性能,不过这是一个非常糟糕的选择域访问的理由。现在的优化JVM会使得属性访问的执行与域访问同样地快,而且在任何情况下,数据库的调用都要比域访问或者方法调用慢上几个数量级。
Hibernate的延迟加载—Hibernate延迟加载的实现总是在代理的任意方法被调用时初始化该延迟代理,唯一的例外是在使用属性访问的时候,使用了@Id这一注解来进行注释了的方法。但是在使用域访问的时候就不存在这样的方法,而且Hibernate甚至在调用返回实体标识的方法的时候也会初始化代理。虽然有些人建议使用属性访问直到这一错误被修正,不过我并不赞成在框架错误方面进行基础的设计决策,如果该错误确实是损害了性能的话,你可能会想尝试用以下代码来获取实体的id:
Serializable id = ((HibernateProxy) entity).getHibernateLazyInitializer().getIdentifier()
代码并不友好,但至少可局部用在你真正需要它的地方。
Hibernate的域访问—最好要知道,虽然对于Hibernate来说,使用域访问来填充实体是可以的,不过你的代码仍应该通过方法来访问这些值,否则你将陷入我的同事Maarten Winkels提到的Hibernate代理陷阱的第一个之中。
总结一下,我认为域访问是适宜使用的方式,因为它提供了更好的封装性(没有它的话,妥善管理双向关联是不可能的),并且性能影响可以忽略不计(十大性能问题中的第一条#1仍然是数据库和Java应用之间的相互影响)。唯一的缺点是,Hibernate延迟加载实现的局面有些混乱,这就要求你在使用域访问的时候要额外地小心。
你喜欢使用什么样的访问类型?在域访问和属性访问的实现方式方面,你有没有发现JPA提供程序有任何不同于Hibernate的地方?请在下面留下评论以便让我知道,我们在下一篇JPA实施模式的博客中再见,下次我会谈谈JPA中的继承层次结构。