Hibernate框架下使用Gson遇到StackOverflowError时如何解决

本文介绍在Hibernate框架下,处理多对多关系的学生和选修课实例时,如何避免StackOverflowError异常。通过使用transient关键字来防止序列化过程中的无限循环引用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在hibernate框架下,我们经常会遇到使用多对多映射,比如学生和选修课、项目和成员等等。在这时候想把一个放着学生的List集合转换成JSON对象时,容易遇到StackOverFlowError。

Exception in thread "main" java.lang.StackOverflowError
at sun.util.locale.provider.LocaleProviderAdapter.getAdapter(LocaleProviderAdapter.java:243)
at java.text.DateFormatSymbols.getProviderInstance(DateFormatSymbols.java:357)
at java.text.DateFormatSymbols.getInstance(DateFormatSymbols.java:335)
at java.util.Calendar.getDisplayName(Calendar.java:2110)
at java.text.SimpleDateFormat.subFormat(SimpleDateFormat.java:1125)

  ......

这个异常是怎么产生的呢,原因是在双向多对多映射会在各自的实体类中只有对方的集合,这样在进行序列化的时候会导致互相引用,学生类要引用课程类,到课程类又要引用学生类,最后导致死循环。

想要解决这个异常很简单,互相引用只要在序列化的时候忽略一种对另一种的引用,这样就不会导致无限循环引用,这时候就需要使用transient这个关键字了,在对象进行序列化的时候忽略被transient标识的成员变量,所以只要在任意一方的引用上加上transient关键字,就可以避免发生这个错误了。或者可以搜索Gson如何忽略某个属性也可以解决这个问题。下面附上代码。

课程实体类:

@Entity
@Table(name = "course")
public class Course implements Serializable{
    private int id;                     //主键id
    private Integer cid;                //课程号
    private String cname;               //课程名
    private Double credit;              //学分
    private Double period;              //课时
    private transient Teacher teachers;           //与老师类是一对多映射
    private transient Set students;      //与学生是多对多映射
    
    此处省略了setter和getter方法
}

学生实体类:
@Entity
@Table(name = "course")
public class Course implements Serializable{
    private int id;                     //主键id
    private Integer cid;                //课程号
    private String cname;               //课程名
    private Double credit;              //学分
    private Double period;              //课时
    private transient Teacher teachers;           //与老师类是一对多映射
    private transient Set students;      //与学生是多对多映射
    
    此处省略了setter和getter方法
}

测试类:
public class GsonTest {
    public static void main(String[] args) {
        StudentDAOImpl studentDAO = new StudentDAOImpl();
        Gson gson = new Gson();
        List studentList = new ArrayList<>();
        Student student1 = studentDAO.findStudentByID(4);
        Student student2 = studentDAO.findStudentByID(5);
        studentList.add(student1);
        studentList.add(student2);
        String json = gson.toJson(studentList);
        System.out.println(json);
    }
}

输出结果:

[{"id":4,"sno":201419229,"sname":"曾卓","password":"123456","gender":"男","age":21,"phone":"13000000000","year":"六月 10, 1995",
"courses":[{"id":1,"cid":1,"cname":"高数","credit":4.0,"period":64.0}]},
{"id":5,"sno":201419228,"sname":"小王","password":"123456","gender":"男","age":20,"phone":"13200000000","year":"一月 22, 1997","courses":[]}]




<think>我们使用Gson来处理泛型集合`List<?extendsAction>`的序列化和反序列化。由于Java的泛型擦除,我们需要在反序列化提供具体的类型信息。以下是解决方案:###1.序列化(对象→JSON)序列化Gson可以直接处理`List<?extendsAction>`,因为运行类型是具体的子类集合。```javaimportcom.google.gson.Gson;importjava.util.ArrayList;importjava.util.List;publicclassSerializationDemo{publicstaticvoidmain(String[]args){//创建具体子类对象的列表List<ActionImpl>actions=newArrayList<>();actions.add(newActionImpl("task1"));actions.add(newActionImpl("task2"));Gsongson=newGson();Stringjson=gson.toJson(actions);System.out.println(json);//输出示例:[{"name":"task1"},{"name":"task2"}]}}```###2.反序列化(JSON→对象)反序列化,我们需要指定具体的列表类型。由于`List<?extendsAction>`本身无法直接实例化,我们需要将其反序列化为一个具体子类的列表(如`List<ActionImpl>`),然后赋值给一个声明为`List<?extendsAction>`的变量。**步骤:**1.使用`TypeToken`捕获泛型类型信息。2.使用`fromJson`方法并传递正确的`Type`。```javaimportcom.google.gson.Gson;importcom.google.gson.reflect.TypeToken;importjava.lang.reflect.Type;importjava.util.List;publicclassDeserializationDemo{publicstaticvoidmain(String[]args){Stringjson="[{\"name\":\"task1\"},{\"name\":\"task2\"}]";Gsongson=newGson();//使用TypeToken指定具体类型:List<ActionImpl>Typetype=newTypeToken<List<ActionImpl>>(){}.getType();List<?extendsAction>actions=gson.fromJson(json,type);//使用列表中的对象for(Actionaction:actions){System.out.println(action.execute());}}}```###3.处理多态类型如果列表中包含多种`Action`子类,我们需要在序列化和反序列化处理多态。Gson默认不保存类型信息,因此我们需要自定义序列化策略。####方案:注册类型适配器(TypeAdapter)或使用RuntimeTypeAdapterFactory**步骤:**1.添加Gson扩展库(如`gson-extras`)以使用`RuntimeTypeAdapterFactory`。2.配置Gson实例,注册多态类型处理。**Maven依赖:**```xml<dependency><groupId>org.danilopianini</groupId><artifactId>gson-extras</artifactId><version>0.2.0</version></dependency>```**代码示例:**```javaimportcom.google.gson.Gson;importcom.google.gson.GsonBuilder;importorg.danilopianini.gson.extras.RuntimeTypeAdapterFactory;publicclassPolymorphicDemo{publicstaticvoidmain(String[]args){//创建支持多态的RuntimeTypeAdapterFactoryRuntimeTypeAdapterFactory<Action>typeFactory=RuntimeTypeAdapterFactory.of(Action.class,"type")//指定基类和类型字段名.registerSubtype(ActionImpl.class,"action_impl")//注册子类.registerSubtype(OtherAction.class,"other_action");Gsongson=newGsonBuilder().registerTypeAdapterFactory(typeFactory).create();//序列化List<Action>actions=newArrayList<>();actions.add(newActionImpl("task1"));actions.add(newOtherAction("special_task"));Stringjson=gson.toJson(actions);System.out.println(json);//输出:[{"type":"action_impl","name":"task1"},{"type":"other_action","id":"special_task"}]//反序列化Typetype=newTypeToken<List<Action>>(){}.getType();List<Action>deserialized=gson.fromJson(json,type);deserialized.forEach(System.out::println);}}```###4.核心注意事项-**类型安全**:反序列化后,列表中的对象是具体的子类实例,可以直接使用。-**无参构造器**:确保子类有无参构造器(或使用`@Expose`等注解控制字段),否则Gson可能无法实例化对象。-**字段可见性**:子类字段应为`public`或提供`public`的getter/setter,否则Gson可能无法访问。###5.性能优化-**复用Gson实例**:避免重复创建(线程安全)。-**缓存TypeToken**:对于频繁使用的类型,缓存`TypeToken`对象。>**警告**:如果JSON数组中包含多种子类对象,必须使用多态处理方案(如`RuntimeTypeAdapterFactory`),否则反序列化会失败。§§相关问题§§1.Gson中如何处理循环引用?2.如何自定义Gson的日期格式?3.Gson与Jackson在多态处理上的区别是什么?4.如何在SpringBoot中配置Gson作为默认JSON处理器?</think>### 使用 Gson 处理泛型集合 `List<? extends Action>` 的 JSON 转换 #### 一、解决方案概述 Gson 通过 `TypeToken` 解决泛型类型擦除问题,结合多态类型适配器实现接口泛型集合的转换。以下是完整实现: #### 二、序列化(对象 → JSON) ```java import com.google.gson.Gson; import java.util.List; public class SerializationDemo { public static void main(String[] args) { List<? extends Action> actions = List.of( new ActionImpl("task1"), new OtherAction(123) ); Gson gson = new Gson(); String json = gson.toJson(actions); System.out.println(json); // 输出: [{"type":"action_impl","name":"task1"},{"type":"other_action","code":123}] } } ``` #### 三、反序列化(JSON → 对象) ```java import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.util.List; public class DeserializationDemo { public static void main(String[] args) { String json = "[{\"type\":\"action_impl\",\"name\":\"task1\"}]"; // 1. 创建支持多态的 Gson 实例 Gson gson = createPolymorphicGson(); // 2. 通过 TypeToken 捕获泛型类型 Type type = new TypeToken<List<? extends Action>>(){}.getType(); // 3. 执行反序列化 List<? extends Action> actions = gson.fromJson(json, type); actions.forEach(a -> System.out.println(a.execute())); } } ``` #### 四、核心配置 ##### 1. 定义多态类型适配器(关键) ```java import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import java.io.IOException; public class PolymorphicAdapter extends TypeAdapter<Action> { private static final String TYPE_FIELD = "type"; @Override public void write(JsonWriter out, Action value) throws IOException { // 序列化自动添加类型标识 out.beginObject(); out.name(TYPE_FIELD).value(value.getClass().getSimpleName()); // 使用 Gson 默认序列化其他字段 Gson gson = new Gson(); gson.toJson(value, value.getClass(), out); out.endObject(); } @Override public Action read(JsonReader in) throws IOException { // 动态识别类型并反序列化 in.beginObject(); String typeName = null; Action result = null; while (in.hasNext()) { String field = in.nextName(); if (TYPE_FIELD.equals(field)) { typeName = in.nextString(); } else { in.skipValue(); // 跳过非类型字段 } } in.endObject(); // 根据类型标识创建实例 if ("ActionImpl".equals(typeName)) { result = new Gson().fromJson(in, ActionImpl.class); } else if ("OtherAction".equals(typeName)) { result = new Gson().fromJson(in, OtherAction.class); } return result; } } ``` ##### 2. 创建支持多态的 Gson 实例 ```java private static Gson createPolymorphicGson() { return new GsonBuilder() .registerTypeAdapter(Action.class, new PolymorphicAdapter()) .create(); } ``` ##### 3. 接口和实现类定义 ```java public interface Action { String execute(); } public class ActionImpl implements Action { private String name; // 必须有无参构造器 public ActionImpl() {} public ActionImpl(String name) { this.name = name; } @Override public String execute() { return "Executing: " + name; } } public class OtherAction implements Action { private int code; public OtherAction() {} public OtherAction(int code) { this.code = code; } @Override public String execute() { return "Code: " + code; } } ``` #### 五、关键问题解决 | 问题 | Gson 解决方案 | |---------------------|-----------------------------------| | **泛型类型擦除** | 使用 `TypeToken` 捕获泛型类型信息[^2] | | **多态类型识别** | 自定义 `TypeAdapter` 添加类型标识 | | **无参构造缺失** | 所有子类必须提供默认构造器 | | **字段过滤** | 使用 `@Expose(serialize=false)` 或 `transient` | #### 六、最佳实践建议 1. **类型标识字段**:使用固定字段(如 `type`)存储类名,确保反序列化可识别 2. **注册子类型**:在适配器中维护类型映射表,避免硬编码类名 3. **安全校验**:反序列化后添加类型检查 ```java actions.stream() .filter(a -> a instanceof ActionImpl) .map(ActionImpl.class::cast) .forEach(System.out::println); ``` 4. **性能优化**:复用 Gson 实例(线程安全) > **注意**:当 JSON 包含多种子类型,必须通过类型字段明确区分,否则会反序列化失败。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值