关系映射开发(一):一对一映射
- 1.认识实体间关系映射
- 1.1 映射方向
- 1.2 ORM 映射类型
- 2.实现 “一对一” 映射
- 2.1 编写实体
- 2.1.1 新建 Student 实体
- 2.1.2 新建 Card 实体
- 2.2 编写 Repository 层
- 2.2.1 编写 Student 实体的 Repository
- 2.2.2 编写 Card 实体的 Repository
- 2.3 编写 Service 层
- 2.3.1 编写 Student 的 Service 层
- 2.3.2 编写 Card 的 Service 层
- 2.4 编写 Service 实现
- 2.4.1 编写 Student 实体的 Service 实现
- 2.4.2 编写 Card 实体的 Service 实现
- 2.5 编写测试
- 3.单向 / 双向 OneToOne
- 3.1 单向一对一关系的拥有端
- 3.2 单向一对一关系的接收端
- 3.3 双向一对一关系的接收端
1.认识实体间关系映射
对象关系映射(object relational mapping
)是指通过将对象状态映射到数据库列,来开发和维护对象和关系数据库之间的关系。它能够轻松处理(执行)各种数据库操作,如插入、更新、 删除等。
1.1 映射方向
ORM 的映射方向是表与表的关联(join
),可分为两种。
- 单向关系:代表一个实体可以将属性引用到另一个实体。即只能从 A 表向 B 表进行联表查询。
- 双向关系:代表每个实体都有一个关系字段(属性)引用了其他实体。
1.2 ORM 映射类型
- 一对一(
@OneToOne
):实体的每个实例与另一个实体的单个实例相关联。 - 一对多(
@OneToMany
):一个实体的实例可以与另一个实体的多个实例相关联。 - 多对一(
@ManyToOne
): —个实体的多个实例可以与另一个实体的单个实例相关联。 - 多对多(
@ManyToMany
):—个实体的多个实例可能与另一个实体的多个实例有关。在这个映射中,任何一方都可以成为所有者方。
2.实现 “一对一” 映射
一对一 映射首先要确定实体间的关系,并考虑表结构,还要考虑实体关系的方向性。
若为 双向关联,则在保存实体关系的实体中要配合注解 @JoinColumn;在没有保存实体关系的实体中,要用 mappedBy
属性明确所关联的实体。
下面通过实例来介绍如何构建一对一的关系映射。
2.1 编写实体
2.1.1 新建 Student 实体
package com.example.demo.entity;
import lombok.Data;
import javax.persistence.*;
@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.1.2 新建 Card 实体
package com.example.demo.entity;
import lombok.Data;
import javax.persistence.*;
@Entity
@Table(name = "card")
@Data
public class Card {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private Integer num;
}
2.2 编写 Repository 层
2.2.1 编写 Student 实体的 Repository
package com.example.demo.repository;
import com.example.demo.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import javax.transaction.Transactional;
public interface StudentRepository extends JpaRepository<Student, Long> {
Student findById(long id);
Student deleteById(long id);
}
2.2.2 编写 Card 实体的 Repository
package com.example.demo.repository;
import com.example.demo.entity.Card;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface CardRepository extends JpaRepository<Card, Long>, JpaSpecificationExecutor<Card> {
Card findById(long id);
}
2.3 编写 Service 层
2.3.1 编写 Student 的 Service 层
package com.example.demo.service;
import com.example.demo.entity.Student;
import java.util.List;
public interface StudentService {
public List<Student> getStudentlist();
public Student findStudentById(long id);
}
2.3.2 编写 Card 的 Service 层
package com.example.demo.service;
import com.example.demo.entity.Card;
import java.util.List;
public interface CardService {
public List<Card> getCardList();
public Card findCardById(long id);
}
2.4 编写 Service 实现
2.4.1 编写 Student 实体的 Service 实现
package com.example.demo.service.impl;
import com.example.demo.entity.Student;
import com.example.demo.repository.StudentRepository;
import com.example.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
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.4.2 编写 Card 实体的 Service 实现
package com.example.demo.service.impl;
import com.example.demo.entity.Card;
import com.example.demo.repository.CardRepository;
import com.example.demo.service.CardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@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);
}
}
2.5 编写测试
完成了上面的工作后,就可以测试它们的关联关系了。
package com.example.demo.entity;
import com.example.demo.repository.CardRepository;
import com.example.demo.repository.StudentRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.*;
@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);
}
}
首先,注释掉 studentRepository.deleteById(id);
,看一下数据库。因为级联的原因,在运行 studentRepository.save(student1);
时,就保存了 card1
,所以不需要像 cardRepository.save(card2);
一样运行 cardRepository.save(card1);
。
运行代码,在控制台输出如下测试结果:
可以看到,同时在两个表 stdu
和 card
中添加了内容,然后删除了刚添加的有关联的 stdu
和 card
表中的值。如果没有关联,则不删除。
🚀 在双向关系中,有一方为关系的 发出端,另一方是关系的 反端,即
Inverse
端(接收端)。对于双向的一对一关系映射,发出端和接收端都要使用注解 @OneToOne,同时定义一个接收端类型的字段属性和 @OneToOne 注解中的mappedBy
属性。这个在双向关系的接收端是必需的。
3.单向 / 双向 OneToOne
单向一对一 是关联关系映射中最简单的一种,简单地说就是可以从关联的一方去查询另一方,却不能反向查询。我们用下面的例子来举例说明,清单 1 中的 Person 实体类和清单 2 中的 Address 类就是这种单向的一对一关系,我们可以查询一个 Person 的对应的 Address 的内容,但是我们却不能由一个 Address 的值去查询这个值对应的 Person。
3.1 单向一对一关系的拥有端
@Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int age;
@OneToOne
private Address address;
// Getters & Setters
}
3.2 单向一对一关系的接收端
@Entity
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String street;
private String city;
private String country;
// Gettes & Setters
}
从上面的 ER 图可以看出,这种单向的一对一关系在数据库中是以外键的形式被映射的。其中关系的 发出端 存储一个指向关系的接收端的一个外键。在这个例子中 person
表中的 ADDRESS_ID 就是指向 address
表的一个外键,缺省情况下这个外键的字段名称,是以它指向的表的名称加下划线 _
加 ID
组成的。当然我们也可以根据我们的喜好来修改这个字段,修改的办法就是使用 @JoinColumn 这个注解。在这个例子中我们可以将这个注解注释在 Person 类中的 Address 属性上去。
3.3 双向一对一关系的接收端
@Entity
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String street;
private String city;
private String country;
@OneToOne(mappedBy = "address")
private Person person;
// Gettes & Setters
}
双向关系有一方为关系的拥有端,另一方是关系的接收端(反端),也就是 Inverse 端。在这个例子中 Person 拥有这个关系,而 Address 就是关系的 Inverse 端。Address 中我们定义了一个 person
属性,在这个属性上我们使用了 @OneToOne 注解并且定义了它的 mappedBy
属性,这个在双向关系的 Inverse 端是必需的。