开源框架对数据层的映射包括实体的映射和关系的映射,其中关系的映射是最复杂的。如果你掌握不好关系映射,你干脆就不要用,否则会严重地影响性能。
Hibernate中的实体关系分为四种,基本上与我们在数据库建模中了解到的实体关系是一样的,即一对一关系、一对多关系、多对一关系、多对多关系。我们下面分别给出说明:
一、一对一关系、多对一关系
一对一、多对一关系在代码上的体现是在JavaBean中包含一个实体类属性。比如,夫妻关系是一对一的关系,那么丈夫的属性中就应该有一个属性是妻子,妻子的属性中也应该有一个属性是丈夫。同样,多对一的关系中,在代码上的体现也是在Java中包含一个实体类属性。比如,孩子与妈妈的关系就是多对一的关系,每一个孩子都应该有一个属性是妈妈。我们发现,无论是一对一,还是多对一,它们在代码是都是一样的,就是属性中包含一个实体类属性。而事实上,一对一关系是多对一关系的一种特例而已。所以,在映射时,由外键实现的一对一关系或多对一关系时,无论是哪一种,外键所在一方关系属性都是通过many-to-one映射的,而不是用one-to-one。
二、一对多关系、多对多关系
这两种关系也有些共性,就是它们在代码上的体现都是在JavaBean中包含一个集合属性。比如在上面说的妈妈与孩子的关系,妈妈应该包含一个集体属性,在这个集合中包含了她所有的小孩。这种一对多关系使用one-to-many来映射,在数据库中的体现是在一的一方是不含有外键的。而多对多的关系虽然也是在属性中包含一个集合属性,但在映射时使用的却是many-to-many。这主要是因为多对多关系需要一个关系表,必须告诉Hibernate这个关系表是谁。
以上只是简单的概括了一下Hibernate中一些关系映射的特点,下面来说说Hibernate关系映射中的难点问题。
如果不是真正使用Hibernate开发过项目,恐怕很难理解为什么说如果掌握不好关系最好不要使用关系。这里面有这样几个问题:
1、关系的级联操作问题。
我们知道,在数据库中,建立外键的同时还要建立约束关系。这就存在一个问题,即当外键所指向表的行被删除时,当前行应该如何操作。比如,丈夫表含有一个外键指向妻子,如果妻子被删除了,那么丈夫所在行的外键的值应该如何操作?如果保持原有值不变,但他所指向的妻子却已经不在了,这样的记录就没有意义了,所以必须要采取一些行为。在数据库中一般会根据用户的设置采取三种行为,一是给出提示,告诉你因为有外键指向这个行,所以不能删,如果非要删除,必须先将指向它的外键却除;二是在删除当前行后,自动将指向它的外键置为空;三是删除当前行后,将所有指向它的行也同时删除。
一般数据库默认情况下都采取第一种行为,这样如果不做任何设置,当我们对一个实体进行删除时,就会报错,告诉你有外键指向不能删除。
这种问题如何解决呢?你可以事先做好数据库设计,让数据库帮你采取一种更合适的行为。两种可选的确定行为,即置空或级联删除。究竟采用哪一种更合适,要视你的应用而定,限于篇幅,我这里就不做过多的讲解了。再者,可以使用Hibernate的cascade属性,它的delete代表删除时置空外键,而delete-orphan则代表删除时同时删除指向它的行。
2、关系的方向性问题
“一个巴掌拍不响”,一个关系也一定有两个实体。这就存在了另外一个问题,当一个实体发生变化时,关系是不是也一定要跟着变化?这种问题很难说清,因为它跟具体的应用关联。有时,我们只要更新实体,而不想更新关系;而有时我们又想更新关系而不更新实体;还有些情况下,我们是实体和关系同时都要更新。在默认情况下,Hibernate对于实体和关系是同时更新的,即使你根本没有更改过关系。这对于性能的影响比较大。我们可以给关系设置一个inverse属性,告诉它在任何变化下,都不要更新关系。当然还有其它的办法,读者可以参考其文档。总之,这是一个非常复杂的问题,要视你的应用而定。
3、N+1查询问题
关于什么是N+1查询,我不想解释,读者可以看一下我前面的文章或者到网上去查询。总的来说N+1查询的问题就是性能太低,在有些情况下甚至会导致系统崩溃。但有些时候它又是有益的。因为N+1查询实际上是延迟加载了,它节省了空间。Hibernate有一个fetch属性,用于说明抓取数据的策略,如果选择了join则不会使用N+1查询,但加载上来了所有的数据并不一定都是你想要的,也可能会浪费存储空间。
4、延迟加载
延迟加载就是并不是在读取的时候就把数据加载进来,而是等到使用时再加载。那么Hibernate是怎么知识用户在什么时候使用数据了呢?又是如何加载数据呢?其实很简单,它使用了代理机制。返回给用户的并不是实体本身,而是实体对象的代理。代理对象在用户调用getter方法时就会去数据库加载数据。但加载数据就需要数据库连接。而当我们把会话关闭时,数据库连接就同时关闭了。这种情况就叫做未初始化的关系。
延迟加载的好处就是节省了存储空间。因为我们并不是在所有情况下都需要关系数据。比如,妈妈和孩子。如果你只想修改妈妈的数据,而Hibernate将她10几个孩子也同时给你加载进来了,这显然是无意义的。所以你可以使用Hibernate.initialize()方法主动地去决定是否初始化关系。当然也可以在配置文件中通过lazy属性,但这样一来就固定了,要么延迟,要么不延迟。
Hibernate的关系还有很多问题,这里限于篇幅先讲这么多。还是开头的那句话,如果你没有掌握好Hibernate中关系的映射,那你干脆就不要用了,否则严重地影响性能。