Hibernate入门2-关联和映射

2018-05-09  本文已影响0人  sunblog

Hibernate 快速入门2 - 关联映射和类继承

2 关联映射

我们知道两个表A、B的映射关系有 1-1, 1-N, M-N

对于每种映射关系,还可以分为单向和双向。即单向 1-1, 其中一端不能访问另外一端。 双向1-1,两端能互访。对于其他映射关系也是一样的。

从编码的角度来说,单向的意思是,A含有类型为B的成员变量,而B没有类型为A的成员变量。双向的意思是,,A含有类型为B的成员变量, B也含有类型为A的成员变量。

下面我们来一一讲解

2.1.1 单向1-1 (A含有B,B不含有A)

对于单项1-1映射,我们用@OneToOne来指明另一端的类,用@JoinColumn来指定外键的关联。

增加一个Professor类。假设一个Professor只教育一个学生,一个学生也只由一个Professor教育。现在我们假设只有Professor能查看学生的信息,学生不能查看Professor的信息.

Student类不变,下面是Professor类:

@Entity
public class Professor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "professor_id")
    private Integer id;

    private String name;

    @OneToOne(targetEntity = Student.class)
    // FOREIGN KEY (student_id) REFERENCES student_info (id);
    @JoinColumn(name = "student_id", referencedColumnName = "id")
    private Student student;
    
    // getters and setters
}
public static void addProfessor(Integer student_id) {
    Configuration conf = new Configuration().configure();
    StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
    try (SessionFactory sessionFactory = conf.buildSessionFactory(registry); Session session = sessionFactory.openSession()) {
        Transaction transaction = session.beginTransaction();

        Professor professor = new Professor();
        professor.setName("james lee");

        Student student = session.get(Student.class, student_id);
        professor.setStudent(student);

        session.save(professor);
        transaction.commit();
    }
}

public static void main(String[] args) {
    addProfessor(10);
}

addProfessor(10)传入10是因为student_info中有id = 10的数据。

启动的时候,我们会发现抛异常了:
Exception in thread "main" org.hibernate.MappingException: Unknown entity: com.example.test.Professor

原来是我们忘了在hibernate.cfg.xml中添加映射类的了。

<mapping class="com.example.test.Professor"/>

添加完后成功启动,我们可以看到新建的professor表:

mysql> select * from professor;
+--------------+-----------+------------+
| professor_id | name      | student_id |
+--------------+-----------+------------+
|            1 | james lee |         10 |
+--------------+-----------+------------+
1 row in set (0.00 sec)

student_info表是没有任何改变的,我这里就不贴出来了。

对Professor.student属性,可以省略@JoinColumn,hibernate会自动生成一个属性来代表Student。但是最好不要这么做。

注意,OneToOne映射,为两个类都建立了表。而在前面一节中,我们把BodyInfo作为Student的一个属性来,BodyInfo用@Embeddable标注了。结果是BodyInfo的所有字段(都是非集合字段)都合并到了Student表中。

2.1.2 双向1-1 (A、B互相包含)

首先,mysql用root登入,先把hibernatedb,删了重新建过,然后再赋予ALL权限给hibernate,向第一章那样。一定要这么做,不然不能正常插入数据。

Professor.java修改如下:

@Entity
public class Professor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "professor_id")
    private Integer id;

    private String name;

    @OneToOne(targetEntity = Student.class, mappedBy = "professor")
    // FOREIGN KEY (student_id) REFERENCES student_info (id);
//    @JoinColumn(name = "student_id", referencedColumnName = "id")
    private Student student;
}    

mappedBy表示,Professor放弃控制权,由Student来控制。在SQL上的表现就是,Professor表没专门为此生成的属性和外键。mappedBy = "professor",这个"professor"表示,Student类中名字为professor的属性存在。即Student.professor这个成员变量存在(其实只要保证Student.getProfessor(), Student.setProfessor(Professor)存在即可)。

Student.java修改如下:

@Entity
@Table(name = "student_info")
public class Student {
    @OneToOne(targetEntity = Professor.class)
    @JoinColumn(name = "professor_id", referencedColumnName = "professor_id", unique = true)
    private Professor professor;
}

HibernateTest.java修改如下:

public static void biOneToOne() {
    Configuration conf = new Configuration().configure();
    StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
    try (SessionFactory sessionFactory = conf.buildSessionFactory(registry); Session session = sessionFactory.openSession()) {
        Transaction transaction = session.beginTransaction();

        Professor professor = new Professor();
        professor.setName("professor name one2one");

        session.save(professor);

        Student student = new Student();
        student.setName("student name one2one");
        student.setProfessor(professor);

        session.save(student);

        transaction.commit();
    }
}
public static void main(String[] args) {
    biOneToOne();
}

MySQL输出如下:

mysql> select * from student_info;
+----+-----+--------+--------+----------------------+--------------+
| id | age | height | weight | name                 | professor_id |
+----+-----+--------+--------+----------------------+--------------+
|  1 |   0 |   NULL |   NULL | student name one2one |            1 |
+----+-----+--------+--------+----------------------+--------------+
1 row in set (0.00 sec)

mysql> select * from professor;
+--------------+------------------------+
| professor_id | name                   |
+--------------+------------------------+
|            1 | professor name one2one |
+--------------+------------------------+
1 row in set (0.00 sec)

mysql> show create table student_info;
CREATE TABLE `student_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `age` int(11) NOT NULL,
  `height` int(11) DEFAULT NULL,
  `weight` int(11) DEFAULT NULL,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `professor_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_gg8r834cvr56est49a2tlw8an` (`professor_id`),
  CONSTRAINT `FKmudc1uutqukeete7gravfalew` FOREIGN KEY (`professor_id`) REFERENCES `Professor` (`professor_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

mysql> show create table professor;
CREATE TABLE `professor` (
  `professor_id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`professor_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

可以看到professor上面的确没有Student的任何信息,因为它已经用mappedBy声明放弃控制。

2.1.3 单向N - 1 (A含有B,B不含有A)

比如一个教授可以教授多个学生,每个学生只能由一个教授指导。

我们当然可以让一个教授包含多个学生,就像前一章中映射集合一样。但这里,Student是一个实体(Entity),在语境上不符合作为Embeddable。因为Student完全可能和Professor的其他Entity产生关系。

为了简单起见,我们把Student的集合属性和组合属性都删掉。然后重新hibernatedb,重建。

Student.java

@Entity
@Table(name = "student_info")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    private int age;

    @ManyToOne(targetEntity = Professor.class)
    @JoinColumn(name = "professor_id", referencedColumnName = "professor_id", nullable = false) // remove unique=true!!!
    @Cascade(org.hibernate.annotations.CascadeType.ALL)
    private Professor professor;
    
    // getter and setters
}

Professor.java

@Entity
public class Professor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "professor_id")
    private Integer id;

    private String name;
}

HibernateTest.java

public static void uniManyToOne() {
    Configuration conf = new Configuration().configure();
    StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
    try (SessionFactory sessionFactory = conf.buildSessionFactory(registry); Session session = sessionFactory.openSession()) {
        Transaction transaction = session.beginTransaction();

        Professor professor = new Professor();
        professor.setName("professor name one2one");

        Student student = new Student();
        student.setName("student name one2one");
        student.setProfessor(professor);

        Student student2 = new Student();
        student2.setName("student2");;
        student2.setProfessor(professor);

        session.save(student2);

        transaction.commit();
    }
}

public static void main(String[] args) {
    uniManyToOne();
}    

数据库输出:

mysql> select * from professor;
+--------------+------------------------+
| professor_id | name                   |
+--------------+------------------------+
|            1 | professor name one2one |
+--------------+------------------------+
1 row in set (0.00 sec)

mysql> select * from student_info;
+----+-----+----------------------+--------------+
| id | age | name                 | professor_id |
+----+-----+----------------------+--------------+
|  1 |   0 | student name one2one |            1 |
|  2 |   0 | student2             |            1 |
+----+-----+----------------------+--------------+
2 rows in set (0.00 sec)

mysql> show create table professor;
CREATE TABLE `professor` (
  `professor_id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`professor_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

mysql> show create table student_info;
CREATE TABLE `student_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `age` int(11) NOT NULL,
  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `professor_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `FKmudc1uutqukeete7gravfalew` (`professor_id`),
  CONSTRAINT `FKmudc1uutqukeete7gravfalew` FOREIGN KEY (`professor_id`) REFERENCES `Professor` (`professor_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

可以看到professor是没有任何外键的,student_info有一个外键,引用了professor.id。这就是对于关系映射N-1的情况下,我们应该设计的数据的样子。

@Cascade: 指定映射的级联。如果没有这个注解,那我们就必须先save(professor),才能save(student)。

2.1.4 单向 1 - N

hibernate不推荐控制权在1端,而是推荐控制权在N端,也就是推荐使用前面的单向N-1。

2.1.5 双向1-N (A含有元素类型为B的属性,B含有A)

双向1-N和双向N-1都是一样的。

先删除前面建的表(drop table xxx)。

双向的1-N和前面的单向N-1十分类似。只需要修改Professor类:

@Entity
public class Professor {
    @OneToMany(targetEntity = Student.class, mappedBy = "professor")
    private List<String> studentList;
//    private Student student;
}

输出结果和上面都是一样的。这里就不重复了。

值得提一下的是:

HibernateTest.java

public static void searchProfessor(Integer id) {
    Configuration conf = new Configuration().configure();
    StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
    try (SessionFactory sessionFactory = conf.buildSessionFactory(registry); Session session = sessionFactory.openSession()) {
        Transaction transaction = session.beginTransaction();
        Professor professor = session.get(Professor.class, id);
        if (professor != null) {
            System.out.println("student name: " + professor.getStudentList());
        }
        transaction.commit();
    }
}

其中参数Id是数据库中的一个id。如果运行这个函数的话,能正确的输出Professor对应的student list。

2.1.6 单向N-M (A含有B)

在控制的一段添加ManyToMany


@Entity
@Table(name = "student_info")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    private int age;

    @ManyToMany(targetEntity = Professor.class)
    @JoinColumn(name = "professor_id", referencedColumnName = "professor_id", nullable = false)
    private List<Professor> professorList = new ArrayList<>();
}

@Entity
public class Professor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "professor_id")
    private Integer id;

    private String name;
}

Transaction transaction = session.beginTransaction();

Professor professor = new Professor();
professor.setName("professor1");
session.save(professor);

Professor professor2 = new Professor();
professor2.setName("professor2");
session.save(professor2);

Student student = new Student();
student.setName("student name one2one");
student.getProfessorList().add(professor);
student.getProfessorList().add(professor2);

session.save(student);

Student student2 = new Student();
student2.setName("student2");
student2.getProfessorList().add(professor);
student2.getProfessorList().add(professor2);

session.save(student2);

transaction.commit();

数据库输出:

mysql> select * from student_info;
+----+-----+----------------------+
| id | age | name                 |
+----+-----+----------------------+
|  1 |   0 | student name one2one |
|  2 |   0 | student2             |
+----+-----+----------------------+
2 rows in set (0.01 sec)

mysql> select * from professor;
+--------------+------------+
| professor_id | name       |
+--------------+------------+
|            1 | professor1 |
|            2 | professor2 |
+--------------+------------+
2 rows in set (0.00 sec)

mysql> select * from student_info_Professor;
+------------+----------------------------+
| Student_id | professorList_professor_id |
+------------+----------------------------+
|          1 |                          1 |
|          1 |                          2 |
|          2 |                          1 |
|          2 |                          2 |
+------------+----------------------------+
4 rows in set (0.00 sec)

mysql> show create table student_info_Professor;

CREATE TABLE `student_info_Professor` (
  `Student_id` int(11) NOT NULL,
  `professorList_professor_id` int(11) NOT NULL,
  KEY `FKoboctn2nj4vqepv9lngaatdc9` (`professorList_professor_id`),
  KEY `FKh6rxyclu4wmcpuqi215m6rwd9` (`Student_id`),
  CONSTRAINT `FKh6rxyclu4wmcpuqi215m6rwd9` FOREIGN KEY (`Student_id`) REFERENCES `student_info` (`id`),
  CONSTRAINT `FKoboctn2nj4vqepv9lngaatdc9` FOREIGN KEY (`professorList_professor_id`) REFERENCES `Professor` (`professor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

2.1.7 双向N-N (A含有B、B也含有A)

@Entity
public class Professor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "professor_id")
    private Integer id;

    private String name;

    @ManyToMany(targetEntity = Student.class)
    @JoinTable(joinColumns = @JoinColumn(name = "professor_id", referencedColumnName = "professor_id"))
    private List<String> studentList;
}

@Entity
@Table(name = "student_info")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    private int age;

    @ManyToMany(targetEntity = Professor.class)
    @JoinTable(joinColumns = @JoinColumn(name = "id", referencedColumnName = "id"))
//    @Column(name = "student_id")
    private List<Professor> professorList = new ArrayList<>();

其他的和单向N-N一样。

@JoinTable:指定用连接表来保存映射关系(像前面的student_info_Professor)。需要注意的是,里面的@JoinColumn,为什么这里和以前的不一样,以前的JoinColumn(name, referencedColumnName)都是不同的,而这里为什么不一样?JoinColumn的意思是,本段的name属性应用了另一个表的referencedColumnName。由于这里指定了@JoinTable,也就是指定了用一个连接表,那么另一个表指的就是连接表。我们当然应该表这个表中的name属性和连接表中的name属性映射起来。即A.a 映射到连接表AB.a, B.b映射到连接到AB.b。

3 类继承

比如我们前面的Student代表的是大学生,它在数据库中有一个对应的表。 现在新建它的一个子类PostGraduate(研究生),那么对于PostGraduate在数据库中的存在有几种呢?

对于类的继承,我们有如下几种方式:

3.1 子类和父类共用一表 (Hibernate默认实现方式)

考虑一下,如果子类和父类共用一个表,那我们势必需要在表中添加一个属性,标识是父类还是子类。这个属性叫做辨别者列(discriminator),我们用@DiscriminatorColumn注解来标识辨别者列。对于每个类在辨别者列上的取值,我们用@DiscirminatorValue指定。

先来看看重定义的Student:

@Entity
@DiscriminatorColumn(name = "student_type", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue("student")
@Table(name = "student_info")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    private int age;
}

新定义一个PostGraduate继承Student:

@Entity
@DiscriminatorValue("post_gradudate")
public class PostGraduate extends Student {
    private String finalPaper;
}

HibernateTest.java

public static void inheritance() {
    Configuration conf = new Configuration().configure();
    StandardServiceRegistry registry = conf.getStandardServiceRegistryBuilder().build();
    try (SessionFactory sessionFactory = conf.buildSessionFactory(registry); Session session = sessionFactory.openSession()) {
        Transaction transaction = session.beginTransaction();

        Student student = new Student();
        student.setName("student name");
        student.setAge(18);
        session.save(student);

        Student student1 = new Student();
        student1.setName("student name1");
        student1.setAge(19);
        session.save(student1);

        PostGraduate postGraduate = new PostGraduate();
        postGraduate.setAge(23);
        postGraduate.setFinalPaper("introduction to linux");
        session.save(postGraduate);

        PostGraduate postGraduate1 = new PostGraduate();
        postGraduate1.setAge(24);
        postGraduate1.setFinalPaper("java io");
        session.save(postGraduate1);

        transaction.commit();
    }
}

public static void main(String[] args) {
    inheritance();
}

sql输出:

mysql> select * from student_info;
+----------------+----+-----+---------------+-----------------------+
| student_type   | id | age | name          | finalPaper            |
+----------------+----+-----+---------------+-----------------------+
| student        |  1 |  18 | student name  | NULL                  |
| student        |  2 |  19 | student name1 | NULL                  |
| post_gradudate |  3 |  23 | NULL          | introduction to linux |
| post_gradudate |  4 |  24 | NULL          | java io               |
+----------------+----+-----+---------------+-----------------------+
4 rows in set (0.00 sec)

可以看到student_info增加了PostGraduate的属性。student_type是辨别者列,通过这个列上的属性来判断是哪个类。对于子类新增的属性,父类对应的值为NULL。

3.2 子类、父类各自处于单独的表

考虑一下,前面我们生成主键的时候,指定了@GeneratedValue(strategy = GeneratedType.IDENTITY),表示自增。在只有一张表的情况下,这是OK的。但对于我们现在描述的情况,父类子类处于不同的表,这种自增策略就不行了。此时我们要使用Hibernate自带生成策略。这里我们使用hibernate hilo生成策略,策略的意思以及其他策略请参考文档。

同时,为了让父类、子类各自处于单独的表,我们还需要用@Inheritance替换@Discriminator

Student.java

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Table(name = "student_info")
public class Student {
    @Id
    @GenericGenerator(name = "student_uuid", strategy = "org.hibernate.id.UUIDGenerator")
    @GeneratedValue(generator = "student_uuid")
    private String id;

    private String name;

    private int age;
}    

需要注意的是,我们把id从Integer变成了String。因为UUIDGenerator生成的主键不可能用integer保存。

PostGraudate.java

@Entity
public class PostGraduate extends Student {
    private String finalPaper;
}    

sql输出:

mysql> select * from PostGraduate;
+--------------------------------------+-----+------+-----------------------+
| id                                   | age | name | finalPaper            |
+--------------------------------------+-----+------+-----------------------+
| ad0f4762-8d8e-459e-a6b3-bb219d7658c7 |  24 | NULL | java io               |
| e83572ce-a814-4e85-b8ba-79dd298b1240 |  23 | NULL | introduction to linux |
+--------------------------------------+-----+------+-----------------------+
2 rows in set (0.00 sec)

mysql> select * from student_info;
+--------------------------------------+-----+---------------+
| id                                   | age | name          |
+--------------------------------------+-----+---------------+
| 3c96ba4f-e719-491f-bd96-76811d832bf5 |  19 | student name1 |
| c5299329-90bf-4dbf-9085-cd366da0b407 |  18 | student name  |
+--------------------------------------+-----+---------------+

可以看到,的确是在两个不同的表中。

3.3 连接子类映射策略

这种策略只需要把上面的@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)修改为@Inheritance(strategy = InheritanceType.JOINED)

这种策略由于查找的时候,要join一次,性能比前面两种低,所以这里暂时不讲解。有兴趣的可以自己跑一下看输出。

上一篇下一篇

猜你喜欢

热点阅读