什么是休眠? Hibernate Core 实现的基础
已发表: 2013-04-17Hibernate 是一个开源的 Java 持久性框架项目。 使用 HQL 和 SQL 执行强大的对象关系映射和查询数据库。
一般来说,广泛使用的库都经过精心设计和实现,向它们学习一些编码最佳实践非常有趣。
让我们看一下 hibernate 核心库的内部,并发现它的一些设计关键。
在这篇文章中, JArchitect
对 Hibernate Core 进行了分析,以深入了解其设计和实现。
按功能打包
Package-by-feature
使用包来反映功能集。 它将与单个功能(并且仅该功能)相关的所有项目放入单个目录/包中。 这导致包具有高内聚性和高度模块化,并且包之间的耦合最小。 紧密结合的项目彼此相邻放置。
Hibernate 核心包含许多包,每个包都与特定功能 hql、sql 等相关。
耦合
低耦合是可取的,因为在应用程序的一个区域中的更改将需要在整个应用程序中进行较少的更改。 从长远来看,这可以减少与修改和向应用程序添加新功能相关的大量时间、精力和成本。
以下是使用接口带来的三个主要好处:
- 接口提供了一种定义促进重用的契约的方法。 如果一个对象实现了一个接口,那么该对象将符合一个标准。 使用另一个对象的对象称为消费者。 接口是对象与其消费者之间的契约。
- 接口还提供了使程序更易于理解的抽象级别。 接口允许开发人员开始讨论代码行为的一般方式,而不必涉及许多详细的细节。
- 接口强制组件之间的低耦合,这使得保护接口使用者免受实现接口的类中的任何实现更改变得容易。
让我们搜索 Hibernate Core 定义的所有接口,为此我们使用CQLinq
来查询代码库。
1 |
from t in Types where t . IsInterface select t |
如果我们的主要目标是强制低耦合,那么在使用接口时会出现一个常见错误,这可能会破坏使用它们的实用性。 这是使用具体类而不是接口,为了更好地解释这个问题,让我们举个例子:
类 A 实现了包含 calculate() 方法的接口 IA,消费者类 C 是这样实现的
1 2 3 4 5 6 7 8 9 10 11 |
public class C < br > { … . public void calculate ( ) { … . . m_a . calculate ( ) ; … . } A m_a ; } |
类 C 不是引用接口 IA,而是引用类 A,在这种情况下我们失去了低耦合的好处,这种实现有两个主要缺点:
- 如果我们决定使用 IA 的另一个实现,我们必须更改 C 类的代码。
- 如果在 A 中添加了一些在 IA 中不存在的方法,而 C 使用它们,我们也失去了使用接口的合同利益。
C# 为语言引入了显式接口实现能力,以确保 IA 中的方法永远不会从对具体类的引用中调用,而只会从对接口的引用中调用。 这种技术对于保护开发人员免于失去使用接口的好处非常有用。
使用 JArchitect,我们可以使用CQLinq
检查此类错误,其想法是从其他方法直接使用的具体类中搜索所有方法。
1 2 3 4 5 6 7 8 9 |
from m in Methods where m . NbMethodsCallingMe > 0 && m . ParentType . IsClass && ! m . ParentType . IsThirdParty && ! m . ParentType . IsAbstract let interfaces = m . ParentType . InterfacesImplemented from i in interfaces where i . Methods . Where ( a = > a . Name == m . Name && a . ParentType ! = m . ParentType ) . Count ( ) > 0 select new { m , m . ParentType , i } |
例如,实现 SessionFactoryImplementor 接口的 SessionFactoryImpl 中的 getEntityPersister 方法就关注这个问题。
让我们搜索直接调用 SessionFactoryImpl.getEntityPersister 的方法。
1 2 3 |
from m in Methods where m . IsUsing ( "org.hibernate.internal.SessionFactoryImpl.getEntityPersister(String)" ) select new { m , m . NbBCInstructions } |

像 SessionImpl.instantiate 这样的方法直接调用 getEntityPersister,而不是通过接口传递,这破坏了使用接口的好处。 幸运的是,hibernate core 不包含很多有这个问题的方法。
与外部罐子耦合
当使用外部库时,最好检查一下我们是否可以轻松地将第三方库更改为另一个库而不影响整个应用程序,有很多原因可以鼓励我们更改第三方库。
另一个库可以:
- 拥有更多功能
- 更加强大
- 更安全
让我们以antlr lib
为例,它用于解析hql查询,想象一下另一个比antlr更强大的解析器被创建,我们可以很容易地用新的解析器改变antlr吗?
要回答这个问题,让我们搜索一下 hibernate 中哪些方法直接使用它:
1 2 3 |
from m in Methods where m . IsUsing ( "antlr-2.7.7" ) select new { m , m . NbBCInstructions } |
哪些间接使用了它:
1 2 3 4 |
from m in Projects . WithNameNotIn ( "antlr-2.7.7" ) . ChildMethods ( ) let depth0 = m . DepthOfIsUsing ( "antlr-2.7.7" ) where depth0 > 1 orderby depth0 select new { m , depth0 } |
许多方法直接使用antlr,这使得hibernate核心与其高度耦合,而将antlr换成另一个并不是一件容易的事。 这个事实并不意味着我们在休眠设计中存在问题,但是我们在使用第三方库时必须小心,并仔细检查第三方库是否必须与应用程序低耦合。
凝聚
单一责任原则指出,一个类应该有一个并且只有一个改变的理由。 据说这样的类是有凝聚力的。 高LCOM
值通常表明内聚性较差的类。 有几个 LCOM 指标。 LCOM 的取值范围为 [0-1]。 LCOMHS(HS 代表 Henderson-Sellers)取值在 [0-2] 范围内。 请注意,LCOMHS 度量通常被认为更有效地检测非内聚类型。
LCOMHS 值高于 1 应被视为警报。
一般来说,更关心内聚的类是具有许多方法和字段的类。
让我们搜索具有许多方法和字段的类型。
1 2 3 4 |
from t in Types where ( t . Methods . Count ( ) > 40 | | t . Fields . Count ( ) > 40 ) && t . IsClass orderby t . Methods . Count ( ) descending select new { t , t . InstanceMethods , t . Fields , t . LCOMHS } |
此查询只关注少数类型,并且所有这些类型的 LCOMHS 都小于 1。
使用注解
基于注解的开发让 Java 开发者从繁琐的配置中解脱出来。 并为我们提供了一个强大的功能,可以将源代码从样板代码中解放出来。 生成的代码也不太可能包含错误。
让我们搜索 hibernate core 定义的所有注解。
1 |
from t in Types where t . IsAnnotationClass && ! t . IsThirdParty select t |
定义了很多注解,让开发者轻松使用hibernate,避免了配置文件的烦恼。
结论
Hibernate Core 是值得学习的开源项目的一个很好的例子,不要犹豫,看看里面。