jpa实现级联操作

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 包含以上全部级联操作

目录

jpa实现级联操作

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>