组件属性包含的关联实体
前面已经提到过,组件里的属性不仅可以是基本类型、字符串、日期类型等,也可以是值类型行的组件,甚至可以是关联实体。
对于组件的属性是关联实体的情形,可以使用@OneToOne、@ OneToMany、@ManyToOne、@ ManyToMany修饰代表关联实体的属性。
如果程序采用基于外键的映射策略,还需要配合@ JoinColumn注解——该注解用于映射外键列:
如果程序采用基于连接表的映射策略,还需要配@ JoinTable注解—该注解用于映射连接表
下面的 Person实体定义包含一个 Address类型的属性,但这个 Address类型的属性并非代表关连,只是一个组件
@Entity
@Table(name="person_inf")
public class Person {
//标识属性
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
//定义一个组件
private Address address;
}
上面程序中粗体字代码定义了 Address类型的组件属性, Address组件中包含了一个Set类型的属性,该Set类型的属性负责维护与 School实体之间的1-N关联。
如果程序使用不带连接表的关联策略来维护 Address与 School之间的关联关系,则需要使用@oneToMany、@JoinColum修饰代表关联实体的Set属性。
如果程序使用带连接表的关联策略来维护 Address与 School之间的关联关系,则需要使用@OneToMany、@ JoinTable修饰代表关联实体的set属性
1 下面使用不带连接表的关联策略来维护Adess与 School之间的关联关系,因此需要使用@OneToMany、@ JoinColumn修饰Address里代表关联实体的set属性。下面是Adess类的源代码。
@Entity
@Table(name="address_inf")
public class Address {
@Id @Column(name="address_inf")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
//定义地址详细信息的成员变量
private String addressDetail;
//定义该组件属性所在的包含实体
@parent
@OneToMany(targetEntity=School.class)
//映射外键列,此处告诉Hibernate在school实体对应的表中增加外键列
//该外键列的列名为address_id,参照person_inf表 的person_id主键列
@JoinColumn(name="address_id",referencedColumnName="person_id")
private Set<School> schools = new HashSet<>();
}
下面示范如何保存Person对象和Address对象
//保存 Person和 Schoo1对象
private void test Person (
Session session= Hibernateutil.currentsession ();
Transaction tx= session. beginTransaction ( );
//创建一个 Person对象
Person p= new Person ();
//设置 Person的name
p.setName("crazyit");
p.setAge(21);
session. save(p);
//创建一个 Address对象
Address a= new Address("广州天河");
//设置 Person对象的 Address属性
p.setAddress (a);
//创建两个 Schoo1对象
School s1= new Schoo1("疯狂os训练营”);
Schoo1s2= new Schoo1("疯狂Java训练营”);
//保存两个 Schoo1实体
session. save(s1);
session. save(s2);
//设置 Address对象和两个 Schoo1的关联关系
a. getSchools().add(sl);
a.getschools ().add(s2);
tx.commitor();
Hibernateutil.closesession()
基于符合主键的关联关系
对于1-N的双向关联,1的一端将两个属性结合起来作为符合主键,N的一端依然使用Integer类型的普通主键。
下面是1的一端的源码
@Entity
@Table(name="person_inf")
public class Person implements java.io.Serializable{
//定义first成员变量,作为标识属性的成员
private String first;
//定义last成员变量,作为标识属性的成员
@Id
private String last;
//记录该Person实体关联的所有Address实体
@OneToMany(targetEntity=Address.class,mappedBy="person",cascade=CascadeType.All)
private Set<Address> addresses = new HashSet<>();
//重写equals和hashCode方法
}
下面是Address实体类的代码
@Entity
@Table(name="address_inf")
public class Address {
@Id @Column(name="address_inf")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
//定义地址详细信息的成员变量
private String addressDetail;
//定义该组件属性所在的包含实体
@ManyToOne(targetEntity=Person.class)
//使用@JoinColumns包含多个@JoinColumn定义外键列
@JoinColumns({//由于主表使用了符合主键(有两个主键列)
//因此需要使用两个@JoinColumn定义外键列来参照person_inf表的两个主键列
@JoinColumn(name="person_first",referencedColumnName="first",nullable=false),
@JoinColumn(name="person_last",referencedColumnName="last",nullable=false)
})
private Person person;
}
复合主键的成员属性为关联实体
例如:订单,商品,订单项
一个订单包含多个订单项,一个订单项用于订购某个商品,以及订购数量,一个商品可以多次出现在不同的订单中
Product类没有保留与其他实体的关联关系
@Entity
@Table(name="product_id")
public class Product {
//定义标识属性
@Id @Column(name="product_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer productId;
private String name;
//五参数的构造器
public Product() {}
//
}
Order持久化类
OrderItem需要维护与订单项的1-N关联关系,因此Order类中需要增加Set类型的属性,该属性负责管理与多个OrderItem之间的关联关系,应该使用@OneToMany修饰该Set集合属性
@Entity
@Table(name="order_inf")
public class Order {
//定义标识属性
@Id @Column(name="product_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer orderId;
private Date orderDate;
//关联的订单项
@OneToMany(targetEntity=OrderItem.class,mappedBy="order")
private Set<OrderItem> items = new HashSet<>();
}
OrderItem需要维护与Product的N-1关联关系,还需要维护与Order之间的N-1关联关系,因此使用@ManyToOne修饰这两个代表关联实体的属性,并配置@JointColumn来映射底层的外键列
下面是OrderItem的实体
@Entity
@Table(name="order_item_inf")
public class OrderItem implements java.io.Serializable{
//下面三个属性将作为联合主键
//定义关联的Order实体
@ManyToOne(targetEntity=Order.class)
//映射名为order_id的外键列,参照order_inf的order_id主键列
@JoinColumn(name="order_id",referencedColumnName="order_id")
@Id
private Order order;
//定义关联的Product实体
@ManyToOne(targetEntity=Product.class)
//映射名我product_id的外键列,参照product_inf的product_id主键列
@JoinColumn(name="product_id",referencedColumnName="product_id")
@Id
private Product product;
private Product product;
//该订单订购的产品数量
@Id
private int count;
//有无参数构造器 重写equals方法和hashcode方法
}
持久化的传播性
、、、、、、 正如在前面的程序中看到的,当程序中有两个关联实体时,程序需要主动保存、除或重关个化实体:如果需要处理许多彼此关联的实体,则需要依次保存每个实体,这会让人感觉机点
从数据库建模的角度来看,两个表之间的1-N关联关系总是用外键约束来表示,其中保留外键的表称为从表,被从表参照的数据表称为主表,对于这种主从表约束关系,Hibemate则有两种映射策略。
1>将从表记录映射成持久化类的组件
2>将从表记录映射成持久化类的实体
将从表记录映射成持久化类的组件,这些组件的生命周期总是依赖于父对象, Hibernate会默联操作,不需要额外的动作。当父对象被保存时,这些组件子对象也将被保存,父对象被删除,子对象也将被删除
如果将从表记录映射成持久化实体,则从表实体也有了自己的生命周期,从而应该允许其他实体共对它的引用,例如,从集合中移除一个实体,不意味着它可以被删除。所以 Hibernate默认不启用实其他关联实体之间的级联操作
对于关联实体面言, Hibernate默认不会启用级联操作,当父对象被保存时,它关联的子实体不会保存:父对象被删除时,它关联的子实体不会被删除,
为了启用不同持久化操作的级联行为, Hibernate定义了如下级联风格
///////////
1>CascadeType .ALLt指定 Hibernate将所有的持久化操作都级联到关联实体
2>CascadeType .MERGE:指定 Hibemate将 merge操作级联到关联实体
3>CascadeType .PERSIST:指定 Hibernate将persist操作级联到关联实体
4>CascadeType.REFRESH:指定 Hibernate将resh操作级联到关联实体
5>CascadeType.REMOVE:指定 Hibernate将 remove操作级联到关联实体
如果程序期望某个操作能被级联传播到关联实体,则可以在配置@ OneToMany、@OneToOne、@MenyToMany时通过 cascade属性来指定,例如:
//指定persist操作将级联到关联实体
@OneToOne(cascade=CascadeType.PERSIST)
级联风格是可组合的,如下面配置所示:
//指定persist()操作将级联关联到实体
@OneToOne(cascade={ CascadeType.PERSIST, CascadeType.DELTE})
可以使用 cascade= CascadeType.ALL指定所有的持久化操作都被级联到关联实体。 Hibernate对关联实体默认不使用任何级联,即任何操作都不会被级联到关联实体,
Hibernate还支持一个特殊的级联策略:删除“孤儿”记录(可通过 @oneToMany, @OneToOne的orphanRemoval属性来启动该级联策略),该级联策略只对当前实体是1的一端,且底层数据表为主表有效,对于启用了 ophanRemoval策略的级联操作而言,当程序通过主表实体切断与从表实体的关联关系时,虽然此时主表实体对应的记录井没有删除,但由于从表实体失去了对主表实体的引用,因此这个从表实体就变成了“孤儿”记录, Hibemate会自动删除这些记录
对于级联的设定, Hibernate有如下建议
1>在 ManyToOne中指定级联没什么意义,级联通常在 @OneToOne和@ OneToMany关系中比较有用---一因为级联操作应该是由主表记录传播到从表记录,通常从表记录则不应该传播到主表记录,因此 @ManyToOne不支持指定 cascade属性,但在某些极端情况下,
如果程序就是希望为 @ManyToOne指定级联策略,则也可使用 Hibernate提供的 @cascade注解
2>如果从表记录被完全限制在主表记录之内(当主表记录被删除后,从表记录没有存在的意义),则可以指定 cascade=Cascade.ALL,再配合 orphanRemoval=true级联策略,将从表实体的生命周期完全交给主表实体管理
3>如果经常在某个事务中同时使用主表实体和从表实体,则可以考虑指定 cascade=( CascadeType.PERSIST,CascadeType.MERGE}级联策略
可能有读者对 cascade=CascadeType.ALL和 orphanRemoval=true两种策略感到迷感,对于 CascadeType. ALL级联策略的详细解释如下:
如果主表实体被persist(),那么关联的从表实体也会被 persist()
如果主表实体被merge(),那么关联的从表实体也会被 merge()
如果主表实体被save()、 update()或 saveOrUpdate(),那么所有的从表实体则会被 saveOrUpdate()
如果把持久化状态下的主表实体和瞬态或脱管的从表实体建立关联,则从表实体将被自动持久化
如果主表实体被删除,那么关联的从表实体也会被删除
如果没有把主表实体删除,只是切断主表实体和从表实体之间的关联关系,则关联的从表实体不会被删除,只是将关联的从表实体的外键列设为mull
如果指定 orphanRemoval=true策略,则只要一个从表实体失去了关联的主表实体,不管该主表实体是被删除,还是切断了主表实体和它的关联,那么该从表实体就变成了 orphan(孤儿),Hibernate将自动删除该从表实体
所有操作都是在调用期( call time)或者写入期( flush time)级联到关联对象上的,如果可能,Hibernate通常会在调用持久化操作时(调用期)将持久化操作级联到关联实体上,然而, saveupdate和orphanRemoval操作是在 Session flush时(写入期)才级联到关联对象上的
Hibernate的级联风格看上去比数据库本身的级联操作更加强大,这是因为 Hibernate的级联操作是建立在程序级别上的,而数据库的级联操作则是建立在数据库级别上的
继承映射
Hibernate支持多种继承映射策略,不管哪种继承映射策略, Hibernate的多态查询都可以运行良好,接下来介绍的示例程序中包含了多个持久化类,这些持久化类之间不仅存在继承关系,也存在复杂的关联关系,
对于类与类之间的继承关系,Hibernate提供了三种映射策略
- 整个类层次对应一个表
- 连接子类的映射策略
- 每个具体类对应一个
介绍这三种继承映射策略
本示例中一共包括 Person, Employee、 Manager、 Customer四个持久化类,其中Peson持久化类还包含一个 Address组件属性
///////////////////
上面4个持久化类之间的继承关系是: Person派生出了 Employee和 Customer,Employee生出了 Manager
上面4个实体之间的关联关系是: Employee和 Manager之间存在双向的N-1关联关系。Employee和 Customer之间存在双向的1-N关联关系
其中Person实体包含一个Address符合属性。Address是普通的javaBean。
1>整个类层次对应一个表的映射策略
整个类层次对应一个表的映射策略是 Hibernate继承映射默认的映射策略,在这种映射第下Person持久化类,Employee持久化类, Customer持久化类和 Manager持久化类都存储在一个数据表中,这个数据表包含很多列,这些数据列是整个类层次中所有实体的全部属性的总和,由于整个类层次中的所有实体都存放在一个数据表中一-----,为该表额外增加一列,一这个列被称为区分每行记录到底到底是哪个类的实例。使用@DiscriminatorColumn来配置此列。@DiscriminatorColumn可指定的属性
属性 |
是否必须 |
说明 |
columnDefinition |
否 |
指定Hibernate使用该属性值指定的SQL片段来创建该辨别者列 |
name |
否 |
指定辨别者列的名称,该属性值是DTYPE |
discriminatorType |
否 |
指定辨别者列的数据类型。 DiscriminatorType.CHAR: 辨别者列的类型是字符串 类型,既该列只接受单个字符 DiscriminatorType.INTEGER辨别者列是整数类型,既该列只接受整数类型 DiscriminatoryType.STRING:辨别者列的类型是字符串类型,只接受字符串值 |
length |
否 |
该属性指定辨别者列的字符长度 |
使用@DiscriminatorColumn修饰父类,使用@DiscriminatorValue修饰每个子类
Adress类示例
@Entity
@Table(name="address_inf")
public class Address {
//定义代表该Adress详细信息的成员变量
private String detail;
//邮编信息成员变量
private String zip;
//国家成员信息变量
private String country;
}
Person实例类如下
@Entity
//定义辨别者列的列名为person_type,列类型为字符串
@DiscriminatorColumn(name="person_type",discriminatorType=DiscriminatorType.STRING)
//指定Person实体对应的记录在辨别者列的值为--普通人
@Table(name="person_inf")
public class Person implements java.io.Serializable{
@Id @Column(name="person_id")
@GeneratedValue(strategy=GeneratedType.IDENTITY)
private Integer id;
private String name;
private char gender;
//定义该person实体的组件属性:address
@Embedded
@AttributeOverrides({
@AttributeOverride(name="detail",column=@Column(name="addres_detial")),
@AttributeOverride(name="zip",column=@Column(name="address_zip"))
@AttributeOverride(name="country",column=@Column(name="address_country"))
})
private Address address;
}
@Entity
//指定Employee实体对应的记录在辨别者列的值为------员工
@DiscriminatorValue("员工")
public class Employee extends Person {
//职位
private String title;
//工资
private double salary;
//定义和该员工保持关联的Customer关联实体
@OneToMany(cascade=CascadeType.ALL,mappedBy="employee", targetEntity=Customer.class)
private Set<Customer> customers = new HashSet<>();
//定义和该员工关联的Manager关联实体
@ManyToOne(cascade=CascadeType.ALL,targetEntity=Manger.class)
@JoinColumn(name="manager_id",nullable=true)
private Manager manager;
}
连接子类的映射策略
这种策略不是 Hibemate继承映射的默认策略,因此如果需要在继承映射中采用这种映射策略,须在继承树的根类中使用 @Inheritance指定映射策略。
使用@ Inheritance时必须指定strategy属性,该属性支持如下三个值。
- InheritanceType.SINGLE_TABLE:整个类层次对应一个表的映射策略。这是默认值。
- InheritanceType.JOINED:连接子类的映射策略。
- InheritanceType.TABLE_PER_CLASS:每个具体类对应一个表的映射策略。
采用这种映射策略时,父类实体保存在父类表里,而子类实体则由父类表和子类表共同存储。子类实体也是一个特殊的父类实体,因此必然也包含了父类实体的属性,于是将子类与父类共有的属性保存在父类表中,而子类增加的属性则保存在子类表中。
在这种映射策略下,无须使用辨别者列,只要在继承树的根实体类上使用@Inheritance修饰,并为该注解指定 strategy=InheritanceType.JOINED即可。
下面是连接子类的映射策下person类的代码
@Entity
//指定使用链接子类的映射策略
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="person_inf")
public class Person implements java.io.Serializable{
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private char gender;
//定义该person实体的组件属性:address
@Embedded
@AttributeOverrides({
@AttributeOverride(name="detail",column=@Column(name="addres_detial")),
@AttributeOverride(name="zip",column=@Column(name="address_zip"))
@AttributeOverride(name="country",column=@Column(name="address_contry"))
})
private Address address;
}
@Entity
@Table(name="customer_inf")
public class Customer extends Person {
//
private String comments;
//和顾客关联的Employee实体
@ManyToOne(cascade=CascadeType.ALL,targetEntity=Employee.getClass())
@JoinColumn(name="employee_id",nullable=true)
private Employee employee;
}
Employee和Manager和customer类似
使用连接子类的继承映射策略,当程序查询子类实例时,需要跨越多个表查询。到底
需要跨越多少个表,取决于该子类有多少层父类
采用连接子类的映射策略时,无须使用辨别者列,子类增加的属性也可以有非空约束,是一种比较里想的映射策略。只是在查询子类实体的数据时,可能需要跨越多个表来查询。对于类继承层次较深的继承树来说,查询子类实体时需要在多个子类表之间进行连接操作,可能导致性能低下
每个具体类对应一个表的映射策略
Hibemate规范还支持每个具体类对应一个表的映射策略,在这种映射策略下,子类增加的属性也可以有非空约束,即父类实例的数据保存在父表中,而子类实例的数据则保存在子表中,与连接子类映射策略不同的是,子类实例的数据仅保存在子类表中,没有在父类表中有任何记录,在这种映射策略下,子类表的字段比父类表的字段要多,因为子类表的字段等于父类属性加子类增加属性的总和。在这种映射策略下,如果单从数据库来看,几乎难以看出它们之间存在继承关系,只是多个实体之的主键值具有某种连续性一因此不能让数据库为各数据表自动生成主键值。因此,采用这种继承策略时,不能使用 GenerationType.IDENTITY、 Generation Type.AUTO这两种主键生成策略
与连接子类映射策略相似的是,采用这种映射策略时,开发者必须在继承树的根类中使用
@Inheritance修饰,使用该注解时指定 strategy= InheritanceType.TABLE_PER_CLASS属性
如下是在这种继承映射策略下 Person实体类的代码。
@Entity
//指定使用每个具体类对应一个表的映射测略
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
@Table(name="person_inf")
public class Person implements java.io.Serializable{
@Id @Column(name="person_id")
//由于不能使用identity主键生成策略,故此处采用hilo主键生成策略
@GenericGenerator(name="person_hilo",strategy="hilo")
@GeneratedValue(generator="person_hilo")
private Integer id;
private String name;
private char gender;
//定义该person实体的组件属性:address
@Embedded
@AttributeOverrides({
@AttributeOverride(name="detail",column=@Column(name="addres_detial")),
@AttributeOverride(name="zip",column=@Column(name="address_zip"))
@AttributeOverride(name="country",column=@Column(name="address_contry"))
})
private Address address;
}