Spring Boot 学习研究笔记(十六) -Spring Data JPA 实现多表关联查询

简介: Spring Boot 学习研究笔记(十六) -Spring Data JPA 实现多表关联查询

Spring Data JPA 实现多表关联查询

 

一、多对多的实现

需求

[1] 通过用户ID 查询视频信息,通过该视频信息也获得对应的用户信息

 

 

如果要从用户表的信息获得视频表的信息。必须需要三个条件:

  1. 必须需要有一个中间表。
  2. 必须需要中间表对应本表的外键。
  3. 必须需要中间表对应关联表的外键。

 

第一步:配置单表

user 表

package com.call.show.model;
import com.call.show.common.jpa.JsonType;
import com.call.show.model.converter.ChanelConverter;
import com.call.show.model.converter.MemberConverter;
import com.call.show.model.converter.OnlineConverter;
import com.call.show.model.converter.PlatformConverter;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @DESC users表的实体类
 */
@Entity
@Table(name="users")
@TypeDef(name = "JsonType", typeClass = JsonType.class)
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "menuSeq")
    @SequenceGenerator(name = "menuSeq", initialValue = 100000, allocationSize = 1, sequenceName = "MENU_SEQUENCE")
    @Column(name="id", columnDefinition = "BIGINT")
    /**
     *  用户唯一ID
     */
    private long id;
    /**
     * 设备id
     */
    @Column(name = "device", unique = true, nullable = true,length = 33)
    private String device ;
    /**
     * 在线状态
     * 0-离线 1-在线
     */
    @Column(name = "online")
    @Convert(converter = OnlineConverter.class)
    private EnumOnline  online = null;
    /**
     * 平台类型
     * 0-iOS  1-Android
     */
    @Column(name = "platform")
    @Convert(converter = PlatformConverter.class)
    private EnumPlatform platform = null ;
    /**
     * 渠道编号
     */
    @Column(name = "chanle")
    @Convert(converter = ChanelConverter.class)
    private EnumChanel chanle = null ;
    /**
     * App 版本号
     */
    @Column(name = "app_version", unique = false, nullable = false,length = 16)
    private String appVersion="0.0.1";
    /**
     * 是否会员
     */
    @Column(name = "member")
    @Convert(converter = MemberConverter.class)
    private  EnumMember member = null;
    /**
     * 会员到期时间
     */
    @Column(name = "member_expire_time",nullable = true)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date memberExpireTime;
    /**
     * 语言
     * 0-简体中文 1-英文
     */
    @Column(name = "language",nullable = false,length = 2)
    private  short language = 0;
    /**
     * 注册时间
     */
    @Column(name = "register_time",updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    @org.hibernate.annotations.CreationTimestamp
    private  Date registerTime ;
    /**
     * 最后一次心跳时间
     */
    @Column(name = "heart_time")
    @org.hibernate.annotations.UpdateTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    private  Date heartTime ;
    /**
     * 上级用户绑定列表
     * {"data": {"1": 661, "2": 639, "3": 637, "4": 633,
     * "ids": [661, 639, 637, 633], "bindTime": "2020-10-15 20:07:34"}}
     */
    @Column(name = "agent_parent_ids",columnDefinition="jsonb")
    @Type(type = "JsonType")
    private Map<String,Object> agentParentIds   = new HashMap<>();
    /**
     * 账号信息 备用字段
     *
     */
    @Column(name = "account_info",columnDefinition="jsonb")
    @Type(type = "JsonType")
    private  Map<String,Object>  accountInfo  = new HashMap<>();
    /**
     * 扩展信息备用字段timestamp
     */
    @Column(name = "extended_info",columnDefinition="jsonb")
    @Type(type = "JsonType")
    private  Map<String,Object>   extendedInfo  = new HashMap<>();
    /**
     * 最后一次登录ip
     */
    @Column(name = "login_last_ip", unique = false, nullable = true, updatable = true,length = 20)
    private  String loginLastIp ;
    /**
     * 最后一次登录时间
     */
    @Column(name = "login_last_time")
    @org.hibernate.annotations.UpdateTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    private Date loginLastTime;
    public User() {
    }
    public EnumMember getMember() {
        return member;
    }
    public EnumOnline getOnline() {
        return online;
    }
    public EnumChanel getChanle() {
        return chanle;
    }
    public long getId() {
        return id;
    }
    public int getLanguage() {
        return language;
    }
    public EnumPlatform getPlatform() {
        return platform;
    }
    public String getAppVersion() {
        return appVersion;
    }
    public String getDevice() {
        return device;
    }
    public Date getHeartTime() {
        return heartTime;
    }
    public Date getMemberExpireTime() {
        return memberExpireTime;
    }
    public void setAppVersion(String appVersion) {
        this.appVersion = appVersion;
    }
    public String getLoginLastIp() {
        return loginLastIp;
    }
    public Date getLoginLastTime() {
        return loginLastTime;
    }
    public Date getRegisterTime() {
        return registerTime;
    }
    public void setAccountInfo(Map<String, Object> accountInfo) {
        this.accountInfo = accountInfo;
    }
    public void setExtendedInfo(Map<String, Object> extendedInfo) {
        this.extendedInfo = extendedInfo;
    }
    public void setAgentParentIds(Map<String, Object> agentParentIds) {
        this.agentParentIds = agentParentIds;
    }
    public Map<String, Object> getAccountInfo() {
        return accountInfo;
    }
    public Map<String, Object> getAgentParentIds() {
        return agentParentIds;
    }
    public Map<String, Object> getExtendedInfo() {
        return extendedInfo;
    }
    public void setChanle(EnumChanel chanle) {
        this.chanle = chanle;
    }
    public void setDevice(String device) {
        this.device = device;
    }
    public void setHeartTime(Date heartTime) {
        this.heartTime = heartTime;
    }
    public void setId(long id) {
        this.id = id;
    }
    public void setLanguage(short language) {
        this.language = language;
    }
    public void setLoginLastIp(String loginLastIp) {
        this.loginLastIp = loginLastIp;
    }
    public void setMember(EnumMember member) {
        this.member = member;
    }
    public void setMemberExpireTime(Date memberExpireTime) {
        this.memberExpireTime = memberExpireTime;
    }
    public void setOnline(EnumOnline online) {
        this.online = online;
    }
    public void setPlatform(EnumPlatform platform) {
        this.platform = platform;
    }
    public void setLoginLastTime(Date loginLastTime) {
        this.loginLastTime = loginLastTime;
    }
    public void setRegisterTime(Date registerTime) {
        this.registerTime = registerTime;
    }
    @Override
    public int hashCode() {
        return super.hashCode();
    }
    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }
    @Override
    public String toString() {
        return super.toString();
    }
}
}
Video 表
package com.call.show.model;
import com.call.show.model.converter.StatusConverter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import java.util.Date;
import java.util.List;
/**
 * @DESC 视频列表的实体类
 */
@Entity
@Table(name="video")
public class Video {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id", columnDefinition = "BIGINT")
    /**
     *  视频对应的主键ID
     */
    private long id;
    /**
     *  视频封面地址
     */
    @Column(name="cover_url",nullable = false,columnDefinition = "TEXT")
    private String coverUrl;
    /**
     *  视频地址
     */
    @Column(name="url", unique = true,nullable = false,columnDefinition = "TEXT")
    private String url;
    /**
     *  排序ID
     */
    @Column(name="sort_id", columnDefinition = "BIGINT",nullable = true)
    private long sortId;
    /**
     *  状态 0 禁用 1- 启用 2 -删除
     */
    @Column(name="status")
    @Convert(converter = StatusConverter.class)
    private EnumStatus  status = null;
    /**
     * 更新时间
     */
    @Column(name = "update_time",updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    @org.hibernate.annotations.UpdateTimestamp
    private Date updateTime ;
    public void setId(long id) {
        this.id = id;
    }
    public long getId() {
        return id;
    }
    public Date getUpdateTime() {
        return updateTime;
    }
    public void setCoverUrl(String coverUrl) {
        this.coverUrl = coverUrl;
    }
    public EnumStatus getStatus() {
        return status;
    }
    public long getSortId() {
        return sortId;
    }
    public String getCoverUrl() {
        return coverUrl;
    }
    public void setSortId(long sortId) {
        this.sortId = sortId;
    }
    public String getUrl() {
        return url;
    }
    public void setStatus(EnumStatus status) {
        this.status = status;
    }
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }
    public void setUrl(String url) {
        this.url = url;
    }
}

第二步,配置多表关联

需求配置内容:

  1. 必须需要有一个中间表。
  2. 必须需要中间表对应本表的外键。
  3. 必须需要中间表对应关联表的外键。

 

在User中加入如下代码:

/**
     * 关联中间表-用户视频表
     */
    @ManyToMany
    @JoinTable(name = "user_video", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "video_id"))
    private List<Video> videoArray;
  public List<Video> getVideoArray() {
        return videoArray;
    }
    public void setVideoArray(List<Video> videoArray) {
        this.videoArray = videoArray;
    }

-在Video中加入如下代码:

@ManyToMany
    @JoinTable(name = "user_video", joinColumns = @JoinColumn(name = "video_id"), inverseJoinColumns = @JoinColumn(name = "user_id"))
    private List<User> userArray;
    public List<User> getUserArray() {
        return userArray;
    }
    public void setUserArray(List<User> userArray) {
        this.userArray = userArray;
    }

第三步,测试

@Override
   public  ResultData queryUserVideo(Integer userId){
        Optional<User> userOptional =null;
        try {
            userOptional = userRepository.findById(userId.longValue());
        }catch (final Exception exception) {
            return ResultUtils.error(null);
        }
        User  user  = userOptional.get();
        List<Video>  videos =  user.getVideoArray();
       for (Video item:videos) {
           System.out.println("item:" + item.getId() +  " url:" + item.getUrl());
       }
        return    ResultUtils.success(videos);
   }

 

二、无限递归调用问题解决

HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowE

问题描述:

在使用SpringBoot进行多对多的关联查询的时候,写数据回页面时出现异常, 出现了这个StackOverflowError的错误。 即: 在将对象转换为JSON格式的数据的时候出现了无限递归调用的情况:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.hibernate.collection.internal.PersistentBag[0]->com.call.show.model.User["videoArray"]->org.hibernate.collection.internal.PersistentBag[0]->com.call.show.model.Video["userArray"]->org.hibernate.collection.internal.PersistentBag[0]->com.call.show.model.User["videoArray"]->org.hibernate.collection.internal.PersistentBag[0]->com.call.show.model.Video["userArray"]->org.hibernate.collection.internal.PersistentBag[0]->com.call.show.model.User["videoArray"]->org.hibernate.collection.internal.PersistentBag[0
.......

//循环打印出上面的内容

 

原因:

实体类之间互相关联 在序列化A实体类的时候 由于A里有B类, 然后去序列化B 在序列化B实体类的时候, 又由于B里有A类 然后去序列化A,如此反复递归 从而造成该问题。

我的实体类:

User 类中有一个字段List<Video> videoArray,在遍历集合中,输出一个Video实例的时候,List<User> userArray 字段也将输出,因为是双向多对多的关联查询,每一个Video实例也会输出List<User> userArray`字段值,因此一直递归下去直到栈溢出报错,反之,从Video中读User也是一样的道理。

 

解决方法:

在被维护的表里面,表示其他表的外键上添加@JsonIgnore注解。

例一:

/**
   * 多对多的关系,理论上是维护关系的一方
*/
@ManyToMany
@JoinTable(
    name = "user_video", 
    joinColumns = @JoinColumn(name = "video_id"), 
    inverseJoinColumns = @JoinColumn(name = "user_id")
)
private List<User> userArray;

   

例二:

/**
     * 关联中间表-用户视频表
     * JsonIgnore
     *
     * *  需要在被维护的@JsonIgnore 注解
     * *  解决无限递归调用BUG
     *
     */
    @JsonIgnore
    @ManyToMany
    @JoinTable(
    name = "user_video",
    joinColumns = @JoinColumn(name = "video_id"),
    inverseJoinColumns = @JoinColumn(name = "user_id")
    )
    private List<User> userArray;
相关文章
|
10月前
|
人工智能 Java 机器人
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
Spring AI Alibaba集成Ollama,基于Java构建本地大模型应用,支持流式对话、knife4j接口可视化,实现高隐私、免API密钥的离线AI服务。
7354 2
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
|
9月前
|
NoSQL Java 数据库连接
《深入理解Spring》Spring Data——数据访问的统一抽象与极致简化
Spring Data通过Repository抽象和方法名派生查询,简化数据访问层开发,告别冗余CRUD代码。支持JPA、MongoDB、Redis等多种存储,统一编程模型,提升开发效率与架构灵活性,是Java开发者必备利器。(238字)
|
9月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
9月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
9月前
|
存储 Java 关系型数据库
Spring Boot中Spring Data JPA的常用注解
Spring Data JPA通过注解简化数据库操作,实现实体与表的映射。常用注解包括:`@Entity`、`@Table`定义表结构;`@Id`、`@GeneratedValue`配置主键策略;`@Column`、`@Transient`控制字段映射;`@OneToOne`、`@OneToMany`等处理关联关系;`@Enumerated`、`@NamedQuery`支持枚举与命名查询。合理使用可提升开发效率与代码可维护性。(238字)
768 1
|
9月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
784 2
|
9月前
|
JavaScript Java 关系型数据库
基于springboot的项目管理系统
本文探讨项目管理系统在现代企业中的应用与实现,分析其研究背景、意义及现状,阐述基于SSM、Java、MySQL和Vue等技术构建系统的关键方法,展现其在提升管理效率、协同水平与风险管控方面的价值。
|
9月前
|
搜索推荐 JavaScript Java
基于springboot的儿童家长教育能力提升学习系统
本系统聚焦儿童家长教育能力提升,针对家庭教育中理念混乱、时间不足、个性化服务缺失等问题,构建科学、系统、个性化的在线学习平台。融合Spring Boot、Vue等先进技术,整合优质教育资源,提供高效便捷的学习路径,助力家长掌握科学育儿方法,促进儿童全面健康发展,推动家庭和谐与社会进步。
|
9月前
|
JavaScript Java 关系型数据库
基于springboot的古树名木保护管理系统
本研究针对古树保护面临的严峻挑战,构建基于Java、Vue、MySQL与Spring Boot技术的信息化管理系统,实现古树资源的动态监测、数据管理与科学保护,推动生态、文化与经济可持续发展。