您的位置:首页 > 其它

Hibernate笔记――6.映射组件属性

2015-09-06 09:53 316 查看
当持久化的属性并不是基本数据类型,也不是字符串,日期等变量,而是一个复杂类型的对象,这个对象就称为组件属性。在持久化过程中,它仅仅被当做值类型,而并非引用另一个持久化类实体。组件属性的类型可以是任意的自定义类。
@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标注我们想要成为主键的属性即可。跟刚才的差不多,只不过这里不再是一个组件的两个属性,而是持久化类的两个属性。

本文出自 “指尖轻飞” 博客,谢绝转载!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: