美文网首页
Hibernate入门2-关联和映射

Hibernate入门2-关联和映射

作者: sunblog | 来源:发表于2018-05-09 01:31 被阅读0次

    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在数据库中的存在有几种呢?

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

    • 子类和父类处于同一个表内(这样是hibernate默认的实现方式)。这需要修改父类的表以适应子类。
    • 子类处和父类各自处于单独的表。
    • 连接子类。子类新增的属性处于一个单独的表,同时修改父类的表,增加一个外键引用子类的表。

    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一次,性能比前面两种低,所以这里暂时不讲解。有兴趣的可以自己跑一下看输出。

    相关文章

      网友评论

          本文标题:Hibernate入门2-关联和映射

          本文链接:https://www.haomeiwen.com/subject/cmbgrftx.html