Hibernate入门(4):集合映射&组件映射

集合映射


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条目:
tablequestion
columnquestion_id (primary key)
question_content
tableanswer
columnanswer_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",                                            //标记保存映射的表
            joinColumns=@JoinColumn(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",                                            //标记保存映射的表
            joinColumns=@JoinColumn(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",                                            //标记保存映射的表
            joinColumns=@JoinColumn(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条目:
tablestudent
columnstudent_id (primary key)
student_name
tablescore
columnscore_id (primary key)
student_id (foreign key)
score_subject
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",
        joinColumns=@JoinColumn(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",
        joinColumns=@JoinColumn(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",                                            //标记保存映射的表
            joinColumns=@JoinColumn(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类映射的表如下:
tableuser
columnuser_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",
        joinColumns=@JoinColumn(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 是一个组件类;
tablestudent
columnstudent_id (primary key)
student_name
tablescore
columnscore_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",
        joinColumns=@JoinColumn(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;
tablestudent
columnstudent_id (primary key)
student_name
tablescore
columnscore_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",
        joinColumns=@JoinColumn(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();
    }          

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值