Binding Parameter to VARBINARY Hibernate 6.4.4

Hi,

in JPA we designed OneToMany relationship where the foreign key realitionship is done via a UUID:

@Id
@Column(columnDefinition = "TEXT")
private String id;

The relation is a follows:

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "id" , nullable = false)
@BatchSize(size = BATCH_SIZE)
@EqualsAndHashCode.Exclude
private Set<Elements> elements;

In some testcases we do a jparepository.deleteAll() call to clean up the database.
Since the upgrade to hibernate 6 we got problems in doing that.

The SQL is as follows:

select
e1_0.id,
e1_0.propertyA,
from
elements e1_0
where
e1_0.id = any (?)

Now I can see in the log:
org.hibernate.orm.jdbc.bind - binding parameter (1:VARBINARY) ← [[b08fb7b8-2a0e-4584-81be-a64b7a87a8f6, a19f1716-ae46-4993-bcb8-0089b3110998]]

and afterwards
java.lang.ClassCastException: class [Ljava.lang.String; cannot be cast to class [B ([Ljava.lang.String; and [B are in module java.base of loader ‘bootstrap’)

I guess, that for the jpa delteAll at first a findAll is done, and then a deleteAll with the any clause is done.
Why does the orm binder binds those values to varbinary? Is that the problem?

Thanks in advance!

Hello, I don’t know what calls the deleteAll Spring repository operation is performing to trigger this problem, the issue might be related to how they’re binding the identifiers.

Could you please try to reproduce this error using plain Hibernate? Also please share the complete mappings and the full stack-trace of the error, otherwise it will be impossible to help you further.

Hi, well, that what you describe is one of the problems I think.

That what you see is the complete Mapping. The Elements class has no id column specified. Its unidirectional. In the DB (postgres) this is a column of type text.

And thats almos all from the log. There are no more log statements related to this.

Doing this with plain hibernate will work. Then the actions have to be done separately, first getting the entities, second get the child elements and thrid delete them by Id or any criteria else…then go ahead do the rest.

JPA and the sping-boot stuff provides “doMagic” to do this in one call. But here also at first all entities are fetched, then the oneToMany elements are fetched via this any(?)-clause. And this parameter binding fails (for me).

In the former version where this deleteAll works, the fetch from the oneToMany elements is done via a in clause.

… where id in (?, ?, ?)

Hmmm I will look for a workaround for the moment…

That what you see is the complete Mapping

I meant if you could share the entire entity classes involved in this example, instead of just the association code snippet you posted before, just so we have a complete picture of your mappings. Also please share the entire stack trace instead of just the error message.

Doing this with plain hibernate will work

Then maybe this is not an Hibernate problem? You can try asking the Spring folks for help.

JPA and the sping-boot stuff provides “doMagic” to do this in one call

I assure you spring-boot is not “magic” :wink: under the hood, all calls from Spring Data rely on Hibernate functionality - I suggest debugging into the deleteAll method to try and figure out what calls are made and how this might be reproduced with plain Hibernate.

I figured out that the reason was having hypersistence-utils-hibernate on the classpath, in combination with @BatchSize annotation on my mappings. The following example ends in a

class [Ljava.lang.String; cannot be cast to class [B ([Ljava.lang.String; and [B are in module java.base of loader 'bootstrap')

Thats because of fetching the addresses in batch. Thats done via:

    select
        a1_0.user_id,
        a1_0.id,
        a1_0.country,
        a1_0.postal_code,
        a1_0.is_primary 
    from
        users_addresses a1_0 
    where
        a1_0.user_id = any (?)

This any() clause is causing the ClassCastException.

public class Address {

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

    @Column(columnDefinition = "TEXT")
    private String postalCode;

    @Column(columnDefinition = "TEXT")
    private String country;

    @Basic
    @Builder.Default
    @Column(columnDefinition = "boolean default false")
    private Boolean primary = false;
}


public class User {

    private static final String JOIN_COLUMN_NAME = "user_id";
    private static final int BATCH_SIZE = 50;

    @Id
    @Column(columnDefinition = "TEXT")
    private String id;

    @Column(columnDefinition = "TEXT")
    private String userName;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
    @JoinColumn(name = JOIN_COLUMN_NAME, nullable = false)
    @BatchSize(size = BATCH_SIZE)
    @EqualsAndHashCode.Exclude
    private Set<Address> addresses;
}

public class Test {
    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("org.example");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        try (entityManager) {
            EntityTransaction transaction = entityManager.getTransaction();
            transaction.begin();

            CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
            CriteriaQuery<User> query = criteriaBuilder.createQuery(User.class);

            query.from(User.class);
            List<User> users = entityManager.createQuery(query).getResultList();

            for (User user : users) {
                log.info("User {}", user);
            }
        }
    }
}

    

Accordingly I found a solution described in an Issue of the hypersistence repo:

https://github.com/vladmihalcea/hypersistence-utils/issues/700

This works for me so far.

Setting enable_types_contributor to false:

spring:
  jpa:
    properities:
      hypersistence:
        utils:
          enable_types_contributor: false