欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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
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))
    @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();
    }