當持久化的屬性並不是基本數據類型,也不是字符串,日期等變量,而是一個複雜類型的對象,這個對象就稱爲組件屬性。在持久化過程中,它僅僅被當做值類型,而並非引用另一個持久化類實體。組件屬性的類型可以是任意的自定義類。
@Entity @Table(name="persona_inf") public class PersonA { @Id @Column(name="person_id") @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private int age; private Name name; }
@Embeddable public class Name { @Column(name="person_firstname") private String first; @Column(name="person_lastname") private String last; @Parent private PersonA owner; public Name(){} public Name(String first, String last){ this.first=first; this.last=last; } }
private static void createAndStorePersons() { Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); PersonA p=new PersonA(); p.setAge(99); p.setName(new Name("Alvin","Meng")); session.save(p); tx.commit(); session.close(); }
mysql> select * from persona_inf;
+-----------+-----+------------------+-----------------+
| person_id | age | person_firstname | person_lastname |
+-----------+-----+------------------+-----------------+
| 1 | 99 | Alvin | Meng |
+-----------+-----+------------------+-----------------+
1 row in set (0.00 sec)
我們注意到Person的name屬性不是基本類型或者String,而是自定義類。不但無法指定列明,也無需任何標註。爲了讓PersonA與Name建立起聯繫,我們在Name中使用@Embaddable來指定這是一個組件。然後標註兩列。再使用@Parent指明誰是它的擁有者。最後還要提供兩個構造器。爲什麼需要構造器呢?因爲在給name設值的時候並沒有顯式地new一個實例變量,因此需要使用構造器自動創建。從數據庫中的表結構我們可以看出,Hibernate會把Name的每一個屬性映射稱爲一個數據列。
Hibernate還提供了另一種組件映射策略。這種策略無需再組件雷尚使用@Embeddable註解,而只需要在持久化類中使用@Embedded註解修飾組件屬性。然後再使用@AttributeOverrides來指明組件屬性的屬性值。每個屬性值再用@AttributeOverride標註,可以有name,column等。
@Entity @Table(name = "personb_inf") public class PersonB { @Id @Column(name = "person_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private int age; @Embedded @AttributeOverrides({ @AttributeOverride(name = "first", column = @Column(name = "person_first")), @AttributeOverride(name = "last", column = @Column(name = "person_lastname")) }) private Name name; }
如果這樣配置,那麼Name類就是一個普通的bean類。沒有Parent,也沒有任何註解(表頭還是需要@Embeddable),只有兩個屬性跟set,get方法,還有兩個構造器。
組合屬性爲集合
如果組件類裏面又包括了List、Set、Map等集合屬性,則可以直接在組件中使用@ElementCollection修飾集合屬性,並使用@CollectionTable指定保存集合屬性的表明。在上面例子的基礎上,我們再給Name屬性增加一個Map類型的Power屬性。
@Embeddable public class Name { @Column(name="first_name") private String first; @Column(name="last_name") private String last; @ElementCollection(targetClass=Integer.class) @CollectionTable(name="power_inf") @MapKeyColumn(name="name_aspect") @Column(name="name_power",nullable=false) @MapKeyClass(String.class) private Map<String,Integer> power=new HashMap<>(); public Name(){} public Name(String first, String last){ this.first=first; this.last=last; } }
@Entity @Table(name = "personc_inf") public class PersonB { @Id @Column(name = "person_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private int age; private Name name; }
private static void createAndStorePersons() { Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); PersonB p=new PersonB(); p.setAge(99); Name n=new Name("meng","cao"); n.getPower().put("math", 100); n.getPower().put("history", 100); n.getPower().put("art", 100); p.setName(n); session.save(p); tx.commit(); session.close(); }
如果Name類中沒有顯示地實例化一個Map對象,那麼這裏就要先實例化對象,然後設置,然後再設給Name的Map屬性了。如此運行,數據庫中就有了兩張表。personc_inf裏面記錄了id號,firstname與lastname。而是用id作爲外鍵的power表裏面有了我們設置的三行數據。所以,Hibernate依然將Name屬性映射成兩列,而Name屬性的Map屬性則是是用額外的表來存儲,並且建立主鍵外鍵關係。由此可見雖然Map是Name的屬性,但從鍵上看它也是Person的屬性。
集合屬性的元素爲組件
再放一波大招,集合元素除了可以保存上述的String跟int,還可以保存組件對象。實際上更多的情況下集合裏面保存的都是組件對象。對於集合元素是組件的集合屬性,我們仍然使用@ElementCollection標註這是一個集合,使用@CollectionTable指定使用哪個表來保存,使用@OrderColumn或者對於Map使用@MapKeyColumn指定映射索引。不同的是程序不再使用@Column映射保存集合元素的數據列,因爲我們無法使用單獨的數據列保存一個集合元素!跟上上面的例子一樣, 我們只需要使用@Embeddable來指定一個組件類即可。
@Entity @Table(name="persond_inf") public class PersonD { @Id @Column(name="person_id") @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private int age; @ElementCollection(targetClass=Score.class) @CollectionTable(name="scored_inf",joinColumns=@JoinColumn(name="person_id",nullable=false)) @MapKeyColumn(name="subject_name") @MapKeyClass(String.class) private Map<String,Score> scores=new HashMap<>(); @ElementCollection(targetClass=Name.class) @CollectionTable(name="nick_inf",joinColumns=@JoinColumn(name="person_id",nullable=false)) @OrderColumn(name="list_order") private List<Name> nicks=new ArrayList<>(); }
@Embeddable public class Score { @Column(name="score_level") private String level; @Column(name="score_number") private int number; public Score(){} public Score(String level,int number){ this.level=level; this.number=number; } }
@Embeddable public class Name { @Column(name="person_firstname") private String first; @Column(name="person_lastname") private String last; @Parent private PersonD owner; public Name(){} public Name(String first,String last){ this.first=first; this.last=last; } }
mysql> select * from persond_inf;
+-----------+-------+
| person_id | age |
+-----------+-------+
| 1 | 10000 |
| 2 | 6 |
+-----------+-------+
mysql> select * from scored_inf;
+-----------+-------------+--------------+--------------+
| person_id | score_level | score_number | subject_name |
+-----------+-------------+--------------+--------------+
| 1 | C | 74 | firstyear |
| 1 | A | 99 | secondyear |
| 2 | B- | 88 | firstyear |
| 2 | F | 49 | secondyear |
+-----------+-------------+--------------+--------------+
mysql> select * from nick_inf;
+-------------+------------------------+------------------------+-------------+
| person_id | person_firstname | person_lastname | list_order |
+-------------+------------------------+------------------------+-------------+
| 1 | meng | cao | 0 |
| 2 | jing | shi | 0 |
| 2 | hui | yu | 1 |
+-------------+------------------------+-------------------------+------------+
如圖所示,主表是persond_inf,裏面有兩條記錄。裏面有兩列,第一列是自增長主鍵。其實我們可以想象它還有另外兩列,其中一個保存了一個Map集合指定scored,另一個保存了一個List集合指定nick。對於每個personid,scored表是Map集合,裏面的subject_name是Map的key,而value則是一個Score對象,它有level跟number兩個屬性。事實上我們也可以指定更多屬性。沒錯,這裏有用到了之前我們講的知識,如果一個集合裏面裝的是基本類型,那麼就用列來代表這些屬性吧。nick表示一個List,裏面裝了Name這個類,用兩列表示。而list_order則是有序集合List的索引,用來跟person_id組成聯合主鍵。爲啥Map裏面沒有索引組成聯合主鍵呢?有的!Map中使用的不是OrderColumn,而是MapKeyColumn,使用的是key,而不是自增長的數字。因此map中是key與外鍵組成聯合主鍵。
組件作爲Map的索引
由於Map集合的特殊性,它允許使用一個符合類型的對象作爲它的key。對於這種情況,依然使用@ElementCollection修飾集合屬性,使用@CollectionTable指定保存表。由於索引是一個類,因此我們使用@MapKeyClass註解指定key的類型。
@Entity @Table(name="persone_inf") public class PersonE { @Id @Column(name="person_id") @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private int age; @ElementCollection(targetClass=Score.class) @CollectionTable(name="nick_power_inf",joinColumns=@JoinColumn(name="person_id",nullable=false)) @Column(name="nick_power",nullable=false) @MapKeyClass(Name.class) private Map<Name,Integer> nickPower=new HashMap<Name,Integer>(); }
@Embeddable public class Name { @Column(name = "person_firstname") private String first; @Column(name = "person_lastname") private String last; @Parent private PersonD owner; public Name() {} public Name(String first, String last) { this.first = first; this.last = last; } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj != null && obj.getClass() == Name.class) { Name target = (Name) obj; return target.getFirst().equals(getFirst()) && target.getLast().equals(getLast()); } return false; } public int hashCode() { return getFirst().hashCode() * 31 + getLast().hashCode(); } }
持久化類使用Name對象作爲Map的key,所以程序應該重寫Name類的equals()和hashCode()兩個方法。除此之外,程序還使用@Embeddable修飾組件類。
mysql> select * from persone_inf;
+-----------+-----+
| person_id | age |
+-----------+-----+
| 1 | 6 |
+-----------+-----+
mysql> select * from nick_power_inf;
+-----------+------------+-------+------+
| person_id | nick_power | first | last |
+-----------+------------+-------+------+
| 1 | 23 | meng | cao |
| 1 | 24 | jing | shi |
+-----------+------------+-------+------+
mysql> desc nick_power_inf;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| person_id | int(11) | NO | PRI | NULL | |
| nick_power | int(11) | NO | | NULL | |
| first | varchar(255) | NO | PRI | | |
| last | varchar(255) | NO | PRI | | |
+------------+--------------+------+-----+---------+-------+
圖中我們能夠看到,主表中只有兩列,一個是自動生成的主鍵,另一個是普通age屬性。其實可以想象它還有一列,用於保存Map集合。Map集合中key是一個類,因爲它有兩個屬性,所以是用兩列來保存。它的value是nick_power。注意代碼中的@ElementCollection裏面的targetClass應該制定value的類型!集合裏存什麼,就是什麼class!不要覺得list只有一列,Map有key跟value兩列!有了索引之後都是兩列了!!!md我寫代碼時候沒注意報錯debug了好久。通過description我們發現這裏使用了外鍵,first跟last作爲聯合主鍵!
組件作爲複合主鍵
簡單的邏輯主鍵不涉及這個問題。當組件作爲主鍵時,才涉及!作爲標識符的組件必須有無參數的構造器,實現Serializable接口!建議重寫equals和hashCode方法。Hibernate4中第二條不必要。組件作爲主鍵,Hibernate無法爲複覈主鍵自動生成鍵值,所以程序必須爲持久化實例分配這種組件標識符。
@Entity @Table(name="persone_inf") public class PersonE { @EmbeddedId @AttributeOverrides({ @AttributeOverride(name="first",column=@Column(name="person_firstname")), @AttributeOverride(name="last",column=@Column(name="person_lastnnnmae")) }) private int age; }
public class AName implements Serializable { private String first; private String last; public AName(){} public AName(String first,String last){ this.first=first; this.last=last; } public boolean equals(Object obj) { if (this == obj) { return true; } if (obj != null && obj.getClass() == Name.class) { Name target = (Name) obj; return target.getFirst().equals(getFirst()) && target.getLast().equals(getLast()); } return false; } public int hashCode() { return getFirst().hashCode() * 31 + getLast().hashCode(); } }
private static void createAndStorePersons() { Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); PersonE p=new PersonE(); p.setAge(6); p.setName(new AName("aa","bb")); session.save(p); tx.commit(); session.close(); }
mysql> select * from persone_inf;
+------------------+-------------------+-----+
| person_firstname | person_lastnnnmae | age |
+------------------+-------------------+-----+
| aa | bb | 6 |
+------------------+-------------------+-----+
mysql> desc persone_inf;
+-------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+--------------+------+-----+---------+-------+
| person_firstname | varchar(255) | NO | PRI | NULL | |
| person_lastnnnmae | varchar(255) | NO | PRI | NULL | |
| age | int(11) | NO | | NULL | |
+-------------------+--------------+------+-----+---------+-------+
數據表中可以看到主鍵變成了first跟lastname。代碼中我們使用@EmbeddedId來標識這個AName類型的表示屬性,然後配置列與底層數據庫的映射關係。由於標識屬性不能簡單地自增,所以test類中我們必須手動地設置。
多列作爲聯合主鍵
Hibernate還提供了另一種聯合主鍵支持,允許將持久化類的多個屬性映射成聯合主鍵。如果想要這樣做,持久化類必須有無參數的構造器,實現Serializable接口,重寫equals和hashCode方法。我們只需要簡單地使用@Id標註我們想要成爲主鍵的屬性即可。跟剛纔的差不多,只不過這裏不再是一個組件的兩個屬性,而是持久化類的兩個屬性。