当持久化的属性并不是基本数据类型,也不是字符串,日期等变量,而是一个复杂类型的对象,这个对象就称为组件属性。在持久化过程中,它仅仅被当做值类型,而并非引用另一个持久化类实体。组件属性的类型可以是任意的自定义类。
@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标注我们想要成为主键的属性即可。跟刚才的差不多,只不过这里不再是一个组件的两个属性,而是持久化类的两个属性。
本文出自 “指尖轻飞” 博客,谢绝转载!