这里的业务场景是一个用户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;//动态所属用户
}