README
## 说明
这是JPA实现级联操作的demo。
为了实现方便,就没有写service和impl层,直接写了dao层。(理解级联操作的思路就好)
### 数据库说明
在application.properties中配置您对应的数据库信息。
无需在mysql数据库设计表。运行该项目,则自动生成数据库表。
### 注意点
- 在被维护的一方,比如Survey,添加所有的问题,一定要添加 @ToString.Exclude。
否则报错:$HibernateProxy$Bo3T1LuZ.toString(Unknown Source) ~[classes/:na]
@OneToMany(mappedBy = "survey",cascade=CascadeType.ALL,orphanRemoval = true)
@ToString.Exclude
private List<Question> questions=new LinkedList<>();
### 关系说明
一个问卷有多个题目
一个题目有多个选项
### 级联删除
删除问卷,会把相关的问题和选项都删除。
### 级联更新
目前的思路实现是新增一个问题,把以前的无关问题全部删除。详情看TestController的test6.
如果你想更新部分问题,又不想把以前的无关问题删除。
1.这个想法的思路我暂时这么实现。
2.获取以前的问题。
3.使用survey.getQuestions().clear();删除全部的问题
4.将以前的问题进行修改,再重新生成问题
### 级联查询
根据问卷id,能够查询全部的问题和选项
### 级联标签
CascadeType.PRESIST 级联持久化(保存)操作(持久保存拥有方实体时,也会持久保存该实体的所有相关数据。)
CascadeType.REMOVE 级联删除操作(删除一个实体时,也会删除该实体的所有相关数据。)
CascadeType.MERGE 级联更新(合并)操作(将分离的实体重新合并到活动的持久性上下文时,也会合并该实体的所有相关数据。)
CascadeType.REFRESH 级联刷新操作 (只会查询获取操作)
CascadeType.ALL 包含以上全部级联操作
目录
entity
Survey问卷类
package com.lyr.demo.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.LinkedList;
import java.util.List;
@ApiModel("问卷")
@Entity
@Data
@Table(name = "t_survey")
@AllArgsConstructor
@NoArgsConstructor
@ToString
@JsonIgnoreProperties(value = {"hibernateLazyInitializer"}) //防止转换json数据的时候出现无限制循环
public class Survey {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
@Column
private String title;
/**
* orphanRemoval=true配置表明删除无关联的数据。级联更新子结果集时此配置最关键
*/
@OneToMany(mappedBy = "survey", cascade = CascadeType.ALL,orphanRemoval = true)
@JsonBackReference
private List<Question> questions = new LinkedList<>();
/**
* 手动添加问题
*/
public void addQuestion(Question q) {
if (null != q) {
questions.add(q);
}
}
}
Question问题类
package com.lyr.demo.entity;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.LinkedList;
import java.util.List;
/**
*
* 针对Survey,Question是多的一方,因此在与Survey的关系中,属于维护关系的一方。Survey属于被维护关系的一方。
*
*/
@ApiModel("问题")
@Entity
@Data
@Table(name = "t_question")
@AllArgsConstructor
@NoArgsConstructor
@ToString
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
public class Question {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
@Column
private String title;
/**
* (针对survey)建立外键 belong_survey_id.外键不能为空
*/
@ManyToOne
@JoinColumn(name = "belong_survey_id",nullable = false)
@JsonManagedReference
@ToString.Exclude
private Survey survey;
/**
* (针对Option)
* orphanRemoval=true配置表明删除无关联的数据。级联更新子结果集时此配置最关键
*/
@OneToMany(mappedBy = "question",cascade=CascadeType.ALL,orphanRemoval = true)
@JsonBackReference
private List<Option> options=new LinkedList<>();
/** 手动添加选项 */
public void addOption(Option option){
if(null!=option){
options.add(option);
}
}
}
Option选项类
package com.lyr.demo.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@ApiModel("选项")
@Entity
@Data
@Table(name = "t_option")
@AllArgsConstructor
@NoArgsConstructor
@ToString
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
public class Option {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
@Column
private String title;
/**
* 建立外键quid.外键不能为空
*/
@ManyToOne
@JoinColumn(name = "quid",nullable = false)
@JsonManagedReference
@ToString.Exclude
private Question question;
}
dao层
SurveyDao
package com.lyr.demo.dao;
import com.lyr.demo.entity.Survey;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SurveyDao extends JpaRepository<Survey,String> {
}
QuestionDao
package com.lyr.demo.dao;
import com.lyr.demo.entity.Question;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface QuestionDao extends JpaRepository<Question,String> {
}
OptionDao
package com.lyr.demo.dao;
import com.lyr.demo.entity.Option;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OptionDao extends JpaRepository<Option,String> {
}
TestController
package com.lyr.demo.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.lyr.demo.dao.OptionDao;
import com.lyr.demo.dao.QuestionDao;
import com.lyr.demo.dao.SurveyDao;
import com.lyr.demo.entity.Option;
import com.lyr.demo.entity.Question;
import com.lyr.demo.entity.Survey;
import com.lyr.demo.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class TestController {
@Autowired
private SurveyDao surveyDao;
@Autowired
private QuestionDao questionDao;
@Autowired
private OptionDao optionDao;
/**
* 级联保存。下面的操作是标准流程.
*
* 操作
* 1.创建一张问卷。问卷有两个问题,每个问题分别有一个选项。
* 2.对于单个问题:设置问题的标题,设置问卷属性
* 3.对于单个选项:设置选项的标题,设置问题属性
* 4.问题添加选型
* 5.问卷添加问题
* 6.保存问卷(不需要手动保存问题和问卷)
*/
@GetMapping("/test1")
public Result test1() {
Result result = new Result();
Survey survey = new Survey();
survey.setTitle("s1");
//新增一个问题1和选项,需要setSurvey()
Question q1 = new Question();
q1.setTitle("q1");
q1.setSurvey(survey);
//新增一个选项,需要setQuestion()
Option op1 = new Option();
op1.setTitle("op1");
op1.setQuestion(q1);
// 下面是同样的操作:新增问题2和选项
Question q2 = new Question();
q2.setTitle("q2");
q2.setSurvey(survey);
Option op2 = new Option();
op2.setTitle("op2");
op2.setQuestion(q2);
//Question类需要主动把Option类加入自己的options属性
q1.addOption(op1);
q2.addOption(op2);
//在执行surveyDao.save(survey)之前,Survey类需要主动把Question类加入自己的questions属性
survey.addQuestion(q1);
survey.addQuestion(q2);
surveyDao.save(survey);
return result;
}
/**
* 级联删除.删除一个问卷,问卷下面的所有问题和所有选项都将会被删除。
*/
@GetMapping("/test2")
public Result test2() {
Result result = new Result();
//删除一个问卷(改为数据库对应的问卷id)
surveyDao.deleteById("4028fe8179ef78b80179ef7968160000");
return result;
}
/**
* 级联删除。删除一个问题,问题下面的所有选项都会被删除,但是问题所属的问卷不会有任何操作。
*/
@GetMapping("/test3")
public Result test3() {
Result result = new Result();
//删除一个问题(改为数据库对应的问题id)
questionDao.deleteById("4028fe8179ef65ce0179ef67eaa90001");
return result;
}
/**
* 级联删除。删除一个选项,对所属的问题没有影响,对所属的问卷也没有影响
*/
@GetMapping("/test4")
public Result test4() {
Result result = new Result();
//删除一个选项(改为数据库对应的选项id)
optionDao.deleteById("4028fe8179ef7cc10179ef7dbdbd0002");
return result;
}
/**
* 级联查询。查询一个问卷,可以查到所有的选项和所有的问题。
* 返回的是json数据
*/
/**
* 返回的测试数据
* {
* "msg": "成功装载数据",
* "code": 0,
* "data": {
* "questions": [
* {
* "options": [
* {
* "id": "4028fe8179ef7e470179ef91863a0002",
* "title": "op1"
* },
* {
* "id": "4028fe8179ef7e470179ef91863a0007",
* "title": "op3"
* }
* ],
* "id": "4028fe8179ef7e470179ef91863a0001",
* "title": "q1"
* },
* {
* "options": [
* {
* "id": "4028fe8179ef7e470179ef91863a0004",
* "title": "op2"
* },
* {
* "id": "4028fe8179ef7e470179ef91863a0008",
* "title": "op4"
* }
* ],
* "id": "4028fe8179ef7e470179ef91863a0003",
* "title": "q2"
* }
* ],
* "id": "4028fe8179ef7e470179ef9186350000",
* "title": "s1"
* }
* }
*
*/
@GetMapping("/test5")
public Result test5() {
Result result = new Result();
Survey entity = surveyDao.getOne("4028fe8179ef7e470179ef9186350000");
if (null != entity) {
JSONObject all = new JSONObject(); //总的一个json数组,最后使用result.putData(all)
all.put("id", entity.getId());
all.put("title", entity.getTitle());
List<Question> questions = entity.getQuestions();
JSONArray arrQuesstions = new JSONArray(); // json数组,装进多个question
int size = questions.size();
if (0 != size) {
//循环获取每个问题
for (int i = 0; i < size; i++) {
JSONObject jsonQuestion = new JSONObject(); //一个问题,json形式
Question question = questions.get(i);
jsonQuestion.put("id", question.getId());
jsonQuestion.put("title", question.getTitle());
JSONArray arrOptions = new JSONArray(); //json数组,装进多个option
List<Option> options = question.getOptions();
int opSize = options.size();
//循环获取某个问题的选项
if (0 != opSize) {
for (int j = 0; j < opSize; j++) {
JSONObject jsonOp = new JSONObject();
Option option = options.get(j);
jsonOp.put("id", option.getId());
jsonOp.put("title", option.getTitle());
arrOptions.add(j, jsonOp);
}
jsonQuestion.put("options", arrOptions);
}
arrQuesstions.add(i, jsonQuestion);
}
all.put("questions", arrQuesstions);
return result.putCode(0).putMsg("成功装载数据").putData(all);
}
}
return result;
}
/**
* 级联更新.
*
* 目前需要操作的是:可能问卷需要大改。有删除,也有新增。
*
* 说明
* 1.关键: survey.getQuestions().clear(); 如果缺少了这个,则不能删除无关的数据
* 2.注解@OneToMany(mappedBy = "survey", cascade = CascadeType.ALL,orphanRemoval = true)
*
* 操作步骤(新增一个问题,把以前的无关问题清空)
* 1.拿到一个问卷
* 2.把该问卷后面的所有问题清空,也就是clear()
* 3.添加新问题,设置问题的属性,问题添加问卷的属性
* 4.问卷添加问题
* 5.保存问卷
*/
@GetMapping("/test6")
public Result test6() {
Result result = new Result();
Survey survey = surveyDao.getOne("4028fe8179f396280179f39de6c20006");
survey.getQuestions().clear();
Question question=new Question();
question.setTitle("新的问题");
question.setSurvey(survey);
survey.addQuestion(question);
surveyDao.save(survey);
return result;
}
@GetMapping("/test7")
public Result test7() {
Result result = new Result();
return result;
}
@GetMapping("/test8")
public Result test8() {
Result result = new Result();
return result;
}
}
Result类(主要是controller层的返回标准实现,我自己自定义的)
package com.lyr.demo.utils;
import io.swagger.annotations.ApiModelProperty;
import java.util.HashMap;
import lombok.Data;
/**
* @description 结果返回类
* @author lyr
* @date 2021-05-07
* 说明: 操作成功会返回0 ,操作会返回1
*/
@Data
public class Result extends HashMap<String, Object> {
@ApiModelProperty(value = "0-成功 1-失败")
private int code=0;
@ApiModelProperty(value = "描述信息")
private String msg="";
@ApiModelProperty(value = "传递的数据")
public Object data="";
public Result(){
super();
this.put("code",code);
this.put("msg",msg);
this.put("data",data);
}
@Override
public Result put(String key, Object value){
super.put(key,value);
return this;
}
public Result putData(Object data) {
this.put("data", data);
return this;
}
public Result putMsg(String msg) {
this.put("msg", msg);
return this;
}
public Result putCode(int code) {
this.put("code", code);
return this;
}
}
运行类
package com.lyr.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@EnableSwagger2
@SpringBootApplication
public class RunApplication {
public static void main(String[] args) {
SpringApplication.run(RunApplication.class,args);
}
}
application.properties
## port
server.port=8091
## 事务管理
spring.transaction.rollback-on-commit-failure=true
## local mysql
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/lyr?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.lyr</groupId>
<artifactId>jpa级联操作</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--本地运行时要注释掉-->
<!--<dependency>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-tomcat</artifactId>-->
<!--<scope>provided</scope>-->
<!--</dependency>-->
<!--本地运行时要注释掉-->
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--aop 做日志管理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.4.1</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<!--freemarker-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!--引入swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.github.caspar-chen</groupId>
<artifactId>swagger-ui-layer</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
</dependencies>
<build>
<!--项目名称-->
<finalName>QuestionnaireSystem</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
<include>**/*.xls</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>