延迟加载与即时加载
例如Person类和Email类是一对多关系,如果设为即时加载,当加载Person时,会自动加载Email,如果设置为延迟加载,当第一次调用person.getEmails()时才会执行SQL语句加载Email
注解配置时,@OnetToMany(Fetch = FetchType.EAGER)为即时加载,Fetch = FetchType.LAZY为延迟加载
延迟加载和即时加载的策略适用于所有一对多、多对一、多对多等所有的实体关系
一般来说,延迟加载要比即时加载节省资源,但是如果处理不当,延迟加载容易抛出LazyInitializationException异常,解决当方法有两种,一种是在Session关闭之前调用一下,person.getEmails()方法,强迫Hibernate加载数据,这是最常用的方式,还有一种是延迟Session的范围,使Session关闭前完成所有的业务逻辑,在web程序中有些工具也能延长Session的范围,例如Spring的OpenSessionInViewFilter,能把Session扩大到所有的web资源,包括Servlet层、JSP层、DAO层、Service层
单边一对多
一方有集合属性,包含多个多方,而多方没有一方的引用
import javax.persistence.*; //“一"方 @Entity @Table(name="tb_person") public class Person{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; @OneToMany(fetch = FetchType.EAGER,targetEntity = Email.class,cascade={ CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH }) @JoinColumns(value= {@JoinColumn(name="person_id",referencedColumnName="id")}) @OrderBy(value = “email desc") private List<Email> emails = new ArrayList<Email>(); //在email对应的表中产生一个person_id字段,引用person标的id // referencedColumnName指的是实体类的关联列,默认为主键,可省略 //setter、getter略 } //“多"方 @Entity @Table(name="tb_email") public class Email{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String email; //setter、getter略 }<hibernate-mapping package="com.clf.hibernate.bean"> <class name="Person" table="tb_person"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> <bag name="emails" cascade="all" lazy="false" where="email like %@%" order-by="email"> <key column="email_id"></key> <one-to-many class="com.clf.hibernate.bean.Email" /> </bag> </class> </ hibernate-mapping > <hibernate-mapping package="com.clf.hibernate.bean"> <class name="Email" table="tb_eamil"> <id name="id" column="id"> <generator class="native"> </id> <property name="email"/> </class> </ hibernate-mapping >如果集合属性使用的是Set而非List,则XML配置时需要使用<set>标签而不是<bag>标签,而且不能使用order-by属性
JPA(Java Persistence API)要求实体类必须为POJO,而不能为String等基本类型,换句话说,电子邮件必须封装为Email对象,即使它只有一个String属性
而XML配置允许直接使用String作为实体类,而不需要封装成一个Email对象
配置改动如下
<bag name="emails" cascade="all" lazy="false" where="email like %@%" order-by="email"> <key column="email_id"></key> <element type="String" column="email" /> </bag>单边多对一
//“一"方 @Entity @Table(name = "tb_table") public class Type{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column(unique = true) private String name; //setter、getter方法略 } //“多"方 @Entity @Table(name = <span style="font-family: Arial, Helvetica, sans-serif;">"</span><span style="font-family: Arial, Helvetica, sans-serif;">tb_article")</span> public class Article{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @ManyToOne(cascade = {CascadeType.PERSIST},fetch = FetchType.EAGER) @JoinColumn(name = "type_id") Private Type type; //在article表总产生一个type_id字段,引用type表中的id字段 @Column(columnDefinition = "text") private String content; private String name; //setter、getter方法略 }<class name="Type" table="tb_type"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> </class> <class name="Article" table="tb_article"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> <property name="content"/> <many-to-one name = "type" column = "type_id" cascade = "persist" lazy="false" not-found="ignore"> </ many-to-one > </class>双边多对一、一对多
//“一"方 @Entity @Table(name = "tb_class") public class Clazz{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String name; //使用反向配置(@ManyToOne不存在该属性),双边关系中,控制权一般交给多方,定义在被拥有方,指向拥有方,拥有方能够级联地维护被拥有方的关系。因此这里没有配置数据库的外键,而只配置了一个mappedBy属性,告诉Hibernate,配置信息要到Student类的“clazz"属性中找 @OneToMany(mappedBy = "clazz") private List<Student> students; //setter、getter方法略 } //“多"方 @Entity @Table(name = "tb_student") public class Student{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @ManyToOne(cascade = {CascadeType.PERSIST},fetch = FetchType.EAGER) @JoinColumn(name = "class_id") Private Clazz clazz; //在student表总产生一个class_id字段,引用class表中的id字段 private String sex; private String name; //setter、getter方法略 }<class name="Clazz" table="tb_class"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> <bag name="students" inverse="true" cascade="all"> <key column="class_id"/> <one-to-many class="com.clf.hibernate.bean.Student"/> </bag> </class> <class name="Student" table="tb_student"> <id name="id" column="id"> <generator class="native"> </id> <property name="name"/> <property name="sex"/> <many-to-one name = "clazz" column = "class_id" cascade = "all" > </ many-to-one > </class>
单边一对一
有两种策略可以实现一对一的关联映射
主键关联:即让两个对象具有相同的主键值,以表明它们之间的一一对应的关系;数据库表不会有额外的字段来维护它们之间的关系,仅通过表的主键来关联。
唯一外键关联:外键关联,本来是用于多对一的配置,但是如果加上唯一的限制之后,也可以用来表示一对一关联关系。
唯一外键关联策略
@Entity public class IdCard { private int id; private String cardNo; @Id @GeneratedValue public int getId() {return id;} //setter、getter略 } @Entity public class Person { private IdCard idCard;//引用IdCard对象 private String name; @Id @GeneratedValue private int id; @OneToOne @JoinColumn(name="idCard") private IdCard idCard; //引用IdCard对象 private String name; //setter、getter略 }<class name="com.clf.hibernate.IdCard" table="t_idcard"> <id name="id" column="id"> <generator class="native"/> </id> <property name="cardNo"/> </class> <class name="com.clf.hibernate.Person" table="t_person"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name"/> <!--在多的一端(当前Person一端),加入一个外键(当前为idCard)指向一的一端(当前IdCard),但多对一关联映射字段是可以重复的,所以需要加入一个唯一条件unique="true",这样就可以此字段唯一了。--> <many-to-one name="idCard" unique="true"/> </class>主键关联策略
IdCart正常注解
@Entity public class Person { private IdCard idCard;//引用IdCard对象 private String name; @Id @GeneratedValue private int id; @OneToOne @PrimaryKeyJoinColumn //注解主键关联映射 private IdCard idCard; //引用IdCard对象 private String name; //setter、getter略 }<class name="com.clf.hibernate.IdCard" table="t_idcard"> <id name="id" column="id"> <generator class="native"/> </id> <property name="cardNo"/> </class> <class name="com.clf.hibernate.Person" table="t_person"> <id name="id" column="id"> <!--主键不是自己生成的,而是作为一个外键,所以使用foreign生成策略。foreign:使用另外一个相关联的对象的标识符,通常和<one-to-one>联合起来使用。再使用元素<param>的属性值指定相关联对象(这里Person相关联的对象为idCard,则标识符为idCard的id)为了能够在加载person数据同时加载IdCard数据,所以需要使用一个标签<one-to-one>来设置这个功能。 --> <generator class="foreign"> <!-- 元素<param>属性name的值是固定为property --> <param name="property">idCard</param> </generator> </id> <property name="name"/> <!--表示如何加载它的引用对象(这里引用对象就指idCard这里的name值是idCard),同时也说是一对一的关系。 默认方式是根据主键加载(把person中的主键取出再到IdCard中来取相关IdCard数据。) 我们也说过此主键也作为一个外键引用 了IdCard,所以需要加一个数据库限制(外键约束)constrained="true" --> <one-to-one name="idCard" constrained="true"/> </class>联合主键关联(Annotation方式)
实现上联合主键的原理同唯一外键关联-单向一样,只是使用的是@JoinColumns,而不是@JoinColumn,实体类注解如下:
@OneToOne @JoinColumns( { @JoinColumn(name="personId", referencedColumnName="id"), @JoinColumn(name="personName", referencedColumnName="name") } ) private Person person;注意:@JoinColumns注解联合主键一对一联系,然后再使用@JoinColumn来注解当前表中的外键字段名,并指定关联哪个字段,使用referencedColumnName指定哪个字段的名称
双边一对一
凡是双向关联,必设mappedBy
唯一外键关联策略
@Entity public class IdCard { private String cardNo; @Id @GeneratedValue private int id; @OneToOne(mappedBy="idCard") private Person person; //setter、getter略 } @Entity public class Person { private IdCard idCard;//引用IdCard对象 private String name; @Id @GeneratedValue private int id; @OneToOne @JoinColumn(name="idCard") //为idCard对象指定外键 private IdCard idCard; //引用IdCard对象 private String name; //setter、getter略 }<class name="com.clf.hibernate.IdCard" table="t_idcard"> <id name="id" column="id"> <generator class="native"/> </id> <property name="cardNo"/> <one-to-one name="person" property-ref="idCard"/> </class> 一对一 唯一外键 关联映射 双向 需要在另一端(当前IdCard),添加<one-to-one>标签,指示hibernate如何加载其关联对象(或引用对象),默认根据主键加载(加载person),外键关联映射中,因为两个实体采用的是person的外键来维护的关系,所以不能指定主键加载person,而要根据person的外键加载,所以采用如下映射方式: <one-to-one>标签:告诉hibernate如何加载其关联对象 property-ref属性:是根据哪个字段进行比较加载数据 <class name="com.clf.hibernate.Person" table="t_person"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name"/> <!--在多的一端(当前Person一端),加入一个外键(当前为idCard)指向一的一端(当前IdCard),但多对一关联映射字段是可以重复的,所以需要加入一个唯一条件unique="true",这样就可以此字段唯一了。--> <many-to-one name="idCard" unique="true"/> </class>主键关联策略
@Entity public class IdCard { private String cardNo; @Id @GeneratedValue private int id; @OneToOne(mappedBy="idCard") private Person person; //setter、getter略 } @Entity public class Person { private IdCard idCard;//引用IdCard对象 private String name; @Id @GeneratedValue private int id; @OneToOne @PrimaryKeyJoinColumn //注解主键关联映射 private IdCard idCard; //引用IdCard对象 private String name; //setter、getter略 }<class name="com.clf.hibernate.IdCard" table="t_idcard"> <id name="id" column="id"> <generator class="native"/> </id> <property name="cardNo"/> <one-to-one name="person"/> </class> <class name="com.clf.hibernate.Person" table="t_person"> <id name="id" column="id"> <generator class="foreign"> <param name="property">idCard</param> </generator> </id> <property name="name"/> <one-to-one name="idCard" constrained="true"/> </class>单边多对多
@Entity public class User { private int id; private String name; private Set<User> roles = new HashSet<User>();// Role对象的集合 @Id @GeneratedValue public int getId() {return id;} @ManyToMany @JoinTable(name="u_r",//使用@JoinTable标签的name属性注解第三方表名称 joinColumns={@JoinColumn(name="userId")}, //使用joinColumns属性来注解当前实体类在第三方表中的字段名称并指向该对象 inverseJoinColumns={@JoinColumn(name="roleId")} //使用inverseJoinColumns属性来注解当前实体类持有引用对象在第三方表中的字段名称并指向被引用对象表 ) public Set<User> getRoles() {return roles; }Role实体正常注解
Role映射文件:
<class name="com.clf.hibernate.Role" table="t_role"> <id name="id"> <generator class="native"/> </id> <property name="name" column="name"/> </class> User映射文件: <class name="com.clf.hibernate.User" table="t_user"> <id name="id"column="id"> <generator class="native"/> </id> <property name="name"/> <!--使用<set>标签映射集合(set),标签中的name值为对象属性名(集合roles),而使用table属性是用于生成第三方表名称,例:table="t_user_role",但是第三方面中的字段是自动加入的,作为外键分别指向其它表。 所以表<key>标签设置,例:<key column="userid"/>,意思是:在第三方表(t_user_role)中加入一个外键并且指向当前的映射实体类所对应的表(t_user).使用<many-to-many>来指定此映射集合所对象的类(实例类),并且使用column属性加入一个外键指向Role实体类所对应的表(t_role) --> <set name="roles" table="t_user_role"> <key column="userid"/> <many-to-many class="com.clf.hibernate.Role" column="roleid"/> </set> </class>双边多对多
User实体类的注解与单边多对多一样
Role实体类注解也非常的简单:使用@ManyToMany注解,并使用mappedBy属性指定引用对象持有自己的的属性名
@Entity public class Role { private int id; private String name; private Set<User> users = new HashSet<User>(); @Id @GeneratedValue public int getId() {return id; } @ManyToMany(mappedBy="roles") public Set<User> getUsers() {return users;} public voidsetUsers(Set<User> users) {this.users = users; }<class name="com.clf.hibernate.User" table="t_user"> <id name="id"column="id"> <generator class="native"/> </id> <property name="name"/> <set name="roles" table="t_user_role"> <key column="userid"/> <many-to-many class="com.clf.hibernate.Role" column="roleid"/> </set> </class> <class name="com.clf.hibernate.Role" table="t_role"> <id name="id"> <generator class="native"/> </id> <property name="name" column="name"/> <set name="users" table="t_user_role"order-by="userid"> <key column="roleid"/> <many-to-many class="com.clf.hibernate.User" column="userid"/> </set> </class>component(组件)关联映射
例如有以下两个实体类:用户与员工,两者存在很多相同的字段,但是两者有不可以是同一个类,这样在实体类中每次都要输入很多信息,现在把其中共有部分抽取出来成为一个类,然后在用户、员工对象中引用就可以
值对象没有标识,而实体对象具有标识,值对象属于某一个实体,使用它重复使用率提升,而且更清析。
以上关系的映射称为component(组件)关联映射
在hibernate中,component是某个实体的逻辑组成部分,它与实体的根本区别是没有oid,component可以成为是值对象(DDD)。
采用component映射的好处:它实现了对象模型的细粒度划分,层次会更加分明,复用率会更高。
<class name="com.clf.hibernate.User" table="t_user"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name" column="name"/> <component name="contact"> <property name="email"/> <property name="address"/> <property name="zipCode"/> <property name="contactTel"/> </component> </class>@Entity public class User { private int id; private String name; private Contact contact;//值对象的引用 @Id @GeneratedValue public int getId() { return id;} @Embedded//用于注解组件映射,表示嵌入对象的映射 public Contact getContact() {return contact;} public void setContact(Contactcontact) {this.contact = contact;}Contact类是值对象,不是实体对象,是属于实体类的某一部分,因此没有映射文件
Contact值对象:
public class Contact { private String email; private String address; private String zipCode; private String contactTel; //setter、getter略 }继承关联映射
继承映射:就是把类的继承关系映射到数据库里(首先正确的存储,再正确的加载数据)
继承关联映射的分类:
单表继承:每棵类继承树使用一个表(table per class hierarchy)
具体表继承:每个子类一个表(table per subclass)
类表继承:每个具体类一个表(table per concrete class)(有一些限制)
下面用一个实例来说明三种继承关系的用法和区别:
动物Animal有三个基本属性,然后有一个Pig继承了它并扩展了一个属性,还有一个Brid也继承了并且扩展了一个属性
Animal实体类:
public class Animal { private int id; private String name; private boolean sex; public int getId() {return id; } public void setId(int id) { this.id = id;} public String getName() {return name;} public void setName(Stringname) {this.name = name;} public boolean isSex() {return sex;} public void setSex(boolean sex) {this.sex = sex;} }Pig实体类:
public class Pig extends Animal { private int weight; public int getWeight() {return weight;} public void setWeight(int weight) {this.weight = weight;} }Bird实体类:
public class Bird extends Animal { private int height; public int getHeight() {return height;} public void setHeight(int height) {this.height = height;} }单表继承
把所有的属性都要存储表中,目前至少需要5个字段,另外需要加入一个标识字段(表示哪个具体的子类)
其中:
①、id:表主键
②、name:动物的姓名,所有的动物都有
③、sex:动物的性别,所有的动物都有
④、weight:只有猪才有
⑤、height:只有鸟才有
⑥、type:表示动物的类型;P表示猪;B表示鸟
<class name="Animal" table="t_animal" lazy="false"> <id name="id"> <generator class="native"/> </id> <discriminator column="type" type="string"/> <property name="name"/> <property name="sex"/> <subclass name="Pig" discriminator-value="P"> <property name="weight"/> </subclass> <subclass name="Bird" discriminator-value="B"> <property name="height"/> </subclass> </class>父类用普通的<class>标签定义
在父类中定义一个discriminator,即指定这个区分的字段的名称和类型,如:<discriminator column="XXX" type="string"/>
子类使用<subclass>标签定义,在定义subclass的时候,需要注意如下几点:
Subclass标签的name属性是子类的全路径名
在Subclass标签中,用discriminator-value属性来标明本子类的discriminator字段(用来区分不同类的字段) 的值Subclass标签,既可以被class标签所包含(这种包含关系正是表明了类之间的继承关系),也可以与class标 签平行。 当subclass标签的定义与class标签平行的时候,需要在subclass标签中,添加extends属性,里面的值
是父类的全路径名称。子类的其它属性,像普通类一样,定义在subclass标签的内部。annotation注解
父类中注解如下:
使用@Inheritance注解为继承映射,再使用strategy属性来指定继承映射的方式
strategy有三个值:InheritanceType.SINGLE_TABLE 单表继承
InheritanceType.TABLE_PER_CLASS 类表继承
InheritanceType.JOINED 具体表继承
再使用@DiscriminatorColumn注意标识字段的字段名,及字段类型
在类中使用@DiscriminatorValue来注解标识字段的值
@Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn( name="discriminator", discriminatorType=DiscriminatorType.STRING) @DiscriminatorValue("type") public class Animal {……}继承类中注解如下:
只需要使用@DiscriminatorValue来注解标识字段的值
具体表继承
每个类映射成一个表(table per subclass),所以Animal也映射成一张表(t_animal),表中字段为实体类属性
而pig子类也需要映射成一张表(t_pid),但为了与父类联系需要加入一个外键(pidid)指向父类映射成的表(t_animal),字段为子类的扩展属性。Bird子类同样也映射成一张表(t_bird),也加入一个外键(birdid)指向父类映射成的表(t_animal),字段为子类的扩展属性。
<class name="com.clf.hibernate.Animal" table="t_animal"> <id name="id"column="id"><!-- 映射主键 --> <generator class="native"/> </id> <property name="name"/><!-- 映射普通属性 --> <property name="sex"/> <!--joined-subclass标签:每个类映射成一个表 --> <joined-subclass name="com.clf.hibernate.Pig" table="t_pig"> <!-- <key>标签:会在相应的表(当前映射的表)里,加入一个外键 , 参照指向当前类的父类(当前Class标签对象的表(t_animal))--> <key column="pigid"/> <property name="weight"/> </joined-subclass> <joined-subclass name="com.clf.hibernate.Bird" table="t_bird"> <key column="birdid"/> <property name="height"/> </joined-subclass> </class>这种策略是使用joined-subclass标签来定义子类的。父类、子类,每个类都对应一张数据库表。
在父类对应的数据库表中,实际上会存储所有的记录,包括父类和子类的记录;在子类对应的数据库表中,这个表只定义了子类中所特有的属性映射的字段。子类与父类,通过相同的主键值来关联。实现这种策略的时候,有如下步骤:
父类用普通的<class>标签定义即可
父类不再需要定义discriminator字段
子类用<joined-subclass>标签定义,在定义joined-subclass的时候,需要注意如下几点:
Joined-subclass标签的name属性是子类的全路径名
Joined-subclass标签需要包含一个key标签,这个标签指定了子类和父类之间是通过哪个字段来关联的。如:<key column="PARENT_KEY_ID"/>,这里的column,实际上就是父类的主键对应的映射字段名称。Joined-subclass标签,既可以被class标签所包含(这种包含关系正是表明了类之间的继承关系),也可以与class标签平行。 当Joined-subclass标签的定义与class标签平行的时候,需要在Joined-subclass标签中,添加extends属性,里面的值是父类的全路径名称。子类的其它属性,像普通类一样,定义在joined-subclass标签的内部。
annotation注解
因为子类生成的表需要引用父类生成的表,所以只需要在父类设置具体表继承映射就可以了,其它子类只需要使用@Entity注解就可以了
@Entity @Inheritance(strategy=InheritanceType.JOINED) public class Animal {……}具体表继承效率没有单表继承高,但是单表继承会出现多余的庸于字段,具体表层次分明
类表继承
每个具体类(Pig、Brid)映射成一个表(table per concrete class)(有一些限制)
<class name="com.clf.hibernate.Animal" table="t_animal"> <id name="id"column="id"><!-- 映射主键 --> <generator class="assigned"/><!-- 每个具体类映射一个表主键生成策略不可使用native --> </id> <property name="name"/><!-- 映射普通属性 --> <property name="sex"/> <!--使用<union-subclass>标签来映射"每个具体类映射成一张表"的映射关系,实现上上面的表t_animal虽然映射到数据库中,但它没有任何作用。 --> <union-subclass name="com.clf.hibernate.Pig" table="t_pig"> <property name="weight"/> </union-subclass> <union-subclass name="com.clf.hibernate.Bird" table="t_bird"> <property name="height"/> </union-subclass> </class>如果不想t_animal存在(因为它没有实际的作用),可以设置<class>标签中的abstract="true"(抽象表),这样在导出至数据库时,就不会生成t_animal表了
这种策略是使用union-subclass标签来定义子类的。每个子类对应一张表,而且这个表的信息是完备的,即包含了所有从父类继承下来的属性映射的字段(这就是它跟joined-subclass的不同之处,joined-subclass定义的子类的表,只包含子类特有属性映射的字段)。实现这种策略的时候,有如下步骤:
父类用普通<class>标签定义即可
子类用<union-subclass>标签定义,在定义union-subclass的时候,需要注意如下几点:
Union-subclass标签不再需要包含key标签(与joined-subclass不同)
Union-subclass标签,既可以被class标签所包含(这种包含关系正是表明了类之间的继承关系),也可以与class标签平行。 当Union-subclass标签的定义与class标签平行的时候,需要在Union-subclass标签中,添加extends属性,里面的值是父类的全路径名称。子类的其它属性,像普通类一样,定义在Union-subclass标签的内部。这个时候,虽然在union-subclass里面定义的只有子类的属性,但是因为它继承了父类,所以,不需要定义其它的属性,在映射到数据库表的时候,依然包含了父类的所有属性的映射字段。
注意:在保存对象的时候id是不能重复的(不能使用自增生成主键)
annotation注解
只需要对父类进行注解就可以了,因为子类表的ID是不可以重复,所以一般的主键生成策略已经不适应了,只有表主键生成策略。
首先使用@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)来注解继承映射,并且使用具体表继承方式,使用@TableGenerator来申明一个表主键生成策略再在主键上@GeneratedValue(generator="t_gen", strategy=GenerationType.TABLE)来注解生成策略为表生成策略,并且指定表生成策略的名称继承类只需要使用@Entity进行注解就可以了
@Entity @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) @TableGenerator( name="t_gen", table="t_gen_table", pkColumnName="t_pk", valueColumnName="t_value", pkColumnValue="person_pk", initialValue=1, allocationSize=1 )三种继承关联映射的区别:
第一种:它把所有的数据都存入一个表中,优点:效率好(操作的就是一个表);缺点:存在庸于字段,如果将庸于字段设置为非空,则就无法存入数据;
第二种:层次分明,缺点:效率不好(表间存在关联表)
第三种:主键字段不可以设置为自增主键生成策略。