在Spring Data JPA中有一对一、一对多、多对多等关系映射
@OneToOne
一对一关系,在现实生活中是十分常见的。比如一个大学生只有一张一卡通,一张一卡通只属于一个大学生。再比如人与身份证的关系也是一对一的关系。
在Spring Data JPA中,可用两种方式描述一对一关系映射。一种是通过外键的方式(一个实体通过外键关联到另一个实体的主键);一种是通过一张关联表来保存两个实体一对一的关系。下面我们通过外键的方式讲解一对一关系映射。
具体实现步骤如下。 1)创建持久化实体类 2)创建数据访问层 3)创建业务层 4)创建控制器类 5)运行
创建名为com.ch.ch6_2.entity的包,并在该包中创建名为Person和IdCard的持久化实体类。
package com.ch.ch6_2.entity; import java.io.Serializable; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; import org.hibernate.annotations.Proxy; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @Proxy(lazy = false) @Entity @Table(name = "person_table") /** * 解决No serializer found for class * org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor */ @JsonIgnoreProperties(value = { "hibernateLazyInitializer" }) public class Person implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id;// 自动递增的主键 private String pname; private String psex; private int page; @OneToOne(optional = true, fetch = FetchType.LAZY, targetEntity = IdCard.class, cascade = CascadeType.ALL) /** * 指明Person对应表的id_Card_id列作为外键与IdCard对应表的id列进行关联 unique= true 指明id_Card_id * 列的值不可重复 */ @JoinColumn(name = "id_Card_id", referencedColumnName = "id", unique = true) @JsonIgnore // 如果A对象持有B的引用,B对象持有A的引用,这样就形成了循环引用,如果直接使用json转换会报错 private IdCard idCard; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getPname() { return pname; } public void setPname(String pname) { this.pname = pname; } public String getPsex() { return psex; } public void setPsex(String psex) { this.psex = psex; } public int getPage() { return page; } public void setPage(int page) { this.page = page; } public IdCard getIdCard() { return idCard; } public void setIdCard(IdCard idCard) { this.idCard = idCard; } }
package com.ch.ch6_2.entity; import java.io.Serializable; import java.util.Calendar; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.hibernate.annotations.Proxy; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @Proxy(lazy = false) @Entity @Table(name = "idcard_table") @JsonIgnoreProperties(value = { "hibernateLazyInitializer" }) public class IdCard implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id;// 自动递增的主键 private String code; /** * @Temporal主要是用来指明java.util.Date 或 java.util.Calendar 类型的属性具体 * 与数据库(date、time、timestamp)三个类型中的那一个进行映射 */ @Temporal(value = TemporalType.DATE) private Calendar birthday; private String address; /** * optional = false设置person属性值不能为null,也就是身份证必须有对应的主人。 mappedBy = * "idCard"与Person类中的idCard属性一致 */ @OneToOne(optional = false, fetch = FetchType.LAZY, targetEntity = Person.class, mappedBy = "idCard", cascade = CascadeType.ALL) private Person person;// 对应的人 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Calendar getBirthday() { return birthday; } public void setBirthday(Calendar birthday) { this.birthday = birthday; } public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } }
server.port=8089 server.servlet.context-path=/ch6_2 spring.datasource.url=jdbc:mysql://localhost:3306/springbootjpa?serverTimezone=UTC&autoReconnect=true spring.datasource.username=root spring.datasource.password=admin spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.database=MYSQL spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jackson.serialization.indent-output=true
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.one</groupId> <artifactId>SpringBootOne</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <!-- 声明项目配置依赖编码格式为 utf-8 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <fastjson.version>1.2.24</fastjson.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 添加MySQL依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> <version>8.0.13</version><!--$NO-MVN-MAN-VER$ --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
上述实体类Person中,@OneToOne注解有五个属性:targetEntity、cascade、fetch、optional 和mappedBy。
targetEntity属性:class类型属性。定义关系类的类型,默认是该成员属性对应的类类型,所以通常不需要提供定义。
cascade属性:CascadeType[]类型。该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操作,而且这种关系是递归调用的。cascade的值只能从CascadeType.PERSIST(级联新建)、CascadeType.REMOVE(级联删除)、CascadeType.REFRESH(级联刷新)、CascadeType.MERGE(级联更新)中选择一个或多个。还有一个选择是使用CascadeType.ALL,表示选择全部四项。
FetchType.LAZY:懒加载,加载一个实体时,定义懒加载的属性不会马上从数据库中加载。FetchType.EAGER:急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载。
optional = true,表示idCard属性可以为null,也就是允许没有身份证,如未成年人没有身份证。
mappedBy标签一定是定义在关系的被维护端,它指向关系的维护端;只有@OneToOne,@OneToMany,@ManyToMany上才有mappedBy属性,ManyToOne不存在该属性。拥有mappedBy注解的实体类为关系的被维护端。
创建名为com.ch.ch6_2.repository的包,并在该包中创建名为IdCardRepository和PersonRepository的接口。
package com.ch.ch6_2.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.ch.ch6_2.entity.Person; public interface PersonRepository extends JpaRepository<Person, Integer> { /** * 根据身份ID查询人员信息(关联查询,根据idCard属性的id) 相当于JPQL语句:select p from Person p where * p.idCard.id = ?1 */ public Person findByIdCard_id(Integer id); /** * 根据人名和性别查询人员信息 相当于JPQL语句:select p from Person p where p.pname = ?1 and p.psex * = ?2 */ public List<Person> findByPnameAndPsex(String pname, String psex); }
package com.ch.ch6_2.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.ch.ch6_2.entity.IdCard; public interface IdCardRepository extends JpaRepository<IdCard, Integer> { /** * 根据人员ID查询身份信息(关联查询,根据person属性的id) 相当于JPQL语句:select ic from IdCard ic where * ic.person.id = ?1 */ public IdCard findByPerson_id(Integer id); /** * 根据地址和身份证号查询身份信息 相当于JPQL语句:select ic from IdCard ic where ic.address = ?1 and * ic.code =?2 */ public List<IdCard> findByAddressAndCode(String address, String code); }
3)创建业务层
创建名为com.ch.ch6_2.service的包,并在该包中创建名为PersonAndIdCardService的接口和接口实现类PersonAndIdCardServiceImpl。
package com.ch.ch6_2.service; import java.util.List; import com.ch.ch6_2.entity.IdCard; import com.ch.ch6_2.entity.Person; public interface PersonAndIdCardService { public void saveAll(); public List<Person> findAllPerson(); public List<IdCard> findAllIdCard(); public IdCard findByPerson_id(Integer id); public List<IdCard> findByAddressAndCode(String address, String code); public Person findByIdCard_id(Integer id); public List<Person> findByPnameAndPsex(String pname, String psex); public IdCard getOneIdCard(Integer id); public Person getOnePerson(Integer id); }
package com.ch.ch6_2.service; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ch.ch6_2.entity.IdCard; import com.ch.ch6_2.entity.Person; import com.ch.ch6_2.repository.IdCardRepository; import com.ch.ch6_2.repository.PersonRepository; @Service public class PersonAndIdCardServiceImpl implements PersonAndIdCardService { @Autowired private IdCardRepository idCardRepository; @Autowired private PersonRepository personRepository; @Override public void saveAll() { // 保存身份证 IdCard ic1 = new IdCard(); ic1.setCode("123456789"); ic1.setAddress("北京"); Calendar c1 = Calendar.getInstance(); c1.set(2019, 8, 13); ic1.setBirthday(c1); IdCard ic2 = new IdCard(); ic2.setCode("000123456789"); ic2.setAddress("上海"); Calendar c2 = Calendar.getInstance(); c2.set(2019, 8, 14); ic2.setBirthday(c2); IdCard ic3 = new IdCard(); ic3.setCode("1111123456789"); ic3.setAddress("广州"); Calendar c3 = Calendar.getInstance(); c3.set(2019, 8, 15); ic3.setBirthday(c3); List<IdCard> idCards = new ArrayList<IdCard>(); idCards.add(ic1); idCards.add(ic2); idCards.add(ic3); idCardRepository.saveAll(idCards); // 保存人员 Person p1 = new Person(); p1.setPname("陈恒1"); p1.setPsex("男"); p1.setPage(88); p1.setIdCard(ic1); Person p2 = new Person(); p2.setPname("陈恒2"); p2.setPsex("女"); p2.setPage(99); p2.setIdCard(ic2); Person p3 = new Person(); p3.setPname("陈恒3"); p3.setPsex("女"); p3.setPage(18); p3.setIdCard(ic3); List<Person> persons = new ArrayList<Person>(); persons.add(p1); persons.add(p2); persons.add(p3); personRepository.saveAll(persons); } @Override public List<Person> findAllPerson() { return personRepository.findAll(); } @Override public List<IdCard> findAllIdCard() { return idCardRepository.findAll(); } /** * 根据人员ID查询身份信息(级联查询) */ @Override public IdCard findByPerson_id(Integer id) { return idCardRepository.findByPerson_id(id); } @Override public List<IdCard> findByAddressAndCode(String address, String code) { return idCardRepository.findByAddressAndCode(address, code); } /** * 根据身份ID查询人员信息(级联查询) */ @Override public Person findByIdCard_id(Integer id) { return personRepository.findByIdCard_id(id); } @Override public List<Person> findByPnameAndPsex(String pname, String psex) { return personRepository.findByPnameAndPsex(pname, psex); } @Override public IdCard getOneIdCard(Integer id) { return idCardRepository.getOne(id); } @Override public Person getOnePerson(Integer id) { return personRepository.getOne(id); } }
4)创建控制器类
创建名为com.ch.ch6_2.controller的包,并在该包中创建名为TestOneToOneController的控制器类。
package com.ch.ch6_2.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ch.ch6_2.entity.IdCard; import com.ch.ch6_2.entity.Person; import com.ch.ch6_2.service.PersonAndIdCardService; @RestController public class TestOneToOneController { @Autowired private PersonAndIdCardService personAndIdCardService; @RequestMapping("/save") public String save() { personAndIdCardService.saveAll(); return "人员和身份保存成功!"; } @RequestMapping("/findAllPerson") public List<Person> findAllPerson() { return personAndIdCardService.findAllPerson(); } @RequestMapping("/findAllIdCard") public List<IdCard> findAllIdCard() { return personAndIdCardService.findAllIdCard(); } /** * 根据人员ID查询身份信息(级联查询) */ @RequestMapping("/findByPerson_id") public IdCard findByPerson_id(Integer id) { return personAndIdCardService.findByPerson_id(id); } @RequestMapping("/findByAddressAndCode") public List<IdCard> findByAddressAndCode(String address, String code) { return personAndIdCardService.findByAddressAndCode(address, code); } /** * 根据身份ID查询人员信息(级联查询) */ @RequestMapping("/findByIdCard_id") public Person findByIdCard_id(Integer id) { return personAndIdCardService.findByIdCard_id(id); } @RequestMapping("/findByPnameAndPsex") public List<Person> findByPnameAndPsex(String pname, String psex) { return personAndIdCardService.findByPnameAndPsex(pname, psex); } @RequestMapping("/getOneIdCard") public IdCard getOneIdCard(Integer id) { return personAndIdCardService.getOneIdCard(id); } @RequestMapping("/getOnePerson") public Person getOnePerson(Integer id) { return personAndIdCardService.getOnePerson(id); } }
package com.ch.ch6_2; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Ch62Application { public static void main(String[] args) { SpringApplication.run(Ch62Application.class, args); } }