2

The code of my Spring Data repository method is as follows:

public Optional<byte[]> findShipmentLabelByClientIdAndAwb(String clientId, String awb) {

    String queryString = "select g.shipmentLabel as shipmentLabel from GenericShipment g where g.client.id = :clientId and g.shipmentId = :awb " +
        " AND (g.processingStatus is null or g.processingStatus <> 'DELETED') AND g.shipmentLabel is not null";

    val query = entityManager.createQuery(queryString, byte[].class);

    query.setParameter("clientId", clientId);
    query.setParameter("awb", awb);

    return query.getResultStream().findFirst();

}

As you can see, I am attempting to fetch, as byte array, the shipmentLabel column, defined in my Postgres schema as bytea. The following exception occurs at runtime:

java.lang.ClassCastException: [B cannot be cast to [Ljava.lang.Object; at org.hibernate.internal.ScrollableResultsImpl.prepareCurrentRow(ScrollableResultsImpl.java:203) at org.hibernate.internal.ScrollableResultsImpl.next(ScrollableResultsImpl.java:101) at org.hibernate.query.internal.ScrollableResultsIterator.hasNext(ScrollableResultsIterator.java:33) at java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1811) at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:499) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:486) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:531) at org.hibernate.query.spi.StreamDecorator.findFirst(StreamDecorator.java:260)

I was wondering if this is the intended behaviour here or not, thanks in advance for your answers.

For the time being, the workaround is to use the JPA 2.1 variant:

return query.getResultList().stream().findFirst();

As environment, I am using Spring Boot 2.3.3, Hibernate version is 5.4.20.

Cyril G.
  • 1,585
  • 2
  • 2
  • 15

2 Answers2

3

Try with getResultList first, and see if it works:

public Optional<Byte[]> findShipmentLabelByClientIdAndAwb(
        String clientId, String awb) {
    return entityManager.createQuery("""
        select 
            g.shipmentLabel as shipmentLabel 
        from GenericShipment g 
        where 
            g.client.id = :clientId and 
            g.shipmentId = :awb and 
            (
                g.processingStatus is null or 
                g.processingStatus <> 'DELETED'
            ) and 
            g.shipmentLabel is not null
        """)
    .setParameter("clientId", clientId)
    .setParameter("awb", awb)
    .setMaxResults(1)
    .getResultList()
    .stream()
    .findFirst();
}

Note that it's inefficient to select N records only to take the first one using fidFirst. What if this query returns 100 records? You'd still select all 100 from the DB.

That's why I added the setMaxResults call.

If that doesn't work, try debugging the Hibernate BinaryType and see why it doesn't return byte[].

Vlad Mihalcea
  • 103,297
  • 39
  • 432
  • 788
  • With our current data model and constraints, it is quite impossible to have more than one record returned for the given WHERE clause. I will try to use Byte[] instead of the primitive and return with my findings, thanks. – Dragoş Haiduc Sep 25 '20 at 07:52
  • query.getResultStream().findFirst() still fails in org.hibernate.internal.ScrollableResultsImpl at line 230, where it explicitly attempts to cast the primitive byte array to Object[]. Guess I will stick with query.getResultList().stream().findFirst(). – Dragoş Haiduc Sep 25 '20 at 08:49
  • Use `getResultLust().stream()` then and open a Hibernate Jira issue. – Vlad Mihalcea Sep 25 '20 at 09:36
  • 1
    Thank you, I opened issue https://hibernate.atlassian.net/browse/HHH-14231 – Dragoş Haiduc Sep 25 '20 at 12:09
  • This pull request should solve this in future releases: https://github.com/hibernate/hibernate-orm/pull/3563 – Dragoş Haiduc Sep 28 '20 at 06:27
  • @DragoşHaiduc Cool. I'm glad Nathan was quick on this. – Vlad Mihalcea Sep 28 '20 at 07:20
0

There is no primitive stream for byte, you can check in the debugger what type of stream your are getting maybe Stream<Byte[]> or Stream<Object[]>. This can explain the exception you are getting. Using Byte[] should solve your issue.

public Optional<Byte[]> findShipmentLabelByClientIdAndAwb(String clientId, String awb) {
    String queryString = "select g.shipmentLabel as shipmentLabel from GenericShipment g where g.client.id = :clientId and g.shipmentId = :awb " +
        " AND (g.processingStatus is null or g.processingStatus <> 'DELETED') AND g.shipmentLabel is not null";

     val query = entityManager.createQuery(queryString, Byte[].class);

     query.setParameter("clientId", clientId);
     query.setParameter("awb", awb);

     return query.getResultStream().findFirst();

}

Cyril G.
  • 1,585
  • 2
  • 2
  • 15
  • Tried this already, the following exception occurs: java.lang.IllegalArgumentException: Type specified for TypedQuery [[Ljava.lang.Byte;] is incompatible with query return type [class [B] – Dragoş Haiduc Sep 25 '20 at 08:13