Hibernate筆記——6.映射組件屬性

當持久化的屬性並不是基本數據類型,也不是字符串,日期等變量,而是一個複雜類型的對象,這個對象就稱爲組件屬性。在持久化過程中,它僅僅被當做值類型,而並非引用另一個持久化類實體。組件屬性的類型可以是任意的自定義類。

@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標註我們想要成爲主鍵的屬性即可。跟剛纔的差不多,只不過這裏不再是一個組件的兩個屬性,而是持久化類的兩個屬性。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章