Hibernate - 无法执行 beforeTransactionCompletion 回调:无法读取数组长度,因为“array”为 null

问题描述 投票:0回答:1

Spring Boot 从版本

2.7.14
升级到
3.1.2
后,我收到以下错误。

Caused by: org.hibernate.HibernateException: Unable to perform beforeTransactionCompletion callback: Cannot read the array length because "array" is null
at org.hibernate.engine.spi.ActionQueue$BeforeTransactionCompletionProcessQueue.beforeTransactionCompletion(ActionQueue.java:1016)
at org.hibernate.engine.spi.ActionQueue.beforeTransactionCompletion(ActionQueue.java:548)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1967)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:169)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:267)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:561)
... 143 common frames omitted
Caused by: java.lang.NullPointerException: Cannot read the array length because "array" is null
at java.base/java.util.Arrays.stream(Arrays.java:5428)
at io.hypersistence.utils.hibernate.type.util.ParameterTypeUtils.getAnnotations(ParameterTypeUtils.java:76)
at io.hypersistence.utils.hibernate.type.util.ParameterTypeUtils.getAnnotationOrNull(ParameterTypeUtils.java:53)
at io.hypersistence.utils.hibernate.type.util.ParameterTypeUtils.getColumnType(ParameterTypeUtils.java:90)
at io.hypersistence.utils.hibernate.type.json.internal.JsonJdbcTypeDescriptor.resolveJdbcTypeDescriptor(JsonJdbcTypeDescriptor.java:90)
at io.hypersistence.utils.hibernate.type.json.internal.JsonJdbcTypeDescriptor.sqlTypeDescriptor(JsonJdbcTypeDescriptor.java:72)
at io.hypersistence.utils.hibernate.type.json.internal.JsonJdbcTypeDescriptor$1.doBind(JsonJdbcTypeDescriptor.java:40)
at org.hibernate.type.descriptor.jdbc.BasicBinder.bind(BasicBinder.java:61)
  • 我使用 Oracle Database 19c 企业版,生产版本 19.14.0.0.0,带有 JDBC 驱动程序
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>21.1.0.0</version>
            <scope>runtime</scope>
        </dependency>
  • Hibernate 版本是

    6.2.6.Final
    (由 Spring Boot 管理)。

  • 我使用Hibernate Envers进行审计,因此我将版本更改为

    6.2.6.Final

 <dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate-envers</artifactId>
     <version>6.2.6.Final</version>
  </dependency>
  • 此外,我使用 hypersistence-utils 使用
    CUSTOMER_LOCALIZED_FULLNAME
    将列 @Type(JsonType.class) 映射为
    JSON
    ,请参阅
    CustomerDetailsEntity
<dependency>
    <groupId>io.hypersistence</groupId>
    <artifactId>hypersistence-utils-hibernate-62</artifactId>
    <version>3.5.1</version>
</dependency>

经过一些调试,我意识到问题与Hibernate EnversHypersistence Utils机制有关,用于在

Hibernate Envers
生成的CCS_CUSTOMER_DETAILS_AUDIT_LOG实体中处理@Type(JsonType.class)

Caused by: java.lang.NullPointerException: Cannot read the array length because "array" is null
at java.base/java.util.Arrays.stream(Arrays.java:5428)
at io.hypersistence.utils.hibernate.type.util.ParameterTypeUtils.getAnnotations(ParameterTypeUtils.java:76)

它使用 H2 工作,因此问题涉及到 Oracle 方言。

您在迁移到 Spring Boot 3 时是否遇到过类似的问题?如果是这样,你是如何解决的?

数据库表:

CREATE TABLE CCS_CUSTOMER_DETAILS
(
    CUSTOMER_NUMBER                 VARCHAR2(9)  NOT NULL
        CONSTRAINT CCS_CUSTOMER_DETAILS_PK
            PRIMARY KEY,
    CUSTOMER_EGN                    VARCHAR2(10) NOT NULL
        CONSTRAINT CCS_CUSTOMER_DETAILS_EGN_UNIQUE
            UNIQUE,
    CUSTOMER_IS_AGREED_TO_USE_OF_DATA NUMBER(1, 0) NOT NULL,
    CUSTOMER_LOCALIZED_FULLNAME              VARCHAR2(4000) NOT NULL
        CONSTRAINT LOCALIZED_FULLNAME_IS_JSON
            CHECK (CUSTOMER_LOCALIZED_FULLNAME IS JSON),
    DRIVING_LICENSE_NUMBER          VARCHAR2(9),
    DRIVING_LICENSE_EXPIRATION_DATE DATE,
    CUSTOMER_STATUS              SMALLINT  NOT NULL,
    CREATED_ON                   TIMESTAMP NOT NULL,
    LAST_UPDATED_ON              TIMESTAMP NOT NULL
);

CREATE TABLE CCS_AUDIT_REVISIONS_INFO
(
    REVISION_NUMBER      NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY
        CONSTRAINT CCS_SUBSCRIPTION_SERVICES_PK
            PRIMARY KEY,
    REVISION_TIMESTAMP  NUMBER
);

CREATE TABLE CCS_CUSTOMER_DETAILS_AUDIT_LOG
(
    REVISION_NUMBER_START           NUMBER       NOT NULL,
    REVISION_NUMBER_END             NUMBER,
    REVISION_TYPE                   SMALLINT,
    CUSTOMER_NUMBER                 VARCHAR2(9),
    CUSTOMER_IS_AGREED_TO_USE_OF_DATA NUMBER(1, 0),
    CUSTOMER_LOCALIZED_FULLNAME     VARCHAR2(4000),
    CUSTOMER_EGN                    VARCHAR2(10),
    DRIVING_LICENSE_NUMBER          VARCHAR2(9),
    DRIVING_LICENSE_EXPIRATION_DATE DATE,
    CUSTOMER_STATUS              SMALLINT,
    CREATED_ON                   TIMESTAMP,
    LAST_UPDATED_ON              TIMESTAMP,
    CONSTRAINT CCS_AUDIT_REVISIONS_INFO_START_FK
        FOREIGN KEY (REVISION_NUMBER_START) REFERENCES CCS_AUDIT_REVISIONS_INFO,
    CONSTRAINT CCS_AUDIT_REVISIONS_INFO_END_FK
        FOREIGN KEY (REVISION_NUMBER_END) REFERENCES CCS_AUDIT_REVISIONS_INFO,
    CONSTRAINT CCS_CUSTOMER_DETAILS_AUDIT_LOG_PK
        PRIMARY KEY (CUSTOMER_NUMBER, REVISION_NUMBER_START)
);

JPA 实体:

@Getter
@Setter
@Entity
@Audited
@Table(name = "CCS_CUSTOMER_DETAILS")
public class CustomerDetailsEntity {

    @Id
    @Column(name = "CUSTOMER_NUMBER", nullable = false)
    private String customerNumber;

    @Column(name = "CUSTOMER_EGN", unique = true, nullable = false)
    private String egn;

    @Column(name = "DRIVING_LICENSE_NUMBER", unique = true, nullable = false)
    private String drivingLicenseNumber;

    @Column(name = "DRIVING_LICENSE_EXPIRATION_DATE", nullable = false)
    private LocalDate drivingLicenseExpirationDate;

    @Column(name = "CUSTOMER_IS_AGREED_TO_USE_OF_DATA", nullable = false)
    private boolean agreedToUseOfData;

    @Type(JsonType.class)
    @Column(name = "CUSTOMER_LOCALIZED_FULLNAME", nullable = false)
    private Map<Alphabet, FullName> localizedFullName;

    @Enumerated
    @Column(name = "CUSTOMER_STATUS", nullable = false)
    private Status status;

    @CreationTimestamp
    @Column(name = "CREATED_ON", updatable = false)
    private LocalDateTime createdOn;

    @UpdateTimestamp
    @Column(name = "LAST_UPDATED_ON")
    private LocalDateTime lastUpdatedOn;
}

@Getter
@Setter
@Entity
@RevisionEntity
@Table(name = "CCS_AUDIT_REVISIONS_INFO")
public class RevisionInfoEntity {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @RevisionNumber
    @Column(name = "REVISION_NUMBER")
    private long id;

    @RevisionTimestamp
    @Column(name = "REVISION_TIMESTAMP")
    private long timestamp;
spring-boot hibernate spring-data-jpa hibernate-envers
1个回答
0
投票

我相信,该问题与 hipersistence-utils-hibernate-62 版本

3.5.1
hibernate-envers 版本
6.2.1.Final
之间的不兼容问题有关。

我已经解决了这个问题,为

private Map<Alphabet, FullName> localizedFullName;
字段实现了自定义转换器:


@Getter
@Setter
@Entity
@Audited
@Table(name = "CCS_CUSTOMER_DETAILS")
public class CustomerDetailsEntity {

   // code omitted

   @Convert(converter = FullNameMapToJsonConverter.class)  
   @Column(name = "CUSTOMER_LOCALIZED_FULLNAME", nullable = false)  
   private Map<Alphabet, FullName> localizedFullName;
   
}

@Converter  
class FullNameMapToJsonConverter implements AttributeConverter<Map<Alphabet, FullName>, String> {  

    private final ObjectMapper objectMapper = new ObjectMapper();
      
    @Override  
    public String convertToDatabaseColumn(Map<Alphabet, FullName> customerInfo) {
        if (customerInfo == null) return "{}";  
      
        try {  
            return objectMapper.writeValueAsString(customerInfo);  
        } catch (Exception ex) {
            new IllegalArgumentException("JSON writing error", ex); 
        }  
    }
      
    @Override  
    public Map<Alphabet, FullName> convertToEntityAttribute(String customerInfoJSON) {
        if (customerInfoJSON == null || customerInfoJSON.isBLank()) return Map.of();
          
        try {  
            return bjectMapper.readValue(customerInfoJSON, new TypeReference<Map<Alphabet, FullName>>() {  
            });  
        } catch (Exception ex) {  
            new IllegalArgumentException("JSON reading error", ex); 
        }  
    }  
}
© www.soinside.com 2019 - 2024. All rights reserved.