关系型映射开发

1.认识实体间关系映射

对象关系映射(object relational mapping)是指通过将对象状态映射到数据库列,来开发和维护对象和关系数据库之间的关系。它能够轻松处理(执行)各种数据库操作。如插入、更新、删除等。
  1. 映射方向
    PRM的映射方向是表与表的关联(join),可分为两种。
  • 单向关系:代表一个实体可以将属性引用到另一个实体。即只能从A表向B表进行连表查询。
  • 双向关系: 代表每个实体都有一个关系字段(属性)引用了其他实体。
  1. ORM映射类型
  • 一对一(@OneToOne):实体的每个实体与另一个实体的单个实体相关联。
  • 一对多(@OneToMany):一个实体的实例可以与另一个实体的多个实例相关联。
  • 多对一(@ManyToOne):一个实体的多个实例可以与另一个实体的单个实例相关联。
  • 多对多(@ManyToMany):一个实体的多个实例可能与另一个实体的多个实例有关。在这个映射中,任何一方都可以成为所有者方。

实例:实现“一对一”映射

一对一映射首先要确定实体间的关系,并考虑表结构,还要考虑实体关系的方向性。
若为双向关联,则在保存实体关系的实体中要配合注解@JoinColumn。在没有保存实体关系的实体中,要用mappedBy属性明确所关联的实体。

1.编写实体
(1)新建Student实体,

@Entity
@Data
@Table(name = "stdu")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
    @Column(columnDefinition = "enum('male','female')")
    private String sex;
    /**
     * Description:
     * 建立集合,指定关系是一对一,并且申明它在cart类中的名称
     * 关联的表为card表,其主键是id
     * 指定外键名为card_id
     */
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "card_id")
     private Card card;
}

(2)新建Card实体

@Entity
@Table(name = "card")
@Data
public class Card {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private Integer num;
 }

2.编写Repository层
(1)编写Student实体的Repository

public interface StudentRepository extends JpaRepository<Student, Long> {
    Student findById(long id);
    Student deleteById(long id);
    @Query("select a from Student a where a.name = ?1")
    Student getStudentByMySelf(String name);
    @Query(value = "select * from stdu a where a.name = ?1",nativeQuery = true)
    Student getStudentByMySelf2(String name);

    @Modifying
    @Transactional
    @Query(value = "update Student a set a.name = '龙淘宝' where a.id =:id")
    void updataUserByGuid(@Param("id") long id);
    @Modifying
    @Transactional
    @Query(value = "update Student a set a.name = :name where a.id =:id")
    void updataStudentById(@Param("name") String name, @Param("id") long id);
}

(2)编写Card实体Repository

public interface CardRepository extends JpaRepository<Card,Long>  , JpaSpecificationExecutor<Card> {
    Card findById(long id);
}

3.编写service
(1)编写Student的Service层

public interface StudentService {
    public List<Student> getStudentlist();
    public Student findStudentById(long id);
}

(2)编写Card的Service层

public interface CardService {
    public List<Card> getCardList();
    public Card findCardById(long id);
}

4.编写Service的实现
(1).编写Student实体的Serevice实现

public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentRepository studentRepository;
    @Override
    public List<Student> getStudentlist() {
        return studentRepository.findAll();
    }

    @Override
    public Student findStudentById(long id) {
        return studentRepository.findById(id);
    }
}

(2)编写Card实体的Service实现

public class CardServiceImpl implements CardService {
    @Autowired
    private CardRepository cardRepository;

    @Override
    public List<Card> getCardList() {
        return cardRepository.findAll();
    }

    @Override
    public Card findCardById(long id) {
        return cardRepository.findById(id);
    }
}

5.application.properties配置

spring.datasource.url=jdbc:mysql://127.0.0.1/book?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql= true

spring.thymeleaf.cache=false
server.port=8080

6.编写测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class oneToOneTest {
    @Autowired
    private StudentRepository studentRepository;
    @Autowired
    private CardRepository cardRepository;

    @Test
    public void testOneToOne() {
        Student student1 = new Student();
        student1.setName("赵大伟");
        student1.setSex("male");
        Student student2 = new Student();
        student2.setName("赵大宝");
        student2.setSex("male");

        Card card1 = new Card();
        card1.setNum(422802);
        student1.setCard(card1);
        studentRepository.save(student1);
        studentRepository.save(student2);
        Card card2 = new Card();
        card2.setNum(422803);
        cardRepository.save(card2);
        /**
         * Description: 获取添加之后的id
         */
        Long id = student1.getId();
        /**
         * Description: 删除刚刚添加的student1
         */
        studentRepository.deleteById(id);
    }
}

总结

对于双向的“一对一”关系映射,发出端和接收端都要使用注解@OneToOne,同时定义一个接收端类型的字段属性和@OneToOne注解中的“mappedBy”属性。这个在双向关系的接受端是必须的。在双向关系中,有一方为关系的发出端,另一方是关系的反端。即“Invaese”端(接受端)

实例:实现“一对多”映射

单向关系的一对多注解@OneToMany,只用于关系的发出端(“一”的一方)。另外,需要关系的发出端定义一个集合类型的接受端的字段属性

在一对多关联关系映射中,默认是以中间表方式来映射这种关系的。中间表的名称为“用下画连接关系的拥有端(发出端)和Invaese端(接受端)”,中间表两个字段分别为两张表的表名加下画线“_”再加主键组成。

当然,也可以改变这种默认的中间件表的映射方式。在关系的拥有端,使用@JoinColumn注解定义外键来映射这个关系

1.编写实体
下面以学校(School)和老师(Teacher)来演示一对多的映射关系。
(1)@OneToMany中One的一方 ------School

@Entity
@Data
public class School {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
//    @OneToMany(cascade = CascadeType.ALL)
    @OneToMany()
    @JoinColumn(name = "school_id")
    private List<Teacher> teacherList;
}

(2)@OneToMany中Many的一方------Teacher

@Data
@Entity
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
    @ManyToOne
    private School school;
}

2.测试映射关系
Service和Repository层在前面已经讲过了,这里并没有区别,所以不再讲解。

@SpringBootTest
@RunWith(SpringRunner.class)
public class OneToManyTest {

    @Autowired
    private SchoolRepository schoolRepository;
    @Autowired
    private TeacherRepository teacherRepository;


    @Test
    public void add() {
        School school1 = new School();
        school1.setName("清华大学");
        schoolRepository.save(school1);
        Teacher teacher = new Teacher();
        teacher.setName("long");
        teacher.setSchool(school1);
        teacherRepository.save(teacher);
    }

    @Test
    public void find() {
        School school1 = new School();
        school1 = schoolRepository.findSchoolById(3);
        List<Teacher> teacherList = school1.getTeacherList();
        System.out.println(school1.getName());
        for (Teacher teacher : teacherList) {
            System.out.println(teacher.getName());
        }
    }

    @Test
    public void deleteSchoolById() {
        schoolRepository.deleteById(3);
    }

    @Test
    public void deleteTeacherById() {
        teacherRepository.deleteById(7);
    }
}

在双向一对多关系中,注解@OneToMany(mappedBy=“发出端实体名称小写”)用于关系的发出端(即“One”的一方),同时关系的发出端需要定义一个集合类型的接收端的字段属性;注解@ManyToOne用于关系的接收端(即“Many”的一方),关系的接收端需要定义一个发出端的字段属性

实现“多对多”映射

在“多对多”关联关系中,只能通过中间表的方式进行映射,不能通过增加外键来实现。
注解@ManyToMany用于关系的发出端和接收端。关系的发出端定义一个集合类型的接收端的字段属性,关系的接收端不需要做任何定义

1.创建实体
(1)创建Student实体。

@Entity
@Data
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
    @Column(columnDefinition = "enum('male','female')")
    private String sex;

    @ManyToMany(fetch=FetchType.LAZY)
    @JoinTable(name="teacher_student",joinColumns={@JoinColumn(name="s_id")},inverseJoinColumns={@JoinColumn(name="t_id")})
    private Set<Teacher> teachers;
}

(2)创建Teacher实体

@Data
@Entity
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
    @ManyToMany(fetch=FetchType.LAZY)
    /**
     * Description:
     * 1、关系两边都作为主控;
     * 2、joinColumns中@JoinColumn(name="t_id") 其中t_id为JoinTable 中的外键,由于 Student 和Teacher 的主键都为id 这边就省略referencedColumnName="id"。
     */
     @JoinTable(name="teacher_student",joinColumns={@JoinColumn(name="t_id")},inverseJoinColumns={@JoinColumn(name="s_id")})
    private Set<Student> students;
}

在多对多关系中需要注意以下几点

  • 关系的双方都可以作为主控
  • 在joinColumns的@JoinColumn(name=“t_id”)中,t_id为JoinTable中的外键。由于Student和Teacher的主键都为id,所以这里省略了referencedColumnName=“id”。
  • 在设计模型之间的级联关系时,要考虑好应该采用何种级联规则。
  • 如果设置cascade = CascadeType.PERSIST,则在执行save时会调用onPersist()方法。这个方法会递归调用外联类(Student或Teacher)的onPersist()进行级联新增。但因为值已经添加了,所以会报detached entity passed to persist错误,将级联操作取消(去掉“cascade = CascadeType.PERSIST”)即可。

2.创建测试
由于Service和Pepository与上述代码中的一样,这里不多做重复

@SpringBootTest
@RunWith(SpringRunner.class)
public class ManyToManyTest {
    @Autowired
    private StudentRepository studentRepository;
    @Autowired
    private TeacherRepository teacherRepository;


    @Test
    public void add() {

        Set<Teacher> teachers = new HashSet<>();
        Set<Student> students = new HashSet<>();

        Student student1 = new Student();
        student1.setName("zhonghua");
        students.add(student1);
        studentRepository.save(student1);

        Student student2 = new Student();
        student2.setName("zhiran");
        students.add(student2);
        studentRepository.save(student2);

        Teacher teacher1 =new Teacher();
        teacher1.setName("龙老师");
        teacher1.setStudents(students);
        teachers.add(teacher1);
        teacherRepository.save(teacher1);
    }
}

对于双向ManyToMany关系,注解@ManyToMany用于关系的发出端和接受端。另外,关系的接受端需要设置@ManyToMany(mappedBy=‘集合类型发出端实体的字段名称’)