前段时间所做的权限管理项目,我主要负责基本信息部分的内容,对于基本信息这里,值得一提的就是关系表的维护,我这个关系表比一般的关系表要"另类"一些,另类在哪里,请继续往下看。
我们大家都知道Hibernate可以利用映射关系来自动维护表之间(多对多)的中间关系表,但是有事也会有些麻烦,比如看下图的关系,三个主体表两两之间都有联系,如果用hibernate来维护就需要生成三张表,虽然这么做很简单,但是却暗藏“隐患”。
注:
其中User,Role和Group是主体表,User和Role,User和Group,Group和Role都是是多对多的关系,而这三个关系都由Relation这张关系表来进行维护。
为了更清晰和方便对关系表的维护,我们将Relation这样数据表映射成了三个映射文件(对应的也就是三个po实体对象):UserRole.hbm.xml,UserGroup.hbm.xml,GroupRole.hbm.xml,通过映射文件可以很清晰的看到表之间的结构关系。
我们展示一下各个实体间的关系,po实体对象有User,Role,Group,UserRole,UserGroup,GroupRole(每个po都有一个对应的映射文件):
由于本项目是权限管理,它是为其它系统服务的,所以对它的性能要求就比较高,如果生成三张关系表则会增加维护量,而且对于日后的统计工作也会造成麻烦。
鉴于此种情况,我们采用了使用一张关系表来维护三个主体两两之间的关系,以便减少日后的维护和统计工作。
最初我们采用hibernate自动维护这张关系表。
最初的映射文件,多对多的映射关系是双向维护的,我们可以看下映射文件,以UserRole为例:
先来看User.hbm.xml文件,Role.hbm.xml类似:
<hibernate-mapping>
<classname="com.core.permission.po.User" table="T_USER">
<id name="id"type="java.lang.Long">
<column name="ID"/>
<generatorclass="sequence">
<paramname="sequence">NMS_SEQ_MAINBODY_ID</param>
</generator>
</id>
<property name="username"type="java.lang.String">
<columnname="USERNAME" />
</property>
<property name="password"type="java.lang.String">
<columnname="PASSWORD" />
</property>
<propertyname="authorizeType" type="java.lang.String">
<columnname="AUTHORIZE_TYPE" />
</property>
<propertyname="extendsType" type="java.lang.String">
<columnname="EXTENDS_TYPE"/>
</property>
<set name="userRole"table="T_RELATION" inverse="false"lazy="true">
<key>
<columnname="MAIN_BODY_ID" not-null="false"/>
</key>
<one-to-manyclass="com.core.permission.po.UserRole" />
</set>
<set name="userGroup"table="T_RELATION" inverse="false"lazy="true">
<key>
<columnname="MAIN_BODY_ID" not-null="false"/>
</key>
<one-to-manyclass="com.core.permission.po.UserGroup" />
</set>
</class>
</hibernate-mapping>
再来看下UserRole.hbm.xml文件:
<hibernate-mapping>
<classname="com.core.permission.po.UserRole"table="T_RELATION">
<id name="id"type="java.lang.Long">
<column name="ID"/>
<generatorclass="sequence">
<paramname="sequence">NMS_SEQ_REL_ID</param>
</generator>
</id>
<propertyname="mainBodyType" type="java.lang.String">
<columnname="MAIN_BODY_TYPE"/>
</property>
<propertyname="relMainBodyType" type="java.lang.String">
<columnname="REL_MAIN_BODY_TYPE"/>
</property>
<many-to-one name="user"class="com.core.permission.po.User" fetch="join">
<columnname="MAIN_BODY_ID" not-null="false"/>
</many-to-one>
<many-to-one name="role"class="com.core.permission.po.Role" fetch="join">
<columnname="REL_MAIN_BODY_ID" not-null="false" />
</many-to-one>
</class>
</hibernate-mapping>
其它的映射文件类似,启动服务器生成数据表,从下图我们可以看到,这种配置使得各个主体表各自生成了两个外键关联,并且关系表生成了四个外键,主体id字段有两个外键(关联User和Group),关系主体id字段有两个外键(关联Group和Role),所以t_relation这张表共有三个表的外键。
但是t_relation的每条记录只关系到了两个外键,所以在插入数据时比如插入UserRole的关系时,会报错说Group这个外键没有赋值。这个错误原因是因为数据库的约束完整性导致的。
错误:ORA-02291:违反完整约束条件 (T_RELATION.FK3B183987CA1B323F) - 未找到父项关键字
如何解决这个数据库完整约束的问题呢?
对于该问题的解决方案我采用了“hibernate与数据库不对应”的策略,之所以叫不对应策略,不是数据的不对应而是外键的不对应,hibernate继续维护映射文件间的关系,但是在数据库中要将关系表的外键给禁用掉,即需要手动维护关系表。
除此之外还有要把多对多的映射关系改为由关系表一端进行单向维护(因为如果是主体那边也设立维护的话,由于主体生成了外键引用,所以对主体表进行操作时hibernate会自动的去更改关系表,造成数据不一致)。
1.禁用t_relation关系表外键(在外键上右击禁用(disabled)即可)
2.更改主体映射文件(以User.hbm.xml为例),去掉set标签,关系映射文件不变:
<hibernate-mapping>
<classname="com.core.permission.po.User" table="T_USER">
<id name="id"type="java.lang.Long">
<column name="ID"/>
<generatorclass="sequence">
<paramname="sequence">NMS_SEQ_MAINBODY_ID</param>
</generator>
</id>
<property name="username"type="java.lang.String">
<columnname="USERNAME" />
</property>
<property name="password"type="java.lang.String">
<columnname="PASSWORD" />
</property>
<propertyname="authorizeType" type="java.lang.String">
<columnname="AUTHORIZE_TYPE" />
</property>
<propertyname="extendsType" type="java.lang.String">
<columnname="EXTENDS_TYPE"/>
</property>
</class>
</hibernate-mapping>
这样的最终结果是hibernate保持各个实体间的外键关系,这样有利于查询,比如查询UserRole关系表记录的时候可以抓取到两端的主体User和Role对象(hibernate本身的get和load查询可自动抓取,如果使用hql查询则需要手动fetch两端对象),不需要再手动的通过关系表中的两个外键id再去分别查询对应的主体表。
没有了外键约束,所以我们要手动对维护关系表,手动维护关系表最重要就是要保证数据的一致性,即你插入到关系表的数据要保证在对应的主体表中存在相应的记录(具体方法就是从主体表中取出记录插入到关系表或者插入之前再从主体表中查询是否存在)。
小结:
1.将三张关系表用一张关系表来维护,可以方便维护,统计等操作。
2.将一张表映射为三个关系实体,可以明确对象之间的关系,还可以充分利用hibernate的抓取操作,方便和简化查询。
3.禁用关系表外键,方便操作。
禁用关系表外键,手动维护关系表,可以轻松的插入关系表记录还不影响hibernate的操作。
4.使用单向维护,可以保持数据的完整性。
举例,如果user端还维护着与UserRole的关系,这样在更新user的时候,hibernate会发出更新语句同时更新关系表有该userId的记录,但是关系表并没有与主体关联,这样更新的结果就是该UserRole的记录的roleId变为了空,导致数据不一致。
当然,对于关系表的维护还可以有多种做法,没有哪一种做法做好,只有最合适,如果大家有什么别的想法和建议,请留言,我们共同交流。