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;
相关文章
|
27天前
|
前端开发 Java API
SpringBoot整合Flowable【06】- 查询历史数据
本文介绍了Flowable工作流引擎中历史数据的查询与管理。首先回顾了流程变量的应用场景及其局限性,引出表单在灵活定制流程中的重要性。接着详细讲解了如何通过Flowable的历史服务API查询用户的历史绩效数据,包括启动流程、执行任务和查询历史记录的具体步骤,并展示了如何将查询结果封装为更易理解的对象返回。最后总结了Flowable提供的丰富API及其灵活性,为后续学习驳回功能做了铺垫。
42 0
SpringBoot整合Flowable【06】- 查询历史数据
|
15天前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
35 6
|
28天前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
264 12
|
1月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
76 8
|
21天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
158 17
Spring Boot 两种部署到服务器的方式
|
21天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
56 17
springboot自动配置原理
|
4月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
334 2
|
26天前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
71 11
|
2月前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
1月前
|
Java 测试技术 应用服务中间件
Spring Boot 如何测试打包部署
本文介绍了 Spring Boot 项目的开发、调试、打包及投产上线的全流程。主要内容包括: 1. **单元测试**:通过添加 `spring-boot-starter-test` 包,使用 `@RunWith(SpringRunner.class)` 和 `@SpringBootTest` 注解进行测试类开发。 2. **集成测试**:支持热部署,通过添加 `spring-boot-devtools` 实现代码修改后自动重启。 3. **投产上线**:提供两种部署方案,一是打包成 jar 包直接运行,二是打包成 war 包部署到 Tomcat 服务器。
47 10