一点点学习Hibernate3.6 -关联关系映射
关系总体分为三种:一对多,多对多,一对一.在配置映射文件时, 需要选择与保存对象相应的集合标签.
在一对多的关系中,往往只需要一方维护两者的关系,因为在关系型数据库中,只需要有一方存储关系就行了,称为外键,外键列保存在多的一方即可,对于对象来说,双方都设置关联才比较好,这样双方才都知道与对方关联,但是对于数据库来说,只需要一方设置有关联就可以了,不管哪一方,只进行设置,默认就会生成一条update语句来更新外键的值,以保存关联关系(维护关联关系),如果关联一个没有保存的对象(有效的关联),会抛出一个TransientObjectException异常.不管在哪一方都能找到另一方,称为双向关联,单项关联就是指仅仅从某一方才能找到另一方,而另一方不能找到某一方,因为在另一方的数据库中没有维护关系,自然就找不到.关联关系中重要的属性:
- inverse:是否只由对方维护关联关系,默认值为false.对于一对多,维护关联关系是指更新外键列的值,对于多对多,维护关联关系是指在中间表中插入或删除记录,这个属性只在表示关联关系的集合映射中用(一对多、多对多)cascade:级联操作,即对从对象也做相应的操作,级联风格有:save-update, delete, none, all, delete-orphan, all-delete-orphan, ...,默认值为none.只要是关联关系,都可以配这个属性(一对多、多对一、多对多、一对一)order-by:指定排序子句(order by子句,是指定的sql语句,所以要写列名,这个属性可以在所有的无序集合映射中使用(<set>中可以用,<list>中不可以用),模拟一的一方:
<!--?????不用指定表名,因为根据类,可以自动找到映射文件,映射文件里有描述表的结构????cascade="all":将一个关联关系(无论是对值对象的关联,或者对一个集合的关联)????标记为父/子关系的关联。?????这样对父对象进行?save/update/delete?操作就会导致子对象也进行?save/update/delete?操作
??? lazy:默认值为true,开启懒加载.属性interests是set集合的引用,这个集合里面的对象存在数据库中,是person对象的子对象,
??? 如果开启懒加载,且没有在session关闭之前访问interests的成员,则关闭session之后就获取不到了,访问将抛LazyInitializationException异常.?-->?<set?name="interests"?cascade="all">??????<key?column="personID"></key>??????<one-to-many?class="..domain.Interest"/>?</set>
多的一方:
<!--??????很贴切的标签????name:指定对应的属性????column:指明外键列的列名--><many-to-one?class="..domain.Person"?name="person"?column="personID"></many-to-one>????假设我有一个员工表,和一个部门表,我有15个员工和5个部门,它们的关系在数据库中表示为员工表的外键列,在session开启之后获取到部门的持久对象,也将获取和它关联的员工,在session关闭之前所做的任何修改都将直接同步到数据库中.如果要解除两者的关联关系:
//?解除关联关系@Testpublic?void?testRemoveRelation()?{????Session?session?=?sessionFactory.openSession();????Transaction?tx?=?session.beginTransaction();????//?--------------------------?????//?Employee?employee?=?(Employee)?session.get(Employee.class,?15);????//?employee.setDepartment(null);?????Department?department?=?(Department)?session.get(Department.class,?5);????//?department.getEmployees().remove(obj);?//?与指定的员工解除关联关系????//?department.getEmployees().clear();?//?与关联的所有员工解除关联关系????//?department.setEmployees(null);?//?与关联的所有员工解除关联关系????department.setEmployees(new?HashSet<Employee>());?//?与关联的所有员工解除关联关系?????//?--------------------------????tx.commit();????session.close();}删除部门时,如果有和它关联的员工,则可能会有约束异常,必须先把与员工的关系解除或者连员工一起删除,需要设置cascade属性,然后才能进行:
@Testpublic?void?testDelete()?{????Session?session?=?sessionFactory.openSession();????Transaction?tx?=?session.beginTransaction();????//?--------------------------?????/**?删除部门时,如果有员工引用这个部门的id,则:????????????a,如果inverse=true,即不能维护关联关系,就会?抛异常:?Cannot?delete?or?update?a?parent?row:?a?foreign?key?constraint?fails?????????????b,如果inverse=false,即可以维护关联关系,则Hibernate会先取消与所有员工的关联,再删除部门,不会有异常。???????????????先获取持久对象,用get()方法或者load()方法,或者自己模拟一下:????????Department?department?=?new?Department();????????department.setId(4);????????只要OID对应即可.????*/????Department?department?=?(Department)?session.get(Department.class,?4);????session.delete(department);????//department.getEmployees().clear();?//?解除与所有关联员工的关联关系?????//?--------------------------????tx.commit();????session.close();}
多对多的关联关系比多对一的关联关系更好理解一些,通常在数据库中有张中间表来描述:

在Hibernate中默认双方都能维护关联关系,但双方都设置关联关系会有异常:java.sql.BatchUpdateException.因为中间表通常以两个外键作为联合主键,在程序中,由于是多对多的关系,双方维护的对象都是一样的,比如:

从任何一方都能关联,任何一方都清楚的展示了对方的属性,任何一方的对象里面都维护着另一方的对象(双向关联),这样一来,在持久化对象的时候,就会在中间表中出现重复记录,当然是不允许的.所以一般做法为:只关联一方或者在某一方设置inverse=”true”即可.多对多是关联关系里面用的最多的:
<!--?多对多映射(employees?:?Set<Employee>?属性)????????<key?column="..."/>是配置集合外键,即引用当前对象表的主键的那个外键???????????????????inverse="true"表示放弃维护关联关系(由对方维护)。在多对多中,维护关联关系是指的在中间表中插入一条数据?--><set?name="employees"?table="emmployee_role"?inverse="true">????<key?column="roleId"></key>????<many-to-many?class="Employee"?column="employeeId"></many-to-many></set>
一对一.一对一虽然不难,理解起来比多对多稍微麻烦一点,假设一个部门只能有一个员工,一个员工也只能有一个部门.一对一有两种映射方式:
基于主键(推荐)

这是一种万能方式,因为employee表中的主键是id,属于代理主键,如果开发部被删除了,zhang将不会被删除,因为department_id可能设置允许为null.
有外键的表的映射文件(employee):
<!--????这里用到了多对一的标签,但是这里并不是指的多对一,从unique属性就能看出来这一列的记录不能重复,那????为什么要用这个标签而不是one-to-one呢?因为这张表需要一个保存其他表数据的外键列,而one-to-one????标签不会生成一个外键列,所以需要用到many-to-one标签,并且给它设置unique属性让它唯一,因而也可????以认为一对一只是多对多的一种特例.给它加上cascade属性是为了更方便的管理对象,如果子对象没有持久????化,也自动给它持久化--><many-to-one??? cascade="all"???????????????? unique="true"?name="department"???????????????? class="..domain.Department"???????????????? column="departmentID"></many-to-one>无外键表的映射文件(department):
<!--???????表中不需要维护两个相同的记录,需要获取时一并获取即可,无外键方,使用<one-to-one>?????property-ref:默认与被关联的实体的主键相关联,有了property-ref属性,??????????????????就可以通过它与被指定的实体主键以外的字段相关联?-->??<one-to-one?name="employee"?property-ref="department"?class="..domain.Employee"></one-to-one>基于外键

在这种方式下,employee表中的id既当主键又当外键,如果开发部被删除了,zhang也要被删除,因为主键不能为null,与其他对象有多个一对一关系时,如果外键设计在当前表中,则最多只能有一个基于主键的一对一映射.有外键方的对象需要独立存在而不与对方的某一条数据关联时,不能使用基于主键的一对一映射.除非保证employee表中的id始终有值对应.除了这些以外,还需要在两者之间寻找主从关系,比如人是必须存在的,身份证需要依赖人,人就是主,身份证就是从.
主:
?
<!--????????只需要描述自己的idCard属性所对应的对象即可?????? Hibernate会自动找到对应的映射文件-->?<one-to-one?name="idCard"?class="..domain.IdCard"></one-to-one>??
?
从:
?
<!--????? 因为自己的主键也是外键,一张表中可以存在多个外键,????? 所以必须说明自己的主键是参照哪个外键--><id?name="id">????<generator?class="foreign">????????<param?name="property">person</param>????</generator></id>?
. . .
<!--????? constrained:是否加上外键约束,默认值为false--><one-to-one?name="person"?class="..domain.Person"?constrained="true"></one-to-one>
?
不管关联关系是什么,不是谁都能维护关联关系,关于谁才能维护关联关系:
一对多:
??????????? 默认双方都能维护
??????????? 在一方可以通过设置inverse="true"来放弃维护关联关
??????????? 在多方(有外键方)始终能维护关联关系,因为外键在自己表中
多对多:
??????????? 默认双方都能维护
??????????? 在任何一方都可以通过设置inverse="true"来放弃维护关联关系。注意只需设置一边就可以
一对一:
??????????? 不管是采基于外键的还是基于主键的一对一映射,都是只有有外键方可以维护关联关系,没有外键方不可以维护。而且不可以配置
??????????? 当采用基于主键的一对一映射时,双方都不能解除关联关系,因为主键值为能为null
单向关联与上述的一致,能维护关系的一方才可以做单向关联到对方.
?
继承映射
映射继承结构的时候,一般是整个继承结构使用一个映射文件,名称前缀为超类的名,继承映射有三种方式,全部以论坛的帖子为例:

方式一:
一个继承结构只有一张表,同时只有一个映射文件,那它的子类怎么办?用<subclass>标签配置它的子类.另外分析下,因为是一张表,表中要有所有的字段,但是主题的帖子肯定不需要floor(楼层),只有回复表才需要标注自己是几楼,同理,主题的extra(如精华、置顶...)也是主题表特有的.那么如何判断帖子是什么类型呢?判断帖子有extra,则是主题,有floor,则是回复等,所有可能情况如下:

前两条记录还好说,第三条怎么算呢?可能是一个没有extra的主贴,也有可能是一个没有标注楼层的回复,总之,从设计表的结构上就要把这种可能性排除,所以采用中表中加入一个type来指定具体的类型.那么,如果把类型在添加记录的时候就加入进来呢?不同类型的对象添加的时候自然就是不一样的值,比如主题就是type=topic,回复就是type=reply.如下:
<hibernate-mapping?package="..mapping">????<!--?一个继承结构一张表的映射方式:????????需要有一个额外的列用于保存类型标志。????????每个类都需要指定discriminator-value属性,表示代表当前类型的标志。如果没有指定,默认为当前类的全限定名。????????父类与子类只需要映射自己的属性就可以了。?????-->????<class?name="Article"?table="Article"?discriminator-value="Article">????????<id?name="id">????????????<generator?class="identity"></generator>????????</id>????????<!--?指定用于辨别是什么类型标志列?必须放在property标签之前-->????????<discriminator?column="type_type="string"></discriminator>?????????<property?name="title"></property>????????<property?name="content"></property>?????????<!--?子类:Topic?-->????????<subclass?name="Topic"?discriminator-value="Topic">????????????<property?name="extra"></property>????????</subclass>?????????<!--?子类:Reply?-->????????<subclass?name="Reply"?>????????????<property?name="floor"></property>????????</subclass>????</class></hibernate-mapping>
方式二:
每个类一张表,抽象类也有表(<joined-subclass>),子类表与父类表是一对一的关系,在映射时,每个类都只映射自己特有的属性,公共的属性值存放在父类表中,父类表的主键和子类表的主键相关联,不相关的子类表,不添加记录:
<hibernate-mapping?package="..mapping">????<!--?每个类一张表????????子类表与父类表是一对一的关系????????在映射时,每个类都只映射自己特有的属性?????-->????<class?name="Article"?table="Article">????????<id?name="id">????????????<generator?class="identity"></generator>????????</id>????????<property?name="title"></property>????????<property?name="content"></property>?????????<!--?子类:Topic?????????????需要指定表名。????????????需要指定<key?column="id"></key>,表示自己的主键,且是外键引用父类表的主键(一对一的关系)????????-->????????<joined-subclass?name="Topic"?table="Topic">????????????<key?column="id"></key>????????????<property?name="type"></property>????????</joined-subclass>?????????<!--?子类:Reply?-->????????<joined-subclass?name="Reply"?table="Reply">????????????<key?column="id"></key>????????????<property?name="floor"></property>????????</joined-subclass>????</class></hibernate-mapping>??方式三:?每个非抽象类一张表(<union-subclass>),只要不是抽象类,就对应一张表.每个表中都有全部属性的列,包括从父类继承过来的属性.如果父类是抽像的,也会给父类建张表,但是永远是空表,所以要在<class>中加上属性abstract="true",这样就不会为父类建表了.主键生成策略不能使用identity,不然每个表主键都从1开始递增,获取持久对象的时候,指定的OID不唯一,因为在一个继承结构中不能出现有相同主键值的两个不同类型的数据,否则在使用父类查询时就会查出多条记录,这样是不可以的,只要有可能造成这个问题的主键生成策略都不可以用.?<hibernate-mapping?package="..mapping">????<class?name="Article"?table="itcast_Article">????????<id?name="id">????????????<generator?class="hilo">??????????????<param?name="table">hi_value</param>??????????????<param?name="column">next_value</param>??????????????<param?name="max_lo">100</param>????????????</generator>????????</id>????????<property?name="title"></property>????????<property?name="content"></property>?????????<!--?子类:Topic?????????????需要指定表名。????????-->????????<union-subclass?name="Topic"?table="topic">????????????<property?name="type"></property>????????</union-subclass>?????????<!--?子类:Reply?-->????????<union-subclass?name="Reply"?table="reply">????????????<property?name="floor"></property>????????</union-subclass>????</class></hibernate-mapping>?关于Hibernate的查询,有多种方式:
- 根据OID查询(get()与load())导航对象图检索方式:? 根据已经加载的对象导航到其他对象HQL(Hibernate Query Language)QBC(Query By Criteria)
HQL
?
命名查询
Query query = session.getNamedQuery("queryAllEmployees");List list = query.setParameter(0, 5).list();
对应的配置文件:
<!-- 定义命句查询语句 -->
<query name="queryAllEmployees">
<!--[CDATA[FROM Employee WHERE id>?]]-->
</query>
如果执行一条查询,查询出了多个结果,则会抛NonUniqueResultException异常.