2

I have problem with UUID ID fields defined by domain

CREATE DOMAIN OCTET16 AS CHAR(16) CHARACTER SET OCTETS;

I have defined AttributeConverter for jpa as import java.util.UUID;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

import com.ekser.nakkash.icdv.tools.UUIDTools;

@Converter
public class UUIDAttributeConverter implements AttributeConverter<UUID, byte[]> {
    @Override
    public byte[] convertToDatabaseColumn(UUID arg0) {
        return UUIDTools.asBytes((UUID) arg0);
    }
    @Override
    public UUID convertToEntityAttribute(byte[] arg0) {
        return UUIDTools.asUUID((byte[])arg0);
    }
}

and defined jpa fields as

@Id
@Convert(converter = UUIDAttributeConverter.class)
private UUID id;

but jpa (Eclipselink+Eclipse Gemini) gives error as it tries to convert not binary presentation but textual form of 36 character

I suppose problem is that jaybird gives type of field as CHAR(16), (I have checked with ResultSetMetaData), I know that there is option octetsAsBytes, I have used it with jbdc url like

jdbc:firebirdsql:localhost:D:/aktarma-12-13/VERI.FDB?octetsAsBytes=true

with no result.

My setup is pretty complicated

Java 1.8
Efxclipse 2.4.0 RCP
Firebird 2.5.6
Jaybird 2.2.12 JDK_1.8
HikariCP 2.4.1
EclipseLink 2.6.4
Eclipse Gemini 1.2.0.M1

So I think when ResultSetMetaData will give me type of field as BINARY the problem will gone. But how? Any suggestions.

I just not want to hold ids as char(32) or char(36).

Edit: After switching to Jaybird 3.beta-2, one can clearly see on third line invalid stream header: F1505533. This is a part ASCII decoded id (char(16) character set octets). How to make JPA to accept this value as raw byte and make it pass to UUIDAttributeConverter class??!!

Exception in thread "JavaFX Application Thread" javax.persistence.PersistenceException: Exception [EclipseLink-66] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: Could not deserialize object from byte array.
Internal Exception: java.lang.RuntimeException: java.io.StreamCorruptedException: invalid stream header: F1505533
Mapping: org.eclipse.persistence.mappings.DirectToFieldMapping[id-->YAZDIRMA_TURLERI.ID]
Descriptor: RelationalDescriptor(com.ekser.nakkash.icdv.pojo.YazdirmaTuru --> [DatabaseTable(YAZDIRMA_TURLERI)])
    at org.eclipse.persistence.internal.jpa.QueryImpl.getResultList(QueryImpl.java:484)
    at com.ekser.nakkash.icdv.gui.MainPartIcdv.lambda$1(MainPartIcdv.java:180)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Node.fireEvent(Node.java:8413)
    at javafx.scene.control.Button.fire(Button.java:185)
    at com.sun.javafx.scene.control.behavior.ButtonBehavior.mouseReleased(ButtonBehavior.java:182)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:380)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:294)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$354(GlassViewEventHandler.java:416)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:415)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Unknown Source)
Caused by: Exception [EclipseLink-66] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: Could not deserialize object from byte array.
Internal Exception: java.lang.RuntimeException: java.io.StreamCorruptedException: invalid stream header: F1505533
Mapping: org.eclipse.persistence.mappings.DirectToFieldMapping[id-->YAZDIRMA_TURLERI.ID]
Descriptor: RelationalDescriptor(com.ekser.nakkash.icdv.pojo.YazdirmaTuru --> [DatabaseTable(YAZDIRMA_TURLERI)])
    at org.eclipse.persistence.exceptions.DescriptorException.notDeserializable(DescriptorException.java:1232)
    at org.eclipse.persistence.mappings.converters.SerializedObjectConverter.convertDataValueToObjectValue(SerializedObjectConverter.java:144)
    at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.getObjectValue(AbstractDirectMapping.java:616)
    at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.valueFromRow(AbstractDirectMapping.java:1220)
    at org.eclipse.persistence.mappings.DatabaseMapping.readFromRowIntoObject(DatabaseMapping.java:1539)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildAttributesIntoObject(ObjectBuilder.java:462)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:1005)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildWorkingCopyCloneNormally(ObjectBuilder.java:899)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObjectInUnitOfWork(ObjectBuilder.java:852)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:735)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:689)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.buildObject(ObjectLevelReadQuery.java:805)
    at org.eclipse.persistence.queries.ReadAllQuery.registerResultInUnitOfWork(ReadAllQuery.java:962)
    at org.eclipse.persistence.queries.ReadAllQuery.executeObjectLevelReadQuery(ReadAllQuery.java:573)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1175)
    at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:904)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1134)
    at org.eclipse.persistence.queries.ReadAllQuery.execute(ReadAllQuery.java:460)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1222)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1857)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1839)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1804)
    at org.eclipse.persistence.internal.jpa.QueryImpl.executeReadQuery(QueryImpl.java:258)
    at org.eclipse.persistence.internal.jpa.QueryImpl.getResultList(QueryImpl.java:473)
    ... 56 more
Caused by: java.lang.RuntimeException: java.io.StreamCorruptedException: invalid stream header: F1505533
    at org.eclipse.persistence.sessions.serializers.JavaSerializer.deserialize(JavaSerializer.java:57)
    at org.eclipse.persistence.mappings.converters.SerializedObjectConverter.convertDataValueToObjectValue(SerializedObjectConverter.java:142)
    ... 79 more
Caused by: java.io.StreamCorruptedException: invalid stream header: F1505533
    at java.io.ObjectInputStream.readStreamHeader(Unknown Source)
    at java.io.ObjectInputStream.<init>(Unknown Source)
    at org.eclipse.persistence.internal.helper.CustomObjectInputStream.<init>(CustomObjectInputStream.java:37)
    at org.eclipse.persistence.sessions.serializers.JavaSerializer.deserialize(JavaSerializer.java:53)
    ... 80 more
tJeyhun
  • 195
  • 1
  • 9
  • Is the driver able to handle the byte[] when writing - is the problem only when reading? If so, just have your converter handle the character string returned from the driver. – Chris Jan 11 '17 at 16:48
  • 1
    Could you try with [Jaybird 3-beta-2](https://github.com/FirebirdSQL/jaybird/releases/tag/v3.0.0-beta-2), it will handle `char(16) character set octets` as type `binary` always, see [Character set OCTETS handled as JDBC (VAR)BINARY](https://www.firebirdsql.org/file/documentation/drivers_documentation/java/3.0.0-beta-2/release_notes.html#character-set-octets-handled-as-jdbc-varbinary). The `octetsAsBytes` in Jaybird 2.2 is unfortunately not applied consistently (although it should apply in the case of `ResultSetMetaData`, it doesn't work if for example `getObject` is used on the result set). – Mark Rotteveel Jan 11 '17 at 16:48
  • BTW: It might be helpful to include the actual error in your question. – Mark Rotteveel Jan 11 '17 at 16:53
  • I have tried with Jaybird 3-beta-2, same deal. I try to post error log. – tJeyhun Jan 13 '17 at 09:35
  • The invalid stream header seems to suggest it is trying to use Java serialization instead of converting using the `AttributeConverter` – Mark Rotteveel Jan 13 '17 at 14:21
  • Did you list the converter in your persistence context? – Mark Rotteveel Jan 13 '17 at 15:11

1 Answers1

3

I have made a simple project using eclipselink 2.6.4 to test this, and both with Jaybird 2.2.12 (setting octetsAsBytes=true is required) and Jaybird 3.0.0-beta-2 it works:

Code also on: https://gist.github.com/mrotteveel/273aa9e836880211820f54ff21164ec1

Todo Entity:

package com.example.eclipselink.entity;

import com.example.eclipselink.converter.UUIDAttributeConverter;

import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.UUID;

@Entity
public class Todo {

    @Id
    @Convert(converter = UUIDAttributeConverter.class)
    private UUID id;

    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }
}

UUIDAttributeConverter:

package com.example.eclipselink.converter;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.nio.ByteBuffer;
import java.util.UUID;

@Converter
public class UUIDAttributeConverter implements AttributeConverter<UUID, byte[]> {

    @Override
    public byte[] convertToDatabaseColumn(UUID uuid) {
        if (uuid == null) return null;
        byte[] buffer = new byte[16];
        ByteBuffer bb = ByteBuffer.wrap(buffer);
        bb.putLong(uuid.getMostSignificantBits());
        bb.putLong(uuid.getLeastSignificantBits());
        return buffer;
    }

    @Override
    public UUID convertToEntityAttribute(byte[] bytes) {
        if (bytes == null) return null;
        ByteBuffer bb = ByteBuffer.wrap(bytes);
        long high = bb.getLong();
        long low = bb.getLong();
        return new UUID(high, low);
    }
}

Main:

package com.example.eclipselink;

import com.example.eclipselink.entity.Todo;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import java.util.List;
import java.util.UUID;

public class Main {
    private static final String PERSISTENCE_UNIT_NAME = "todos";
    private static EntityManagerFactory factory;

    public static void main(String[] args) {
        factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
        EntityManager em = factory.createEntityManager();
        // read the existing entries and write to console
        Query q = em.createQuery("select t from Todo t");
        List<Todo> todoList = q.getResultList();
        for (Todo todo : todoList) {
            System.out.println(todo.getId());
        }
        System.out.println("Size: " + todoList.size());

        // create new todo
        em.getTransaction().begin();
        Todo todo = new Todo();
        todo.setId(UUID.randomUUID());
        em.persist(todo);
        em.getTransaction().commit();

        em.close();
    }
}

Persistence context:

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="todos" transaction-type="RESOURCE_LOCAL">
        <class>com.example.eclipselink.entity.Todo</class>
        <class>com.example.eclipselink.converter.UUIDAttributeConverter</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.firebirdsql.jdbc.FBDriver" />
            <property name="javax.persistence.jdbc.url"
                      value="jdbc:firebirdsql://localhost/d:/data/db/fb3/eclipselink.fdb?charSet=UTF-8&amp;octetsAsBytes=true" />
            <property name="javax.persistence.jdbc.user" value="sysdba" />
            <property name="javax.persistence.jdbc.password" value="masterkey" />
        </properties>

    </persistence-unit>
</persistence>

DDL:

CREATE TABLE TODO
(
  ID char(16) CHARACTER SET OCTETS NOT NULL,
  CONSTRAINT PK_TODO PRIMARY KEY (ID)
);

Output after a few runs:

[EL Info]: 2017-01-13 15:59:14.733--ServerSession(1139700454)--EclipseLink, version: Eclipse Persistence Services - 2.6.4.v20160829-44060b6
[EL Info]: connection: 2017-01-13 15:59:15.093--ServerSession(1139700454)--/file:/D:/Development/project/eclipselink/target/classes/_todos login successful
4c062d69-849e-4946-8e25-edfc5d7441be
dac10396-cfe2-4fb0-b048-65f954a82da5
8dab770e-ebd2-4ebc-a29b-4b8aae0b449a
9d376c67-fdc5-4e21-8013-f71cde5119aa
Size: 4
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • Just to be on the safe side, I also tested it using a domain like you mention in your question. – Mark Rotteveel Jan 13 '17 at 15:12
  • Mark, you are my hero! You are saving me for second time. I will not forget this. My code was ok. I was just I forget to annotate uuid field with @Converter annotation. In my other AttibuteConverters there are (autoApply = true). .... – tJeyhun Jan 13 '17 at 17:20