分享知识-快乐自己:Hibernate 关联映射
关联关系映射--概念:
关联关系是使用最多的一种关系,非常重要。在内存中反映为实体关系,映射到DB中为主外键关系。
实体间的关联,即对外键的维护。关联关系的发生,即对外键数据的改变。
外键:外面的主键,即,使用其它表的主键值作为自已的某字段的取值。
1) 基本概念:
关联属性:Java代码的实体类定义中,声明的另一个实例类类型或其集合类型的属性,称为关联属性。
关联关系维护:关联关系的维护,也称为外键维护,即为外键字段赋值。Hibernate默认情况下,关联的双方都具有维护权。即在代码中均可通过调用自己关联属性的set方法来建立关联关系。反映到数据库中,即是为外键字段赋值。
不过,由于外键是建立在多方表中的,所以对于外键的维护方式,即为外键字段赋值的方式,一方维护与多方维护,其底层执行是不同的。
若关联关系由一方维护,只能通过update语句来完成。若关联关系由多方维护,通过insert语句来完成。
虽然双方均具有维护权,但一方同时具有放弃维护权的能力。通过对一方关联属性inverse=“true”设置,即可放弃关联关系维护权,将维护权完全交给多方。
预处理语句:所谓预处理语句,即当前先不执行,等后面条件成熟,或程序运行完毕再执行的语句。当一方具有关联关系的维护权,并且执行save(一方对象)时,会产生一条update预处理语句,用于维护外键值。
当多方具有关联关系的维护权,并且执行save(多方对象)时,会产生一条insert预处理语句,用于维护外键值。
级联关系:当对某一类的对象a进行操作,如增加、删除、修改时,同时会自动对另一类的某对象b进行相同的操作。此时称,对象a、b具有级联关系,对象b为对象a的级联对象。
级联操作是通过映射文件的cascade属性设置的。
该属性的值较多,其介绍如下:
none:在保存、更新或删除当前对象时,忽略其他关联的对象,即不使用级联。它是默认值。
save-update:当通过Session的save()、update()、saveOrUpdate()方法来保存或更新当前对象时,将级联到其他DB中的相关联的表。
delete:当通过Session的delete()方法删除当前对象时,将级联删除所有关联的对象。
all:包含save-update及delete级联的所有行为。另外,当对当前对象执行lock()操作时,也会对所有关联的持久化对象执行lock()操作。
delete-orphan:删除所有和当前对象解除关联关系的对象。
all-delete-orphan:包含all和delete-orphan级联的所有行为。
关联方向:(1)单向关联:指具有关联关系的实体对象间的加载与访问关系是单向的。即,只有一个实体对象可以加载和访问对方,但对方是看不到另一方的。
(2)双向关联 :指具有关联关系的实体对象间的加载与访问关系是双向的。即,任何一方均可加载和访问另一方。
关联数量:实体对象间的关系,从数量上可以划分为:1:1,1:n,n:1,m:n
总结底层执行:
一对多关系中,一方维护关联关系,先插入多方数据,后插入一方数据,最后update多方表中的外键值(???是否正确);
多方维护关联关系,先插入一方数据,后插入多方数据,在插入多方数据的同时插入外键值;
多对多关系中,哪一方维护关联关系,就是哪一方数据先插入,再插入关联方数据,最后插入中间表数据。
建立(多对一、一对多)关系:
我们以用户和城市为例:
多对一:需要在 用户 User 实体类中定义一个 Address 类属性;
一对多:需要在 城市类中定义一个 Set<User> 集合元素属性;
User 用户类:
package com.mlq.bena; import java.io.Serializable; /** * 用户类 * @author asus */ public class User implements Serializable { private Integer uId; private String userName; private String userPwd; private String realName; private Address address; private Integer dId; @Override public String toString() { return "User{" + "uId=" + uId + ", userName='" + userName + '\'' + ", userPwd='" + userPwd + '\'' + ", realName='" + realName + '\'' + ", address=" + address + '}'; } public Integer getdId() { return dId; } public void setdId(Integer dId) { this.dId = dId; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public Integer getuId() { return uId; } public void setuId(Integer uId) { this.uId = uId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPwd() { return userPwd; } public void setUserPwd(String userPwd) { this.userPwd = userPwd; } public String getRealName() { return realName; } public void setRealName(String realName) { this.realName = realName; } }
Address 城市类:
package com.mlq.bena; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * 城市类 * @author asus */ public class Address implements Serializable { private Integer dId; private String addres; private Set<User> list; public Set<User> getList() { return list; } public void setList(Set<User> list) { this.list = list; } @Override public String toString() { return "Address{" + "dId=" + dId + ", addres='" + addres + '\'' + '}'; } public Address() { } public Integer getdId() { return dId; } public void setdId(Integer dId) { this.dId = dId; } public String getAddres() { return addres; } public void setAddres(String addres) { this.addres = addres; } }
user.hbm.xml 文件主要内容:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- package: 需要映射的实体类所在包 class节点: name:对应的是实体类的类名,如果没有书写package,则必须是完整限定类名 table:数据库中的表名,如果表名和实体类名一致,可以省略 id节点:表中的主键 generator:主键生成策略 ,主键由谁去创建?程序猿?Hibernate?数据库 name: 必须和实体类中的属性名一致 column : 必须和数据库中的列名一致,如果列名和属性名一致,可以省略 --> <!--dynamic-update="false":默认非动态更新--> <hibernate-mapping package="com.mlq.bena"> <class name="com.mlq.bena.User" table="`user`" dynamic-update="true"> <id name="uId" column="uId"> <!--<generator class="assigned"/><!–主键生成策略–>--> <generator class="increment"></generator> </id> <property name="userName" column="userName"/> <property name="userPwd" column="userPwd"/> <property name="realName" column="realName"/> <!--多对一:给User内置对象映射--> <many-to-one name="address" column="`dId`" class="com.mlq.bena.Address" /> </class> </hibernate-mapping> <!--Oracle序列增长--> <!--<generator class="squence">--> <!--<param name="squence">SEQ_DEPTNO</param>--> <!--</generator>--> <!--数据库中查询最大值+1--> <!--<generator class="increment"></generator>--> <!--没有制定使用那种方式:需要结合数据库方言自增--> <!--<generator class="native"></generator>--> <!--uuid:生成32位字符串--> <!--<generator class="uuid"></generator>-->
address.hbm.xml 主要配置内容:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- package: 需要映射的实体类所在包 class节点: name:对应的是实体类的类名,如果没有书写package,则必须是完整限定类名 table:数据库中的表名,如果表名和实体类名一致,可以省略 id节点:表中的主键 generator:主键生成策略 ,主键由谁去创建?程序猿?Hibernate?数据库 name: 必须和实体类中的属性名一致 column : 必须和数据库中的列名一致,如果列名和属性名一致,可以省略 --> <!--dynamic-update="false":默认非动态更新--> <hibernate-mapping package="com.mlq.bena"> <class name="com.mlq.bena.Address" table="`address`" dynamic-update="true"> <id name="dId" column="dId"> <generator class="increment"></generator> </id> <property name="addres" column="addres"/> <set name="list"> <key column="dId"></key> <one-to-many class="com.mlq.bena.User"/> </set> </class> </hibernate-mapping>
<many-to-one>元素建立了User表的外键 dId 和 address属性之间的映射。它包括以下属性:
1):name:设定持久化类的属性名,此处为User类的address属性。
2):column:设定持久化的属性对应的外键,此处为 user 表的外键 dId。
3):class:设定持久化类的属性的类型,此处设定address 为Address类型。
到此,User类到Address类的单向多对一映射就完成了。
<set>元素的name属性:设定持久化的属性名,此处为Address类的user 属性。
<set>元素:还包含两个子元素:
<key>元素:cloumn 属性设定与所关联的持久化类相对应的表的外键,此处为User 表的did
<one-to-many>元素:class属性设定所关联的持久化类型,此处为 User类
Hibernate 根据以上映射代码获得以下信息:
<set> 元素表明 Address 类的 list 属性为 java.uitl.Set 集合类型。
<one-to-many> 子元素表明 list 集合中存放的是一组 User 对象。
<key>子元素表明 User 表通过外键 dId参照 Address 表。
双向关联下的级联操作:cascade 属性:
级联(cascade):
当Hibernate持久化一个临时对象时,在默认情况下,它不会自动持久化所关联的其他临时对象,而是会抛出TransientObjectException。如果设定many-to-one元素的cascade属性为save-update的话,可实现自动持久化所关联的对象。
如:
<many-to-one name="customer" column="CUSTOMER_ID" class="..Customer" cascade="save-update" not-null="true" />
级联指的是当主控方执行操作时,关联对象(被动方)是否同步执行同一操作。
Inverse 属性:(维护主外键关系权)
inverse属性 :
inverse所描述的是对象之间关联关系的维护方式。
inverse只存在于集合标记的元素中 。
Hibernate提供的集合元素包括<set/> <map/> <list/> <array /> <bag />
Inverse属性的作用是:是否将对集合对象的修改反映到数据库中。
inverse属性的默认值为false,表示对集合对象的修改会被反映到数据库中;inverse=false 的为主动方,由主动方负责维护关联关系。
inverse=”true” 表示对集合对象的修改不会被反映到数据库中。
为了维持两个实体类(表)的关系,而添加的一些属性,该属性可能在两个实体类(表)或者在一个独立的表里面,这个要看这双方直接的对应关系了: 这里的维护指的是当主控放进行增删改查操作时,会同时对关联关系进行对应的更新。
一对多:
该属性在多的一方。应该在一方的设置 inverse=true ,多的一方设置 inverse=false(多的一方也可以不设置inverse属性,因为默认值是false),这说明关联关系由多的一方来维护。
如果要一方维护关 系,就会使在插入或是删除"一"方时去update"多"方的每一个与这个"一"的对象有关系的对象。
而如果让"多"方面维护关系时就不会有update 操作,因为关系就是在多方的对象中的,直指插入或是删除多方对象就行了。显然这样做的话,会减少很多操作,提高了效率。
注:
单向one-to-many关联关系中,不可以设置inverse="true",因为被控方的映射文件中没有主控方的信息。
多对多:
属性在独立表中。inverse属性的默认值为false。在多对多关联关系中,关系的两端 inverse不能都设为false,即默认的情况是不对的,如果都设为false,在做插入操作时会导致在关系表中插入两次关系。
也不能都设为 true,如果都设为true,任何操作都不会触发对关系表的操作。因此在任意一方设置inverse=true,另一方inverse=false。
一对一:
其实是一对多的一个特例,inverse 的设置也是一样的,主要还是看关联关系的属性在哪一方,这一方的inverse=false。
多对一:
也就是一对多的反过来,没什么区别。
例:
建立多对多关联关系:
多对多就是需要在两个实体类中都存在一个Set集合元素。同时需要一个中间表维护两者的关系。(共三张表)这里以老师和学生为例:
学生表:
中间表:
老师表:
实体类:Teacher
package com.mlq.bena; import java.io.Serializable; import java.util.HashSet; import java.util.List; import java.util.Set; public class Teacher implements Serializable{ private String name; private Integer tid; private Set<Student> students=new HashSet<Student>(); public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getTid() { return tid; } public void setTid(Integer tid) { this.tid = tid; } public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; } }
实体类:Student
package com.mlq.bena; import java.io.Serializable; import java.util.HashSet; import java.util.List; import java.util.Set; public class Student implements Serializable { private Integer sid; private String sname; private Integer age; private Set<Teacher> teachers = new HashSet<Teacher>(); public Integer getSid() { return sid; } public void setSid(Integer sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Set<Teacher> getTeachers() { return teachers; } public void setTeachers(Set<Teacher> teachers) { this.teachers = teachers; } }
提示只需要两张表的实体类就可以:中间表不需要。
核心映射文件:Student.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- package: 需要映射的实体类所在包 class节点: name:对应的是实体类的类名,如果没有书写package,则必须是完整限定类名 table:数据库中的表名,如果表名和实体类名一致,可以省略 id节点:表中的主键 generator:主键生成策略 ,主键由谁去创建?程序猿?Hibernate?数据库 name: 必须和实体类中的属性名一致 column : 必须和数据库中的列名一致,如果列名和属性名一致,可以省略 --> <!--dynamic-update="false":默认非动态更新--> <hibernate-mapping package="com.mlq.bena"> <class name="com.mlq.bena.Student" table="`student`" dynamic-update="true"> <id name="sid" column="sid"> <generator class="increment"></generator> </id> <property name="sname" column="sname"/> <property name="age" column="age"/> <!--cascade级联操作--> <!--inverse:默认false,执行外键的update验证,true:不执行update维护外键--> <set name="teachers" table="middle" cascade="save-update,delete" inverse="true"> <key column="sid"></key> <many-to-many column="tid" class="com.mlq.bena.Teacher"/> </set> </class> </hibernate-mapping>
核心映射文件:Teacher.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- package: 需要映射的实体类所在包 class节点: name:对应的是实体类的类名,如果没有书写package,则必须是完整限定类名 table:数据库中的表名,如果表名和实体类名一致,可以省略 id节点:表中的主键 generator:主键生成策略 ,主键由谁去创建?程序猿?Hibernate?数据库 name: 必须和实体类中的属性名一致 column : 必须和数据库中的列名一致,如果列名和属性名一致,可以省略 --> <!--dynamic-update="false":默认非动态更新--> <hibernate-mapping package="com.mlq.bena"> <class name="com.mlq.bena.Teacher" table="`teacher`" dynamic-update="true"> <id name="tid" column="tid"> <!--<generator class="assigned"/><!–主键生成策略–>--> <generator class="increment"></generator> </id> <property name="name" column="name"/> <!--cascade级联操作--> <!--inverse:默认false,执行外键的update验证,true:不执行update维护外键--> <set name="students" table="middle" cascade="save-update,delete" inverse="false"> <key column="tid"></key> <many-to-many class="com.mlq.bena.Student" column="sid"/> </set> </class> </hibernate-mapping>
注意:虽然中间表不需要实体类,但是在我们配置文件时,要写上 table 属性,来告诉它这是多对多关联。table 属性值就是中间表。
注意关联的字段名称保持一致。
Lazy 属性:延迟加载:
一.延迟加载的概念
当Hibernate从数据库中加载某个对象时,不加载关联的对象,而只是生成了代理对象,获取使用session中的load的方法(在没有改变lazy属性为false的情况下)获取到的也是代理对象,所以在上面这几种场景下就是延迟加载。
二.理解立即加载的概念
当Hibernate从数据库中加载某个对象时,加载关联的对象,生成的实际对象,获取使用session中的get的方法获取到的是实际对象。
三.为什么要使用延迟加载
延迟加载策略能避免加载应用程序不需要访问的关联对象,以提高应用程序的性能。
四.立即加载的缺点
Hibernate在查询某个对象时,立即查询与之关联的对象,我们可以看出这种加载策略存在两大不足:
1.select的语句数目太多,需要频繁的访问数据库,会影响查询的性能。
2.在应用程序只需要访问要的对象,而不需要访问与他关联的对象的场景下,加载与之关联的对象完全是多余的操作,这些多余的操作是会占内存,这就造成了内存空间的浪费。
五.什么时候使用延迟加载什么时候使用立即加载
如果程序加载一个持久化对象的目的是为访问他的属性,则可以采用立即加载。如果程序加载一个持久化对象的目的仅仅是为了获得他的引用,则可以采用延迟加载。
六.Hibernate在对象-关系映射问价中配置加载策略
I.类级别:
<class>元素中lazy属性的可选值为 true (延迟加载)和 false (立即加载);
<class>元素中的lazy属性的默认值为true
II.一对多关联级别:
<set>元素中的lazy属性的可选值为:
true(延迟加载)
extra(增强延迟加载)
false(立即加载);
<set>元素中的lazy属性的默认值为true
III.多对一关联级别:
<many-to-one>元素中lazy属性的可选值为:
proxy(延迟加载)
no-proxy(无代理延迟加载)
false(立即加载)
<many-to-one>元素中的lazy属性的默认值为proxy
Open Session in View 的使用:
为什么要使用 Open Session in View ?
因为:默认是懒加载机制,当我们去操作查询出来的数据时,会给我们报 会话已断开。(例如调用一个类中关联的 类属性)
解决方式:延迟会话关闭时间。(调用完在关闭)增加过滤器拦截请求。(把之前在Session中关闭会话的代码 删掉就 OK 了)
配置如下:
Web.XML 中添加以下配置:
<!--OpenSessionInVie会话配置-->
<filter>
<filter-name>openSessionInview</filter-name>
<filter-class>com.mlq.uitl.Fileter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInview</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Fileter 自定义拦截器类:
package com.mlq.uitl; import org.hibernate.HibernateException; import org.hibernate.Transaction; import javax.servlet.*; import java.io.IOException; public class Fileter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("初始化------"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Transaction transaction=null; try{ //请求达到时,打开Session并启动事务 transaction=SessionTool.getSession().beginTransaction(); //执行请求处理链 chain.doFilter(request,response); //返回响应时,提交事务 transaction.commit(); }catch (HibernateException ex) { System.out.println("=======异常"); ex.printStackTrace(); transaction.rollback(); } } @Override public void destroy() { System.out.println("销毁--------------"); } }