这里的业务场景是一个用户User对应多条动态,且多条动态属于1个用户。即常见的双向1对多或者双向多对1.

看到jackson就应该知道应该是 JPA 中的实体类在处理映射关系,例如一对多的关系时,打印本类时会打印对方类,然后打印对方类又会调用本类,就出现相互调用,进入无限循环的情况,那么必然是序列化的问题了。

 

解决办法:

  • 破坏某一方的 toString()方法即可,最好是破坏多的一方的 toString()方法。
  • 在多的一方对应的实体类属性上加上 @JsonIgnore,进而就可以忽略该字段,跳出循环。但会造成的后果是,输出的结果会缺少该字段的信息,结果就会取不到该字段的相关数据。
  • 可以用 alibaba 旗下的高性能 JSON 框架:FastJSON ,该开源框架速度快,无论序列化和反序列化,都是当之无愧的,并且功能强大,支持普通JDK类包括任意Java Bean Class、Collection、Map、Date、enum,用 FastJSON 可以完美解决互相调用的问题。

上面的方法中,在关联的实体上面设置@JsonIgnore,这个注解的意思是表示在序列化的时候,忽略这个属性。当然这个方法不是很好。所以用fastjson.


演示,通过FastJson:[springboot自定义转换器]

json处理器除了jackson-databind之外。还有GSON、fastjson。

使用fastjson不同于Gson,fastjson继承完之后不能立马使用,需要自己提供响应的HttpMessageConverter才能使用。

步骤:

方式1:

1.首先去除jackson-databind依赖,然后引入fastjson依赖:“

 

2.配置fastjson的HttpMessageConverter:

package com.yinlei.vue.config;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.nio.charset.Charset;

/**
 * 配置fastjson的httpMessageConverter:
 * 这是为了解决springboot自带的jackson-databind造成的jpa1对多关联时候的死循环
 */
@Configuration
public class MyFastConfig {

    @Bean
    FastJsonHttpMessageConverter fastJsonHttpMessageConverter(){
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();

        //配置json解析过程的一些细节:日期格式、数据编码、是否再在生成JSON中输出类名、是否输出value为null的数据、生成json格式化、空集合输出而非null、空字符串输出而非null
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        config.setCharset(Charset.forName("UTF-8"));
        config.setSerializerFeatures(
                SerializerFeature.WriteClassName,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.PrettyFormat,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteNullStringAsEmpty
        );
        converter.setFastJsonConfig(config);
        return converter;
    }
}

3.设置一下响应编码,否则返回的json中文会乱码。

这样就不会死循环了

 

方式2:

package com.yinlei.vue.config;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.nio.charset.Charset;

/**
 * 配置fastjson的httpMessageConverter:
 * 这是为了解决springboot自带的jackson-databind造成的jpa1对多关联时候的死循环
 */
@Configuration
public class MyWebConfig implements WebMvcConfigurer{

    @Override
    public void configureMessageConverters(List<HttoMessageConverter<?>> converters){
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();

        //配置json解析过程的一些细节:日期格式、数据编码、是否再在生成JSON中输出类名、是否输出value为null的数据、生成json格式化、空集合输出而非null、空字符串输出而非null
        config.setDateFormat("yyyy-MM-dd HH:mm:ss");
        config.setCharset(Charset.forName("UTF-8"));
        config.setSerializerFeatures(
                SerializerFeature.WriteClassName,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.PrettyFormat,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteNullStringAsEmpty
        );
        converter.setFastJsonConfig(config);
        converters.add(converter);
    }
}

JPA中的双向1对多配置:

user类是1端,Dongtai类是多端。

下面的配置是双向1对多。

package com.yinlei.vue.entity;

import lombok.*;

import javax.persistence.*;
import javax.validation.constraints.Size;
import java.util.List;

/**
 * 实体类:
 * 用户(登录或者注册或者修改信息或者注销账户)
 * 外键关联:动态表应该隶属于用户表
 * 属于1对多:1个用户对应了多个动态
 * PA使用@OneToMany和@ManyToOne来标识一对多的双向关联。一端(Author)使用@OneToMany,多端(Article)使用@ManyToOne。
 * 在JPA规范中,一对多的双向关系由多端(Article)来维护。就是说多端(Article)为关系维护端,负责关系的增删改查。一端(Author)则为关系被维护端,不能维护关系。
 * 一端(Author)使用@OneToMany注释的mappedBy="author"属性表明Author是关系被维护端。
 *
 * 多端(Article)使用@ManyToOne和@JoinColumn来注释属性 author,@ManyToOne表明Article是多端,@JoinColumn设置在article表中的关联字段(外键)。
 */
@Table(name = "user")
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    @Column(name = "user_id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer userId; //用户id

    @Column(name = "user_name", nullable = false)
    private String userName;//用户名

    @Column(name = "user_sex", nullable = true)
    private String userSex;//性别

    @Size(min=1, max=100)
    @Column(name = "user_age", nullable = true)
    private int userAge;//年龄

    @Column(name = "user_pwd", nullable = false)
    private String userPassword;//密码

    @Column(name = "user_tel", nullable = false)
    private String userTelephone;//手机号

    @Column(name = "user_email", nullable = false)
    private String userEmail;//电子邮件

//    1对多这里的1是User,多是动态。所以User是被维护端.不保持关系。
    @OneToMany(mappedBy = "belongOfUser", cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    private List<DongTai> dongTais;//动态列表
}
package com.yinlei.vue.entity;

import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;

/**
 * 实体类:动态
 * 这个是重要的要与用户交互的类。
 * TODO 用户的登录注册
 */
@Table(name = "dongtai")
@Entity
@EntityListeners(AuditingEntityListener.class)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DongTai {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
    @Column(name = "dongtai_id", nullable = false)
    private Integer dongtaiId ;//动态id

    @Column(name = "dongtai_title", nullable = false)
    private String dongtaiTitle;//标题

    @Column(name = "select_location", nullable = false)
    private String dongtaiSelectLoca;//用户选择的地址

    @Column(name = "all_location", nullable = false)
    private String dongtaiAllLoca;//后端拥有提供给前端的全部地址

    @Column(name = "avatar", nullable = false)
    private String dongtaiAvatar;//上传图片的url

    @Column(name = "score", nullable = false)
    private String dongtaiScore;//心情等级

    @Lob  // 大对象,映射 MySQL 的 Long Text 类型
    @Basic(fetch = FetchType.LAZY) // 懒加载
    @Size(min = 2)
    @Column(name = "editcontent",nullable = false) // 映射为字段,值不能为空
    private String dongtaiEditContent;//用户编辑的内容

    /**
     * 创建时间
     */
    @CreatedDate
    @Column(name = "create_time")
    private Date createTime;

    /**
     * 修改时间
     */
    @LastModifiedDate
    @Column(name = "modify_time")
    private Date modifyTime;

    //1对多: 这里的动态是多端。维护关系端
    @ManyToOne(cascade = {CascadeType.MERGE,CascadeType.REFRESH},optional = false)//可选属性optional=false,表示user不能为空。删除动态,不影响用户
    @JoinColumn(name = "user_id")//设置在动态表中的关联字段(外键)
    private User belongOfUser;//动态所属用户
}
package com.yinlei.vue;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class VueApplication {

    public static void main(String[] args) {
        SpringApplication.run(VueApplication.class, args);
    }

}

 

此外,用非原生jackson,还可以解决jpa实体类上添加Date类型的错误。

提一下,jpa中,设置时间:

可以在实体类中添加

并在启动类上添加:

 


fastjson解析json对象出现$ref: "$":

问题是循环引用造成的:

fastjson提供了多种json转换方案,有兴趣的同学可以自己看看源码,这里我们可以采用禁止循环引用的方案:

SerializerFeature.DisableCircularReferenceDetect就是禁止循环引用的方案

循环引用:当一个对象包含另一个对象时,fastjson就会把该对象解析成引用。引用是通过$ref标示的,下面介绍一些引用的描述
"$ref":".." 上一级
"$ref":"@" 当前对象,也就是自引用
"$ref":"$" 根对象
"$ref":"$.children.0" 基于路径的引用,相当于 root.getChildren().get(0)
 

使用SpringBoot+FastJson的时候,如果json里面的list,包含相同内容,会显示为$.ref[x]或者$.row[x].xxx[x],所以需要在FastJson里面设置一下。

1。FastJson的.java配置增加以下项

//禁用循环引用$ref.xxx[x]
fastConverter.setFeatures(SerializerFeature.DisableCircularReferenceDetect);

 2。如果是代码显式转换,需要

//传入对象进行转换
JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect);

查阅了网上的资料,大多是https://www.cnblogs.com/zhujiabin/p/6132951.html

比较好的是用@JsonBackReference。

使用方法:在"多端"的getter 和setter方法上加上@JsonBackReference。如果是用的lombock。就加载属性字段上。

亲测:

不要打开注释这条

“1端”:

不要用@Data,即不用@toString,并且单端需要用@JsonBackReference

相当于破坏了单端的tostring方法

这样就不会内存溢出了。

 

 

设计:

user.java:

package com.yinlei.vue.entity;

import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.*;

import javax.persistence.*;
import javax.validation.constraints.Size;
import java.util.List;

/**
 * 实体类:
 * 用户(登录或者注册或者修改信息或者注销账户)
 * 外键关联:动态表应该隶属于用户表
 * 属于1对多:1个用户对应了多个动态
 * PA使用@OneToMany和@ManyToOne来标识一对多的双向关联。一端(Author)使用@OneToMany,多端(Article)使用@ManyToOne。
 * 在JPA规范中,一对多的双向关系由多端(Article)来维护。就是说多端(Article)为关系维护端,负责关系的增删改查。一端(Author)则为关系被维护端,不能维护关系。
 * 一端(Author)使用@OneToMany注释的mappedBy="author"属性表明Author是关系被维护端。
 *
 * 多端(Article)使用@ManyToOne和@JoinColumn来注释属性 author,@ManyToOne表明Article是多端,@JoinColumn设置在article表中的关联字段(外键)。
 */
@Table(name = "user")
@Entity
@Getter
@Setter
//@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    @Column(name = "user_id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer userId; //用户id

    @Column(name = "user_name", nullable = false)
    private String userName;//用户名

    @Column(name = "user_sex", nullable = true)
    private String userSex;//性别

    @Size(min=1, max=100)
    @Column(name = "user_age", nullable = true)
    private int userAge;//年龄

    @Column(name = "user_pwd", nullable = false)
    private String userPassword;//密码

    @Column(name = "user_tel", nullable = false)
    private String userTelephone;//手机号

    @Column(name = "user_email", nullable = false)
    private String userEmail;//电子邮件

//    1对多这里的1是User,多是动态。所以User是被维护端.不保持关系。
    @OneToMany(mappedBy = "belongOfUser", cascade = CascadeType.ALL,fetch = FetchType.LAZY)
    @JsonManagedReference
    private List<DongTai> dongTais;//动态列表
}

Dongtai.java: 

package com.yinlei.vue.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;

/**
 * 实体类:动态
 * 这个是重要的要与用户交互的类。
 * TODO 用户的登录注册
 */
@Table(name = "dongtai")
@Entity
@EntityListeners(AuditingEntityListener.class)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DongTai {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增长策略
    @Column(name = "dongtai_id", nullable = false)
    private Integer dongtaiId ;//动态id

    @Column(name = "dongtai_title", nullable = false)
    private String dongtaiTitle;//标题

    @Column(name = "select_location", nullable = false)
    private String dongtaiSelectLoca;//用户选择的地址

    @Column(name = "all_location", nullable = false)
    private String dongtaiAllLoca;//后端拥有提供给前端的全部地址

    @Column(name = "avatar", nullable = false)
    private String dongtaiAvatar;//上传图片的url

    @Column(name = "score", nullable = false)
    private String dongtaiScore;//心情等级

    @Lob  // 大对象,映射 MySQL 的 Long Text 类型
    @Basic(fetch = FetchType.LAZY) // 懒加载
    @Size(min = 2)
    @Column(name = "editcontent",nullable = false) // 映射为字段,值不能为空
    private String dongtaiEditContent;//用户编辑的内容

    /**
     * 创建时间
     */
    @CreatedDate
    @Column(name = "create_time")
    private Date createTime;

    /**
     * 修改时间
     */
    @LastModifiedDate
    @Column(name = "modify_time")
    private Date modifyTime;

    //1对多: 这里的动态是多端。维护关系端
    @JsonBackReference
    @ManyToOne(cascade = {CascadeType.MERGE,CascadeType.REFRESH},optional = false)//可选属性optional=false,表示user不能为空。删除动态,不影响用户
    @JoinColumn(name = "user_id")//设置在动态表中的关联字段(外键)
    private User belongOfUser;//动态所属用户
}

 

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐