【问题标题】:NotSerializableException for java.util.Optional with Jackson [duplicate]杰克逊的 java.util.Optional 的 NotSerializableException [重复]
【发布时间】: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分享了相同的代码

这里可能缺少什么?

【问题讨论】:

    标签: java spring-boot jackson


    【解决方案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,但这似乎是相关的:stackoverflow.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

        【讨论】:

        猜你喜欢
        • 2016-09-11
        • 2013-08-16
        • 1970-01-01
        • 2020-04-12
        • 1970-01-01
        • 2018-04-03
        • 2019-05-02
        • 2020-02-23
        • 1970-01-01
        相关资源
        最近更新 更多