You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Log4j2需FilteredObjectInputStream,JPA仅提供ObjectInputStream如何解决?

解决Log4j2 JPA Appender与Hibernate输入流不兼容的问题

你遇到的这个问题本质上是Log4j2的安全反序列化机制(依赖FilteredObjectInputStream)和Hibernate/JPA默认使用标准ObjectInputStream之间的冲突。下面给你几个可行的解决方案:


1. 自定义Hibernate UserType来替换输入流实现

Hibernate允许我们自定义类型处理逻辑,通过实现UserType接口,我们可以在读取数据库中的日志事件二进制数据时,强制使用FilteredObjectInputStream进行反序列化:

步骤:

  • 实现UserType接口,重点在nullSafeGet方法中包装输入流:
import org.apache.logging.log4j.core.impl.FilteredObjectInputStream;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;
import java.io.*;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

public class LogEventUserType implements UserType {

    @Override
    public int[] sqlTypes() {
        return new int[]{Types.BLOB};
    }

    @Override
    public Class returnedClass() {
        return org.apache.logging.log4j.core.LogEvent.class;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return x == y || (x != null && y != null && x.equals(y));
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
        InputStream inputStream = rs.getBinaryStream(names[0]);
        if (inputStream == null) {
            return null;
        }
        try (FilteredObjectInputStream filteredStream = new FilteredObjectInputStream(inputStream)) {
            return filteredStream.readObject();
        } catch (ClassNotFoundException | IOException e) {
            throw new HibernateException("Failed to deserialize LogEvent", e);
        }
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.BLOB);
            return;
        }
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(value);
            st.setBinaryStream(index, new ByteArrayInputStream(baos.toByteArray()), baos.size());
        } catch (IOException e) {
            throw new HibernateException("Failed to serialize LogEvent", e);
        }
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        // LogEvent本身是不可变的,直接返回即可
        return value;
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    @Override
    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        return cached;
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }
}
  • 在你的日志实体类中,给存储LogEvent的字段标注自定义类型:
import org.hibernate.annotations.Type;
import javax.persistence.*;

@Entity
@Table(name = "log_events")
public class LogEventEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Type(type = "com.your.package.LogEventUserType")
    @Lob
    private org.apache.logging.log4j.core.LogEvent logEvent;

    // getter、setter方法
}

这样Hibernate在读取该字段时,就会使用我们自定义的逻辑,用FilteredObjectInputStream替代标准输入流。


2. 在实体类中手动控制序列化/反序列化

如果你不想自定义Hibernate类型,也可以直接在实体类中把LogEvent转换成byte[]存储,然后在getter方法中手动用FilteredObjectInputStream反序列化:

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.impl.FilteredObjectInputStream;
import javax.persistence.*;
import java.io.*;

@Entity
@Table(name = "log_events")
public class LogEventEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Lob
    private byte[] logEventBytes;

    // 手动实现LogEvent的序列化
    public void setLogEvent(LogEvent logEvent) {
        if (logEvent == null) {
            this.logEventBytes = null;
            return;
        }
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(logEvent);
            this.logEventBytes = baos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException("Failed to serialize LogEvent", e);
        }
    }

    // 手动用FilteredObjectInputStream反序列化
    public LogEvent getLogEvent() {
        if (this.logEventBytes == null) {
            return null;
        }
        try (FilteredObjectInputStream fis = new FilteredObjectInputStream(new ByteArrayInputStream(this.logEventBytes))) {
            return (LogEvent) fis.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Failed to deserialize LogEvent", e);
        }
    }

    // getter、setter for id
}

这种方式完全绕开了JPA默认的反序列化逻辑,直接由我们控制输入流类型,兼容性更好。


3. 考虑升级Log4j2版本

你当前使用的是2.11版本,虽然官方说适配了Java9,但后续的2.12+版本可能修复了JPA Appender中的输入流兼容问题。如果项目允许升级,可以尝试更新到较新的稳定版本(比如2.17.x,注意版本的Java兼容性),看看是否能自动解决这个问题。


总的来说,前两种方案都能实现“强制指定JPA输入流类”的需求,其中自定义Hibernate类型更贴合JPA的扩展规范,而实体类手动处理则更直观易维护,你可以根据项目情况选择。

内容的提问来源于stack exchange,提问作者secustor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:33:02