Hibernate入门(4):集合映射&组件映射
程序员文章站
2022-03-03 08:41:29
...
集合映射
1、Hibernate的集合映射包含2种:
①单纯的集合映射,包括List、Set、数组;②Map结构的集合映射;
2、Hibernate要求使用集合接口来声明集合属性,这是因为这些集合的实际实现是使用Hibernate的内部实现映射类;
3、Hibernate的集合映射实例具有值类型的行为,当持久化对象被保存时,这些集合属性会被持久化,当持久化对象被删除时,这些集合属性对应的记录会被删除;
4、2个持久化对象不能共享一个集合元素的引用;
5、集合映射使用的相关注解
1)@ElementCollection
该注解标注该属性为集合属性,支持的属性如下:
fetch |
指定实体对该集合的抓取策略,即当该实体初始化时,是否立即从数据库抓取集合中的所有元素,支持属性值: FetchType.EAGER(立即抓取,默认值),FecthType.LAZY(延迟抓取); |
targetClass | 指明集合属性种元素的类型 |
2)CollectionTable
该注解标注保存集合属性的表,支持常用的属性如下:
name | 指定保存集合属性的数据表的表名 |
joinColumns | 该属性为一个 @JoinColumn 数组,每个 @JoinColumn 映射一个外键列(一般只需要一个外键列,除非实体使用了复合主键) |
3)@JoinColumn
该注解标注用于定义一个外键列,常用的支持属性如下:
name | 指定外键列列名 |
columnDefinition | 使用指定的SQL片段创建外键列 |
nullable | 指定该列是否允许为null,默认值 true |
table | 指定该列的所在数据表的表名 |
unique | 指定是否为该列增加唯一约束,默认值 false |
referenceColumnName | 指定所参照的主键列的列名 |
4)@OrderColumn 和 @MepKeyColumn
@OrderColumn 用于定义 List、数组的索引列;
@MapKeyColumn 用于定义映射 Map 集合的索引列;
List 映射
假设需要映射的表的结构如下,建立Question类时,其中有一个属性包含所有与之关联的answer条目:
table | question |
column | question_id (primary key) |
question_content |
table | answer |
column | answer_id (primary key) |
question_id (foreign key) | |
answer_content |
Question.java
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name="question")
public class Question {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="question_id")
private int id;
@Column(name="question_content")
private String content;
//List 集合属性,保存问题的答案内容
@ElementCollection(targetClass = String.class) //标记属性为集合属性
@CollectionTable(name="answer", //标记保存映射的表
[email protected](name="question_id",nullable = false)) //标记该表的外键列,及其他参数
@Column(name="answer_content") //指定集合要保存元素
@OrderColumn(name="list_order") //映射集合元素索引的列(自动创建)
private List<String> answers = new ArrayList<>();
//省略所有get,set方法
}
以下是一个简单的使用示例,其中HibernateUtil是一个抽离的Session管理类,代码见:http://blog.csdn.net/al_assad/article/details/77887748#HibernateUtil
QuestionManager.java
public class QuestionManager {
public void main (String[] args){
Session session = HibernateUitl.currentSession();
Transaction tran = session.beginTransaction();
Question question = new Question();
question.setContent("Are you ok?");
question.getAnswers().add("fine");
question.getAnswers().add("not good");
session.save(question);
tran.commit();
HibernateUitl.closeSession();
}
}
数组 映射
数组映射类似于List映射,它和List的区别在于,前者长度可变,后者长度不可变,因此使用数组时无法延迟加载该集合元素;
同样使用以上的数据表示例:
Question.java
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name="question")
public class Question {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="question_id")
private int id;
@Column(name="question_content")
private String content;
//List 集合属性,保存问题的答案内容
@ElementCollection(targetClass = String.class) //标记属性为集合属性
@CollectionTable(name="answer", //标记保存映射的表
[email protected](name="question_id",nullable = false)) //标记该表的外键列,及其他参数
@Column(name="answer_content") //指定集合要保存元素
@OrderColumn(name="array_order") //映射集合元素索引的列(自动创建)
private String[] answers;
//省略所有get,set方法
}
Set 映射
Set 映射是无序不重复的,所以Set集合属性无需使用 @OrderColumn 注解映射元素的索引列;
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name="question")
public class Question {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="question_id")
private int id;
@Column(name="question_content")
private String content;
//List 集合属性,保存问题的答案内容
@ElementCollection(targetClass = String.class) //标记属性为集合属性
@CollectionTable(name="answer", //标记保存映射的表
[email protected](name="question_id",nullable = false)) //标记该表的外键列,及其他参数
@Column(name="answer_content") //指定集合要保存元素
private String[] answers = new HashMap<>();
//省略所有get,set方法
}
Map 映射
Map 映射需要使用 @MapKeyColumn 保存 Map Key 索引列,对于 Map 映射,Hibernate 以外键列和key列作为联合主键;
以下为示例用的数据表,,建立Student类时,其中有一个属性包含所有与之关联的score条目:
table | student |
column | student_id (primary key) |
student_name |
table | score |
column | score_id (primary key) |
student_id (foreign key) | |
score_subject | |
score_mark |
import javax.persistence.*;
import java.util.HashMap;
import java.util.Map;
@Entity
@Table(name="student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="student_id")
private int id;
@Column(name="student_name")
private String name;
@ElementCollection(targetClass = Float.class)
@CollectionTable(name="score",
[email protected](name="student_id",nullable = false))
@MapKeyColumn(name="score_subject") //指定Map Key储存列
@MapKeyClass(String.class) //指定Map Key 类型
@Column(name="score_mark")
private Map<String,Float> scores = new HashMap<>();
//省略get、set方法
}
SortedSet 和 SortedMap 映射
对于这2个Hibernate的有序集合,他们的行为类似 java.util.TreeSet 和 java.util.TreeMap ;
需要使用 @SortNatural 或 @SortComparator注解,前者对集合使用自然排序,后者使用自定义的排序;
这些排序仅仅表现在查询操作返回的结果上,不会影响数据库中的数据排列;
示例如下:
import javax.persistence.*;
import java.util.HashMap;
import java.util.Map;
@Entity
@Table(name="student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="student_id")
private int id;
@Column(name="student_name")
private String name;
@ElementCollection(targetClass = Float.class)
@CollectionTable(name="score",
[email protected](name="student_id",nullable = false))
@MapKeyColumn(name="score_subject") //指定Map Key储存列
@MapKeyClass(String.class) //指定Map Key 类型
@Column(name="score_mark")
@SortedNatural //采用自然排序
private SortedMap<String,Float> scores = new TreeMap<>();
//省略get、set方法
}
要实现返回的集合元素重新排序,除了以上直接映射为有序集合外,亦可以使用hibernate提供的 @OrderBy 注解将一般的集合类型排序,该注解只能在JDK1.4及以上使用(底层使用LinedHashSet或LinedHashMap实现)
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name="question")
public class Question {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="question_id")
private int id;
@Column(name="question_content")
private String content;
//List 集合属性,保存问题的答案内容
@ElementCollection(targetClass = String.class) //标记属性为集合属性
@CollectionTable(name="answer", //标记保存映射的表
[email protected](name="question_id",nullable = false)) //标记该表的外键列,及其他参数
@Column(name="answer_content") //指定集合要保存元素
@OrderBy("answer_id asc")
private String[] answers = new HashMap<>();
//省略所有get,set方法
}
各种集合映射的性能分析
1、Hibernate 默认使用延迟加载以获取较好的性能;
2、有序集合在增删改中拥有比无序集合更好的性能,通常情况下,List、Map集合性能要高于Set;
3、由于数组集合无法改变长度,所以无法延迟加载,所以数组集合的性能一般会比较低;
组件映射
组件映射,即持久化类的属性不是基本的数据类型、String、日期类型,而是一个复合类型的对象,在持久化过程中,这个复合对象仅仅作为值类型,并非引用另一个持久化实体;
普通组件属性映射
如以下示例中,实体User类中包含一个复合类型Name,User类映射的表如下:
table | user |
column | user_id (primary key) |
user_age | |
user_firstname | |
user_lastname |
User.java
import javax.persistence.*;
@Entity
@Table(name="user")
public class User {
@Id
@Column(name="user_id")
private int id;
@Column(name="user_age")
private int age;
//组件属性
private Name name;
//省略get,set
}
Name.java
import org.hibernate.annotations.Parent;
import javax.persistence.*;
@Embeddable //标注该类为实体组件
public class Name {
@Column(name="user_firstname")
private String firstname;
@Column(name="user_lastname")
private String lastname;
@Parent //标注该组件所属的实体类
private User owner;
public Name() {
}
public Name(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
//省略get,set
}
组件中包含集合属性
同样地,在组件中也可以包含集合元素,如以上示例中,假如 Name 组件类包含了一个 points 属性(名称的一个各方面的计分,仅仅用于示例的演示),该属性来自表"point"
则 Name.java 建模如下:
import org.hibernate.annotations.Parent;
import javax.persistence.*;
@Embeddable //标注该类为实体组件
public class Name {
@Column(name="user_firstname")
private String firstname;
@Column(name="user_lastname")
private String lastname;
@Parent //标注该组件所属的实体类
private User owner;
//组件 Name 中包含的集合属性
@ElementCollection(targetClass = Float.class)
@CollectionTable(name="point",
[email protected](name="name_id",nullable = false))
@MapKeyColumn(name="name_aspect")
@MapKeyClass(String.class)
@Column(name="name_score")
private Map<String,Float> scores = new HashMap<>();
public Name() {
}
public Name(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
//省略get,set
}
组件作为集合属性的元素
集合除了可以存放基本类型,日期,String之外,也可以存放组件类型(即复合类型);
以下示例使用以上Map映射的例子,使用 List<Score>代替Map<String,Float>储存分数数据,其中 Score 是一个组件类;
table | student |
column | student_id (primary key) |
student_name |
table | score |
column | score_id (primary key) |
student_id (foreign key) | |
score_subject | |
score_mark |
Student.java
@Entity
@Table(name="student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="student_id")
private int id;
@Column(name="student_name")
private String name;
//集合中的元素为组件Score
@ElementCollection(targetClass = Float.class)
@CollectionTable(name="score",
[email protected](name="student_id",nullable = false))
@OrderColumn(name="list_order")
private List<Score> scores = new ArrayList<>();
}
Scores.java
@Embeddable
public class Score {
@Column(name="score_subject")
private String subject; //学科
@Column(name="score_mark")
private float mark; //分数
@Parent
private Student owner;
public Score(){}
public Score(String subject, float mark) {
this.subject = subject;
this.mark = mark;
}
//省略 get,set
}
组件作为Map索引
组件也可以作为Map的索引或值存在,以下示例使用组件作为 Map key;
table | student |
column | student_id (primary key) |
student_name |
table | score |
column | score_id (primary key) |
student_id (foreign key) | |
score_subject_code | |
score_subject_name | |
score_mark |
Student.java
import javax.persistence.*;
import java.util.HashMap;
import java.util.Map;
@Entity
@Table(name="student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="student_id")
private int id;
@Column(name="student_name")
private String name;
@ElementCollection(targetClass = Float.class)
@CollectionTable(name="score",
[email protected](name="student_id",nullable = false))
@MapKeyClass(Subject.class) //指定Map Key 类型
@Column(name="score_mark")
private Map<Subject,Float> scores = new HashMap<>();
//省略get、set方法
}
Subject.java
import org.hibernate.annotations.Parent;
import javax.persistence.*;
@Embeddable
public class Subject {
@Column(name="socre_subject_name")
private String name;
@Column(name="socre_subject_code")
private int code;
@Parent
private Student owner;
//省略构造器
//省略 get,set
}
组件作为复合主键
一般数据库会采用简单逻辑主键,但在一些特殊情况下,会出现组件类型的复合组件,即使用组件作为持久化类的标识符,这些组件类必须满足以下条件:
①有无参构造器;
②必须实现 java.io.Serializable 接口;
③建议重写 equals 和 hashCode方法;
以下是一个示例的作为复合主键的组件示例:
Name.java
import org.hibernate.annotations.Parent;
import javax.persistence.*;
import java.io.Serializable;
@Embeddable
public class Name implements Serializable {
@Column(name="user_firstname")
private String firstname;
@Column(name="user_lastname")
private String lastname;
@Parent
private User owner;
//省略get,set方法
//省略构造器
public boolean equals(Object obj){
if(this == obj)
return true;
if(obj != null && obj instanceof Name){
Name target = (Name)obj;
return target.getFirstname().equals(this.getFirstname())
&& target.getLastname().equals(this.getLastname());
}
return false;
}
public int hashCode(){
return getFirstname().hashCode() * 31 + getLastname().hashCode();
}
}
也可以使用多列作为复合主键;
User.java
@Entity
@Table(name="user")
public class User {
@Id
@Column(name="user_firstname")
private int firstname;
@Id
@Column(name="user_lastname")
private int lastname;
...
//省略get,set
public boolean equals(Object obj){
if(this == obj)
return true;
if(obj != null && obj instanceof Name){
Name target = (Name)obj;
return target.getFirstname().equals(this.getFirstname())
&& target.getLastname().equals(this.getLastname());
}
return false;
}
public int hashCode(){
return getFirstname().hashCode() * 31 + getLastname().hashCode();
}