Hibernate中的繼承指的是實體類之間的繼承。能夠繼承屬性。本例中書中使用了Person、Customer、Manager和Employee這四個實體類來距離。其中Person衍生出了Customer和Employee,而Employee又衍生出了Employee,所以說Employee的屬性是最多的。除此之外,Person還有一個組件屬性Address。Customer與Employee是N-1映射,而Employee與Manager也是1-N映射。
我們先來寫一下大家都有的Address。
public class Address { private String detail; private String zip; private String country; public Address(){} public Address(String detail , String zip , String country) { this.detail = detail; this.zip = zip; this.country = country; } }
由於我們在Person類中配置組件屬性的列映射關係,因此這裏的Address就是一個普通的bean。Hibernate支持三種繼承映射關係:
整個類層次對應一個表
連接子類的映射策略
每個具體類對應一個表
整個類層次對應一個表
這是默認的策略,所有子類跟父類在一起,表中有很多類,包含了所有的屬性,因此有的列是空的。我們使用@DiscriminatorColumn來配置一個識別列,可以指定列的名字,列中數據類型。有了這個列,我們還要在所有的子類跟父類中使用@Discriminator來指定一個表示,以表示整個表中的某一行具體是屬於哪個實體。
@Entity // 定義辨別者列的列名爲person_type,列類型爲字符串 @DiscriminatorColumn(name="person_type" , discriminatorType=DiscriminatorType.STRING) // 指定Person實體對應的記錄在辨別者列的值爲"普通人" @DiscriminatorValue("allkind") @Table(name="person_inf") public class Person { @Id @Column(name="person_id") @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private String name; private char gender; @Embedded @AttributeOverrides({ @AttributeOverride(name="detail", column=@Column(name="address_detail")), @AttributeOverride(name="zip", column=@Column(name="address_zip")), @AttributeOverride(name="country", column=@Column(name="address_country")) }) private Address address; public Person(){} public Person(Integer id , String name , char gender) { this.id = id; this.name = name; this.gender = gender; } }
這個實體中在類之前我們定義了標識列,以及這個類在標識列中的標識。此外這裏面定義了一個address組件。下面我們看員工類。
@Entity // 指定Employee實體對應的記錄在辨別者列的值爲"員工" @DiscriminatorValue("employee") @Table(name="employee_inf") 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=Manager.class) @JoinColumn(name="manager_id", nullable=true) private Manager manager; public Employee(){} public Employee(String title , double salary) { this.title = title; this.salary = salary; } }
@Entity // 指定Manager實體對應的記錄在辨別者列的值爲"經理" @DiscriminatorValue("manager") @Table(name="manager_inf") public class Manager extends Employee { private String department; // 定義和該經理保持關聯的Employee關聯實體 @OneToMany(cascade=CascadeType.ALL , mappedBy="manager" , targetEntity=Employee.class) private Set<Employee> employees = new HashSet<>(); // 無參數的構造器 public Manager(){} public Manager(String department) { this.department = department; } }
@Entity // 指定Customer實體對應的記錄在辨別者列的值爲"顧客" @DiscriminatorValue("customer") @Table(name="customer_inf") public class Customer extends Person { private String comments; // 定義和該顧客保持關聯的Employee關聯實體 @ManyToOne(cascade=CascadeType.ALL ,targetEntity=Employee.class) @JoinColumn(name="employee_id", nullable=true) private Employee employee; // 無參數的構造器 public Customer(){} public Customer(String comments) { this.comments = comments; } }
下面來看測試代碼。這個代碼我直接衝書中給的光盤中的源代碼粘過來的。
private void createAndStorePerson() { Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); // 創建一個普通員工 Employee zhu = new Employee(); // 設置員工的基本屬性 zhu.setName("老朱"); zhu.setTitle("項目組長"); zhu.setGender('男'); zhu.setSalary(4500); // 設置員工的組件屬性 zhu.setAddress(new Address("廣州","523034","中國")); // 創建第二個員工 Employee zhang = new Employee(); // 設置該員工的基本屬性 zhang.setName("張美麗"); zhang.setTitle("項目分析"); zhang.setGender('女'); zhang.setSalary(5500); // 設置該員工的組件屬性 zhang.setAddress(new Address("廣州","523034","中國")); // 創建一個經理對象 Manager grace = new Manager(); // 設置經理對象的基本屬性 grace.setName("Grace"); grace.setTitle("項目經理"); grace.setGender('女'); grace.setSalary(12000); // 設置經理的組件屬性 grace.setAddress(new Address("加州" , "523034" , "美國")); // 設置經理的管轄部門屬性 grace.setDepartment("研發部"); // 設置第二個員工和grace之間的關聯關係 zhang.setManager(grace); // 創建一個Customer對象 Customer he = new Customer(); // 設置Customer對象的基本屬性 he.setName("小賀"); he.setGender('男'); // 設置Customer對象的組件屬性 he.setAddress(new Address("湖南" , "233034" , "中國")); he.setComments("喜歡購物"); // 建立Customer對象和grace對象的關聯關係 he.setEmployee(grace); // 創建一個普通Person對象 Person lee = new Person(); // 設置Person對象的基本屬性 lee.setName("crazyit.org"); lee.setGender('男'); // 設置Person對象的組件屬性 lee.setAddress(new Address("天河" , "434333" , "中國")); // 持久化所有實體。 session.save(lee); session.save(grace); session.persist(zhu); session.persist(zhang); session.save(he); tx.commit(); HibernateUtil.closeSession(); }
mysql> select * from person_inf;
+-------------+-----------+-----------------+----------------+-------------+--------+--------------+----------+--------+-------+------------+-------------+------------+
| person_type | person_id | address_country | address_detail | address_zip | gender | name | comments | salary | title | department | employee_id | manager_id |
+-------------+-----------+-----------------+----------------+-------------+--------+--------------+----------+--------+-------+------------+-------------+------------+
| ??? | 1 | ?? | ?? | 434333 | ? | crazyit.org | NULL | NULL | NULL | NULL | NULL | NULL |
| ?? | 2 | ?? | ?? | 523034 | female | Grace | NULL | 12000 | ???? | ??? | NULL | NULL |
| ?? | 3 | USA | DC | 22202 | male | Zhijin Zhang | NULL | 9500 | vp | NULL | NULL | NULL |
| ?? | 4 | NULL | NULL | NULL | NULL | NULL | NULL | 0 | NULL | NULL | NULL | 2 |
| ?? | 5 | ?? | ?? | 233034 | ? | ?? | ???? | NULL | NULL | NULL | 2 | NULL |
| general | 6 | ?? | ?? | 434333 | ? | crazyit.org | NULL | NULL | NULL | NULL | NULL | NULL |
| manager | 7 | ?? | ?? | 523034 | female | Grace | NULL | 12000 | ???? | ??? | NULL | NULL |
| employee | 8 | USA | DC | 22202 | male | Zhijin Zhang | NULL | 9500 | vp | NULL | NULL | NULL |
| employee | 9 | NULL | NULL | NULL | NULL | NULL | NULL | 0 | NULL | NULL | NULL | 7 |
| customer | 10 | ?? | ?? | 233034 | ? | ?? | ???? | NULL | NULL | NULL | 7 | NULL |
+-------------+-----------+-----------------+----------------+-------------+--------+--------------+----------+--------+-------+------------+-------------+------------+
由於書中代碼使用了中文,所以產生了亂碼。person_type列指明瞭後面的數據分別屬於哪個實體類。由於不是所有的實體類都擁有全部的屬性,因此有很多空值。所以所有的子類都不能有非空約束。不過他也有它的好處。由於都在同一表中,因此查性能好。
連接子類的映射策略
使用這種策略需要在根類中使用@inheritance指明映射策略。它的strategy屬性支持InheritanceType的種類有三個,SINGLE_TABLE標識整個類層次對應一個表得映射策略,是默認值。JOINED是連接子類的映射策略。TABLE_PER_CLASS是每個具體類對一個一個表得映射策略。這種策略下我們使用JOINED。父類實體保存在父類自己的表中,而子類由父類表跟子類表共同儲存。也就是說共有的屬性在父類,獨有的纔在子類表。這種策略中不需要使用標識列了,取而代之的是@Inheritance(strategy=InheritanceType.JOINED)。所有的子表都是用person_id這個主鍵作爲外鍵。查詢的時候,從所有的表中找出主鍵相同的信息,然後拼在一起。子類也可以增加非空約束。
每個具體類對應一個表
數據不分割,每個子類都有一個表。父類表中沒有信息,每個子類表中都有它的全部屬性。這種方式很難看出繼承關係,只是多個實體之間的主鍵有某種連續性。因此主鍵不能夠用 IDENTITY或者AUTO。跟上面的一樣,在根類中使用@Inheritance修飾。由於不能使用自增長主鍵,因此我們這裏使用GenericGenerator註解指定使用hilo主鍵生成策略。