沉默是金seo推广沧州公司电话
SpringDataJpa一对多、多对一关系关联以及一对多多对一双向关联
前言
案例Github地址(可以用git clone 到本地) https://github.com/chenxiban/SpringBootJpa-One-To-Many.git
今天为大家分享:SpringDataJpa一对多、多对一关系关联以及一对多多对一双向关联。
前面讲了SpringDataJpa自定义查询语句(JPQL),请查看博主的SpringDataJpa系列文章。欢迎关注!
一对多实体关联关系
一对多是以一的一方为主,当我们对班级进行操作时会相应的级联到学生表,比如查询班级,自动会得到该班级下
的所有学生。
1.搭建项目,配置项目环境
2.配置pom
<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.ysd</groupId><artifactId>spring-boot-jap-one-to-many</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>spring-boot-jap-one-to-many</name><url>http://maven.apache.org</url><!-- Spring Boot 启动父依赖 --><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.1.RELEASE</version></parent><!-- 项目全局属性 --><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><mybatis-spring-boot>1.2.0</mybatis-spring-boot><mysql-connector>5.1.39</mysql-connector></properties><dependencies><!-- Spring Boot Web 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot Test 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- Spring Boot devtools 热部署 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId></dependency><!-- Spring Boot JPA 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- MySQL 连接驱动依赖 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql-connector}</version></dependency><!-- Junit --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies><!-- SpringBoot 项目发布 打jar包 依赖 --><build><plugins><!-- SpringBoot 项目发布 打jar包 依赖 --><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><!-- Maven test junit 报告中文UTF-8编码 插件 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.6</version><configuration><forkMode>once</forkMode><argLine>-Dfile.encoding=UTF-8</argLine></configuration></plugin></plugins></build>
</project>
3.配置yml属性文件(sql文件在static文件夹下)
spring: datasource: url: jdbc:mysql://localhost:3306/springbootjpaonetomany?useUnicode=true&characterEncoding=utf8username: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driverjpa:database-platform: org.hibernate.dialect.MySQL5InnoDBDialect #不加这句则默认为myisam引擎##运行时输出jpa执行的sql语句show-sql: true## spring-boot-starter-data-jpa自动映射创建表动作 配置: 有表更新,无表创建hibernate:ddl-auto: update#集中解决各种编码问题banner:charset: UTF-8http:encoding:charset: UTF-8enabled: trueforce: truemessages:encoding: UTF-8# spring mvc 视图解析器mvc:view:prefix: /suffix: .html# 时间格式化jackson:date-format: yyyy-MM-dd HH:mm:ss# 时区设置time-zone: GMT+8
4.编写实体类
》 在com.cyj.springboot.entity下,编写class(班级)、student(学生)、代码如下:
package com.cyj.springboot.entity;import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Transient;import com.fasterxml.jackson.annotation.JsonIgnore;@Entity
@Table(name = "clazztb")
public class Clazz implements Serializable {@Id // 实体类的主键@GeneratedValue // 自动增长列@Column(columnDefinition = "int unsigned NOT NULL comment '备注:班级自动增长主键' ")private Integer clazzId;@Column(length = 10, unique = true)private String clazzName;@OrderBy@Column(columnDefinition = "int unsigned DEFAULT 0 comment '备注:班级总人数' ")private Integer clazzNumber;// @JsonIgnore@OneToMany(mappedBy = "clazz", fetch = FetchType.LAZY, cascade = CascadeType.ALL)private List<Student> list = new ArrayList<>();// ----------------------------- 以下是构造方法 ------------------------// ----------------------------- 以下是Getter和setter方法 -----------------public Integer getClazzId() {return clazzId;}public void setClazzId(Integer clazzId) {this.clazzId = clazzId;}public String getClazzName() {return clazzName;}public void setClazzName(String clazzName) {this.clazzName = clazzName;}public Integer getClazzNumber() {return clazzNumber;}public void setClazzNumber(Integer clazzNumber) {this.clazzNumber = clazzNumber;}public List<Student> getList() {return list;}public void setList(List<Student> list) {this.list = list;}// ----------------------------- 以下是重写的toString方法 ------------------------/** @Override public String toString() { return "Clazz [clazzId=" + clazzId +* ", clazzName=" + clazzName + ", clazzNumber=" + clazzNumber + "]"; }* * * public String showClazz() { return "Clazz [clazzId=" + clazzId +* ", clazzName=" + clazzName + ", clazzNumber=" + clazzNumber + "]"; }* * public String showClazzAndStudent() { return "Clazz [clazzId=" + clazzId +* ", clazzName=" + clazzName + ", clazzNumber=" + clazzNumber + ", list=" +* list + "]"; }*/}
学生实体
package com.cyj.springboot.entity;import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Transient;import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonUnwrapped;@Entity
@Table(name = "studenttb")
public class Student implements Serializable {@Id // 实体类的主键@GeneratedValue // 自动增长列@OrderBy // 数据加载顺序@Column(columnDefinition = "int unsigned NOT NULL comment '备注:学生自动增长主键' ")private Integer studentId;@Column(length = 20) // 字符长度20private String studentName;@Column(columnDefinition = "char(1) comment '备注:学生姓名' ")private String studentSex;@Column(columnDefinition = "int unsigned DEFAULT 0 comment '备注:学生年龄' ")private Integer studentAge;private Date studentBirthday;
// @CreationTimestamp@UpdateTimestamp //插入,修改时自动维护时间戳@Column(columnDefinition = "TIMESTAMP", nullable = false, updatable = false, insertable = false)private Timestamp updateTime;@Transient // 临时参数,不映射到数据库表字段private String studentSpare;@JsonIgnore
// @JsonUnwrapped@ManyToOne(targetEntity = Clazz.class)@JoinColumn(name = "student_clazz_id") // 副表中的外键字段名称private Clazz clazz;// ----------------------------- 以下是构造方法 ------------------------// ----------------------------- 以下是Getter和setter方法 ------------------------public Integer getStudentId() {return studentId;}public void setStudentId(Integer studentId) {this.studentId = studentId;}public String getStudentName() {return studentName;}public void setStudentName(String studentName) {this.studentName = studentName;}public String getStudentSex() {return studentSex;}public void setStudentSex(String studentSex) {this.studentSex = studentSex;}public Integer getStudentAge() {return studentAge;}public void setStudentAge(Integer studentAge) {this.studentAge = studentAge;}public Date getStudentBirthday() {return studentBirthday;}public void setStudentBirthday(Date studentBirthday) {this.studentBirthday = studentBirthday;}public Timestamp getUpdateTime() {return updateTime;}public void setUpdateTime(Timestamp updateTime) {this.updateTime = updateTime;}public String getStudentSpare() {return clazz.getClazzName();}public void setStudentSpare(String studentSpare) {this.studentSpare = studentSpare;}public Clazz getClazz() {return clazz;}public void setClazz(Clazz clazz) {this.clazz = clazz;}/** @Override public String toString() { return "Student [studentId=" + studentId* + ", studentName=" + studentName + ", studentSex=" + studentSex +* ", studentAge=" + studentAge + ", studentBirthday=" + studentBirthday +* ", updateTime=" + updateTime + ", studentSpare=" + studentSpare + ", clazz="* + clazz + "]"; }*/// ----------------------------- 以下是重写的toString方法 ------------------------/** @Override public String toString() { return "Student [studentId=" + studentId* + ", studentName=" + studentName + ", studentSex=" + studentSex +* ", studentAge=" + studentAge + ", studentBirthday=" + studentBirthday +* ", updateTime=" + updateTime + ", studentSpare=" + studentSpare + "]"; }* * public String showStudent() { return "Student [studentId=" + studentId +* ", studentName=" + studentName + ", studentSex=" + studentSex +* ", studentAge=" + studentAge + ", studentBirthday=" + studentBirthday +* ", updateTime=" + updateTime + ", studentSpare=" + studentSpare + "]"; }* * public String showStudentAndClazz() { return "Student [studentId=" +* studentId + ", studentName=" + studentName + ", studentSex=" + studentSex +* ", studentAge=" + studentAge + ", studentBirthday=" + studentBirthday +* ", updateTime=" + updateTime + ", studentSpare=" + studentSpare + ", clazz="* + clazz + "]"; }*/}
此时多的一方学生类无影响,对应studenttb表正常写出所有列,包括外键列,需注意外键列对应属性名必须叫做
clazzId。
@OneToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL):
@OneToMany表示该列为一对多关系列;
- fetch表示该实体的加载方式,有两种:LAZY和EAGER,懒加载和立即加载;
- cascade表示与此实体一对一关联的实体的联级样式类型。联级样式上当对实体进行操作时的策略。
说明:在定义关系时经常会涉及是否定义Cascade(级联处理)属性,担心造成负面影响。- 不定义,则对关系表不会产生任何影响
- CascadeType.PERSIST (级联新建)
- CascadeType.REMOVE (级联删除)
- CascadeType.REFRESH (级联刷新)
- CascadeType.MERGE (级联更新)
- CascadeType.ALL ,表示选择全部四项
- targetEntity 表示默认关联的实体类型,默认为当前标注的实体类;
mappedBy属性用于双向关联实体时使用,在一的一方进行声明,表示自己不是一对多的关系维护端,由对
方来维护。取值应该为多的一方的外键列对应的属性名。
5.编写dao接口
在com.cyj.springboot.dao,编写ClazzRepository、StudentRepository接口,代码如下:
package com.cyj.springboot.dao;import java.util.List;import org.springframework.data.jpa.repository.JpaRepository;import com.cyj.springboot.entity.Clazz;public interface ClazzRepository extends JpaRepository<Clazz, Integer> {// Like --- 等价于 SQL 中的 "like",比如 findByNameLike(String name);public List<Clazz> findByClazzNameLike(String name);}
学生
package com.cyj.springboot.dao;import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.cyj.springboot.entity.Student;public interface StudentRepository extends JpaRepository<Student, Integer> {// Like --- 等价于 SQL 中的 "like",比如 findByNameLike(String name);public List<Student> findByStudentNameLike(String name);}
6.编写业务逻辑层
在com.cyj.springboot.service,代码如下:
package com.cyj.springboot.service;import java.util.List;import com.cyj.springboot.entity.Clazz;public interface ClazzService {public Clazz save(Clazz clazz);public Clazz queryById(Integer id);public List<Clazz> queryByNameLike(String name);}
学生
package com.cyj.springboot.service;import java.util.List;import com.cyj.springboot.entity.Student;public interface StudentService {public Student queryById(Integer id);public List<Student> queryByNameLike(String name);}
7.编写业务逻辑层
在com.cyj.springboot.ServiceImpl下编写业务逻辑实现层,代码如下:
package com.cyj.springboot.ServiceImpl;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import com.cyj.springboot.dao.ClazzRepository;
import com.cyj.springboot.entity.Clazz;
import com.cyj.springboot.entity.Student;
import com.cyj.springboot.service.ClazzService;@Service
public class ClazzServiceImpl implements ClazzService {@Autowiredprivate ClazzRepository repository;@Overridepublic Clazz save(Clazz clazz) {return repository.save(clazz);}@Overridepublic Clazz queryById(Integer id) {return repository.findOne(id);}@Overridepublic List<Clazz> queryByNameLike(String name) {return repository.findByClazzNameLike("%" + name + "%");}}
学生
package com.cyj.springboot.ServiceImpl;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import com.cyj.springboot.dao.StudentRepository;
import com.cyj.springboot.entity.Student;
import com.cyj.springboot.service.StudentService;@Service
public class StudentServiceImpl implements StudentService {@Autowiredprivate StudentRepository repository;@Overridepublic Student queryById(Integer id) {return repository.findOne(id);}@Overridepublic List<Student> queryByNameLike(String name) {return repository.findByStudentNameLike("%" + name + "%");}}
8.编写controller层
在com.cyj.springboot.controller下,编写班级控制层、学生控制层,代码如下:
package com.cyj.springboot.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import com.cyj.springboot.entity.Clazz;
import com.cyj.springboot.entity.Student;
import com.cyj.springboot.service.ClazzService;/*** SpringMVC控制器* * @Description: 子模块* @ClassName: CityRestController.java* @author ChenYongJia* @Date 2017-10-4 下午8:04:34* @Email 867647213@qq.com*/
@RestController
@RequestMapping("/clazz")
public class ClazzController {@Autowiredprivate ClazzService service;/*** http://localhost:8080/clazz/queryById?id=1* * @param id* @return Student*/@RequestMapping("/queryById")public Clazz queryById(Integer id) {Clazz clazz = service.queryById(id);System.out.println("queryById clazz=>" + clazz);// .showClazzAndStudent());return clazz;}/*** http://localhost:8080/clazz/queryId?id=1* * @param id* @return Student*/@RequestMapping("/queryId")public Object queryId(Integer id) {Clazz clazz = service.queryById(id);System.out.println("queryById clazz=>" + clazz);// .showClazzAndStudent());return clazz;}}
学生
package com.cyj.springboot.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.cyj.springboot.entity.Student;
import com.cyj.springboot.service.StudentService;/*** SpringMVC控制器* * @Description: 子模块* @ClassName: CityRestController.java* @author ChenYongJia* @Date 2017-10-4 下午8:04:34* @Email 867647213@qq.com*/
@RestController
@RequestMapping("/student")
public class StudentController {@Autowiredprivate StudentService service;/*** http://localhost:8080/student/queryById?id=1* * @param id* @return Student*/@RequestMapping("/queryById")public Student queryById(Integer id) {Student student = service.queryById(id);System.out.println("queryById student=>" + student);// .showStudentAndClazz());return student;}/*** http://localhost:8080/student/queryId?id=1* * @param id* @return Student*/@RequestMapping("/queryId")public String queryId(Integer id) {Student student = service.queryById(id);System.out.println("queryById student=>" + student);// .showStudentAndClazz());return "查询成功";}/*** http://localhost:8080/student/queryByNameLike?name=张三* * @param id* @return Student*/@RequestMapping("/queryByNameLike")public Object queryByNameLike(String name) {List<Student> list = service.queryByNameLike(name);System.out.println("queryByNameLike list=>" + list);for (Student s : list) {System.out.println("Student =>" + s);// .showStudentAndClazz());}return list;}}
9.编写项目主类
在com.cyj.springboot下编写项目主类,代码如下:
package com.cyj.springboot;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;/*** Spring Boot 应用启动类* * @Description: 主模块* @ClassName: Application.java* @author ChenYongJia* @Date 2017-10-4 下午8:03:41* @Email 867647213@qq.com*/
@EnableJpaRepositories(basePackages = "com.cyj.springboot.dao") // Spring Jpa 启用注解
@EntityScan(basePackages = "com.cyj.springboot.entity") // 扫描Jpa实体对象
@SpringBootApplication // Spring Boot 应用的标识
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);// 程序启动入口 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件}
}
启动项目,查看运行结果,测试路径可根据controller层的提示进行测试
多对一关系
多对一是以多的一方为主,当我们对学生进行操作时会相应的级联到班级表,比如查询学生,自动会得到该学生所
在的班级。
@Entity @Table(name="studenttb")
public class Student implements Serializable{
//多的一方 //...其他属性省略 @ManyToOne(targetEntity = Clazz.class)//通过实体反射说明该外键列来自于哪张表 private Clazz clazz;//因为不只保存所属班级主键,而是所属班级所有信息
}
此时一的一方班级类无影响,对应clazztb表正常写出所有列。
一对多多对一双向关联:
很显然,当一对多的时候,操作学生并不能级联到班级;同样的,当多对一的时候,操作班级也不能级联到学生,
那么想要双向关联要怎么做呢?很简单,把上面两者结合即可,即:
\
多的一方学生因为要显示班级信息,多个学生属于同一个班级,所以学生实体类当中要有一个属性是班级对象,当
查询学生时显示其所属班级,因为该类当中已经有了班级主键作为外键列,所以就有了2.2多对一中的 private Clazz clazz 的写法来代替并包含了外键列;同样的,一个班级对象中也要显示该班级下所有的学生,而班级表并
不需要学生表主键来做外键,为了满足保存所有学生的情况,我们在班级实体类加入泛型为学生的集合来做属性。
此时两者互相关联,那么外键如何处理呢?当我们添加一个学生,如果该班级不存在,按照业务设计要么失败要么
自动级联添加,反之添加班级时也会添加…,此时两方都在维护外键,就会导致冗余和冲突,所以我们需要指定一
方来维护即可。所以我们按照谁使用谁处理的原则,交给多的一方来处理。具体写法为,在一的一方也就是
@OneToMany
中添加 mappedBy="clazz"
来进行指定。
所以实现双向关联即为一对多+多对一组合,且在
@OneToMany
中添加mappedBy="clazz"
即可。
好了到这里也该结束了,各位要自己多动手才能学到真正的东西。加油各位
最后
-
更多参考精彩博文请看这里:《陈永佳的博客》
-
喜欢博主的小伙伴可以加个关注、点个赞哦,持续更新嘿嘿!