杰克逊的 java.util.Optional 的 NotSerializableException [重复]

Posted

技术标签:

【中文标题】杰克逊的 java.util.Optional 的 NotSerializableException [重复]【英文标题】:NotSerializableException for java.util.Optional with Jackson [duplicate] 【发布时间】:2019-08-26 04:07:31 【问题描述】:

我正在尝试使用 JPA 保存一个实体,但为 Optional 类型的字段获取 NotSerializableException。 这里我的目标是以 JSON 序列化形式存储自定义对象(这里是 Grade 类型)。

[学生.java]

@Entity
@Table(name = "student")
public class Student 

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Size(min = 3, max = 20)
    private String name;

    @Lob
    private Grade grade;

    public Student() 
    

[等级.java]

public class Grade implements Serializable 

    /**
     * 
     */
    private static final long serialVersionUID = 7351334541533041431L;
    private Optional<Integer> marks;

    public Optional<Integer> getMarks() 
        return marks;
    

    public void setMarks(Optional<Integer> marks) 
        this.marks = marks;
    

    @Override
    public String toString() 
        return "Grade [marks=" + marks + "]";
    


[StudentRepository.java]

public interface StudentRepository extends JpaRepository<Student, Long> 

    public Student findByName(String name);

[StudentRepositoryTest.java]

@RunWith(SpringRunner.class)
@DataJpaTest
public class StudentRepositoryTest 
    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private StudentRepository studentRepository;

    @Test
    public void whenFindByName_thenReturnEmployee() 

        // given
        Student alex = new Student("alex");
        Grade grade = new Grade();
        grade.setMarks(Optional.ofNullable(90));
        alex.setGrade(grade);

        entityManager.persist(alex);
        entityManager.flush();

        // when
        Student found = studentRepository.findByName(alex.getName());

        System.out.println(found);
        // then
        assertThat(found.getName()).isEqualTo(alex.getName());
    

运行上述测试后,marks 类中的 marks 字段出现以下异常:

Caused by: java.io.NotSerializableException: java.util.Optional
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at org.hibernate.internal.util.SerializationHelper.serialize(SerializationHelper.java:115)
    ... 49 more

我已包含以下依赖项以支持 Jackson 的 java8 数据类型:

<dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jdk8</artifactId>
        </dependency>

Spring boot 版本:2.1.6.RELEASE

在https://github.com/Omkar-Shetkar/spring-repo-test分享了相同的代码

这里可能缺少什么?

【问题讨论】:

【参考方案1】:

看起来 Hibernate 尝试序列化您的 Grade 对象,并且由于它使用默认的序列化算法,它尝试序列化 Optional&lt;Integer&gt; marks 字段。

现在,在 Java 中 Optional 不可序列化:请参阅 https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

所以序列化失败了。

由于 Optional 不打算在数据字段中使用(原因请参阅 [此讨论]1),请考虑删除一个可选项,留下 Integer marks

【讨论】:

实际上我的目标是将自定义对象以 JSON 序列化形式存储到表中。自定义对象还包含 Optional 之外的其他字段。似乎hibernate正在使用Java序列化来存储对象。我可以改用杰克逊提供的 JSON 序列化吗? 我不太熟悉 Hibernate/JPA,但这似乎是相关的:***.com/questions/25738569/…【参考方案2】:

是的,Optional 不是 Serializable。 JPA 尝试序列化标有@Lob 的非文本(除Stringchar[]Char[] 之外的任何内容)字段。

推荐的方法是使用 JPA 转换器将对象序列化为 Json,同时在读取时持久化和反序列化持久化的 Json。 Jackson 支持序列化 Optional 字段,您只需添加 jackson-datatype-jdk8 依赖项并在您的 ObjectMapper 上注册 Jdk8ModuleGradeConverter 的实现如下所示:

@Converter
public class GradeConverter implements AttributeConverter<Grade, String> 

    private ObjectMapper objectMapper;

    public GradeConverter() 
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new Jdk8Module());
    

    @Override
    public String convertToDatabaseColumn(Grade grade) 

        if (grade == null) 
            return null;
        

        return objectMapper.writeValueAsString(grade);
    

    @Override
    public Grade convertToEntityAttribute(String s) 

        if (s == null) 
            return null;
        

        return objectMapper.readValue(s, Grade.class);
    

在您的 Grade 字段上:

@Convert(converter = GradeConverter.class)
private Grade grade;

如果你坚持对字段进行序列化(我的意思是Java序列化),你应该提供自定义的序列化方法。 您可以阅读更多关于 here 的信息。

【讨论】:

【参考方案3】:

杰克逊在这里完全无关紧要;当你说你想使用Serializable 时,你说的是Java 序列化,不管出于什么原因,Optional 不是Serializable

对于持久实体中此类“可选”属性的推荐方法是将实际值存储为可空字段并在 getter 和 setter 中进行转换。

【讨论】:

【参考方案4】:

com.google.common.base 也有 serializable 的 Optional 类:

Java Doc

Maven Repo

【讨论】:

虽然链接可以提供有用的信息,但最好多描述一下解决方案。 只需要添加[依赖]mvnrepository.com/artifact/com.google.guava/guava并将导入包从java optional改为guava optional即可。

以上是关于杰克逊的 java.util.Optional 的 NotSerializableException [重复]的主要内容,如果未能解决你的问题,请参考以下文章

UnsatisfiedDependencyException java.lang.IllegalArgumentException:查询方法公共抽象 java.util.Optional 的验证失败

com.fasterxml.jackson.databind.JsonMappingException:java.util.Optional 不能转换为 java.time.LocalDate

java 基本语法(十九)Optional类的使用

Java8新特性 - Optional容器类

Java8 Optional类

Java8新特性之:Optional