1

I have tried to implement a Maven project on using "PostgreSQL’s JSONB data type with Hibernate v5.1.0" and used a custom usertype called "MyJsonType" for "jsonb" data type. So I am not getting what else need to be configured related to "MyJsonType". I tried as per the below article.

BUT THE SAME CONCEPT WORKED WITH JPA (using EntityManager to persist the object) BUT HERE I USED HIBERNATE API

Please check the complete error log in the last code block.

I have not implemented MyJsonType.java, MyPostgreSQL94Dialect.java, package-info.java and these are already defined ones and got it from the git as per the below article.

And I used the below link to complete this sample project:
https://www.thoughts-on-java.org/persist-postgresqls-jsonb-data-type-hibernate/

I appreciate any help.

hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-5.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
        <property name="hibernate.connection.url">jdbc:postgresql://localhost:5433/sample_database</property>
        <property name="hibernate.connection.username">postgres</property>
        <property name="hibernate.connection.password">admin</property>
        <property name="hibernate.connection.pool_size">10</property>
        <property name="show_sql">true</property>
        <property name="hibernate.dialect">com.code.model.MyPostgreSQL94Dialect</property>
        <property name="hibernate.current_session_context_class">thread</property>

        <mapping class="com.code.model.Address" />

    </session-factory>
</hibernate-configuration>

Address.java

package com.code.model;
import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.Type;

@Entity
@Table(name = "address")
public class Address implements Serializable{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO) 
    @Column(name="emp_id", updatable = false, nullable = false)
    private Long empId; 

    @Column(name="emp_Details")
    @Type(type = "MyJsonType")
    private MyJson empDetails;

    public long getEmpId() {
        return empId;
    }

    public void setEmpId(long empId) {
        this.empId = empId;
    }

    public MyJson getEmpDetails() {
        return empDetails;
    }

    public void setEmpDetails(MyJson empDetails) {
        this.empDetails = empDetails;
    }
}

MyJson.java

package com.code.model;

import java.io.Serializable;

public class MyJson implements Serializable {
    private String details;

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
    }
}

MyJsonType.java

package com.code.model;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;

import com.fasterxml.jackson.databind.ObjectMapper;

public class MyJsonType implements UserType {

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

    @Override
    public Class<MyJson> returnedClass() {
        return MyJson.class;
    }

    @Override
    public Object nullSafeGet(final ResultSet rs, final String[] names, final SessionImplementor session,
                              final Object owner) throws HibernateException, SQLException {
        final String cellContent = rs.getString(names[0]);
        if (cellContent == null) {
            return null;
        }
        try {
            final ObjectMapper mapper = new ObjectMapper();
            return mapper.readValue(cellContent.getBytes("UTF-8"), returnedClass());
        } catch (final Exception ex) {
            throw new RuntimeException("Failed to convert String to Invoice: " + ex.getMessage(), ex);
        }
    }

    @Override
    public void nullSafeSet(final PreparedStatement ps, final Object value, final int idx,
                            final SessionImplementor session) throws HibernateException, SQLException {
        if (value == null) {
            ps.setNull(idx, Types.OTHER);
            return;
        }
        try {
            final ObjectMapper mapper = new ObjectMapper();
            final StringWriter w = new StringWriter();
            mapper.writeValue(w, value);
            w.flush();
            ps.setObject(idx, w.toString(), Types.OTHER);
        } catch (final Exception ex) {
            throw new RuntimeException("Failed to convert Invoice to String: " + ex.getMessage(), ex);
        }
    }

    @Override
    public Object deepCopy(final Object value) throws HibernateException {
        try {
            // use serialization to create a deep copy
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(value);
            oos.flush();
            oos.close();
            bos.close();

            ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray());
            return new ObjectInputStream(bais).readObject();
        } catch (ClassNotFoundException | IOException ex) {
            throw new HibernateException(ex);
        }
    }

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

    @Override
    public Serializable disassemble(final Object value) throws HibernateException {
        return (Serializable) this.deepCopy(value);
    }

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

    @Override
    public Object replace(final Object original, final Object target, final Object owner) throws HibernateException {
        return this.deepCopy(original);
    }

    @Override
    public boolean equals(final Object obj1, final Object obj2) throws HibernateException {
        if (obj1 == null) {
            return obj2 == null;
        }
        return obj1.equals(obj2);
    }

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

MyPostgreSQL94Dialect.java

package com.code.model;

import java.sql.Types;

import org.hibernate.dialect.PostgreSQL94Dialect;

public class MyPostgreSQL94Dialect extends PostgreSQL94Dialect {
    public MyPostgreSQL94Dialect() {
        this.registerColumnType(Types.JAVA_OBJECT, "jsonb");
    }
}

package-info.java

@org.hibernate.annotations.TypeDef(name = "MyJsonType", typeClass = com.comcast.model.MyJsonType.class)

package com.comcast.model;

CreateData.java (Main class)

package com.code;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.code.model.Address;
import com.code.model.MyJson;

public class CreateData {
    static SessionFactory sessFact;
    static Session session;
    static Transaction tr;

    public static void main(String[] args) throws Exception {
        init();
        CreateData cd = new CreateData();
        cd.insert();
    }

    public static void init() {
        sessFact = HibernateUtil.getSessionFactory();
        session = sessFact.getCurrentSession();
        tr = session.beginTransaction();
    }

    public void insert() {
        String json = "{" + "\"city\" : \"Lawrence\"," + "\"state\" : \"432 Essentric exmark street\"," + "\"zipcode\" : \"11111\"}";       

        MyJson j = new MyJson();
        j.setDetails(json);

        Address add = new Address();
        add.setEmpDetails(j);

        session.save(add);
        tr.commit();
        System.out.println("Successfully inserted");
        sessFact.close();
    }   
}

error:

Mar 01, 2018 7:30:33 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.1.0.Final}
Mar 01, 2018 7:30:33 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
Mar 01, 2018 7:30:33 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Mar 01, 2018 7:30:33 PM org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity
WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-configuration. Use namespace http://www.hibernate.org/dtd/hibernate-configuration instead.  Support for obsolete DTD/XSD namespaces may be removed at any time.
Mar 01, 2018 7:30:33 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Mar 01, 2018 7:30:33 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Mar 01, 2018 7:30:33 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [org.postgresql.Driver] at URL [jdbc:postgresql://localhost:5433/sample_database]
Mar 01, 2018 7:30:33 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=postgres, password=****}
Mar 01, 2018 7:30:33 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Mar 01, 2018 7:30:33 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 10 (min=1)
Mar 01, 2018 7:30:34 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: com.comcast.model.MyPostgreSQL94Dialect
Mar 01, 2018 7:30:34 PM org.hibernate.engine.jdbc.env.internal.LobCreatorBuilderImpl useContextualLobCreation
INFO: HHH000424: Disabling contextual LOB creation as createClob() method threw error : java.lang.reflect.InvocationTargetException
Mar 01, 2018 7:30:34 PM org.hibernate.type.BasicTypeRegistry register
INFO: HHH000270: Type registration [java.util.UUID] overrides previous : org.hibernate.type.UUIDBinaryType@6a2f6f80
Enitial SessionFactory creation failedorg.hibernate.boot.registry.classloading.spi.ClassLoadingException: Unable to load class [MyJsonType]
Exception in thread "main" java.lang.ExceptionInInitializerError
    at com.comcast.HibernateUtil.<clinit>(HibernateUtil.java:30)
    at com.comcast.CreateData.init(CreateData.java:29)
    at com.comcast.CreateData.main(CreateData.java:21)
Caused by: org.hibernate.boot.registry.classloading.spi.ClassLoadingException: Unable to load class [MyJsonType]
    at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.classForName(ClassLoaderServiceImpl.java:229)
    at org.hibernate.boot.internal.ClassLoaderAccessImpl.classForName(ClassLoaderAccessImpl.java:62)
    at org.hibernate.cfg.annotations.SimpleValueBinder.fillSimpleValue(SimpleValueBinder.java:521)
    at org.hibernate.cfg.SetSimpleValueTypeSecondPass.doSecondPass(SetSimpleValueTypeSecondPass.java:25)
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1621)
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1579)
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:278)
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:83)
    at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:418)
    at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:87)
    at com.comcast.HibernateUtil.<clinit>(HibernateUtil.java:24)
    ... 2 more
Caused by: java.lang.ClassNotFoundException: Could not load requested class : MyJsonType
    at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl$AggregatedClassLoader.findClass(ClassLoaderServiceImpl.java:217)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Unknown Source)
    at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.classForName(ClassLoaderServiceImpl.java:226)
    ... 12 more
Sairam Vinjamuri
  • 35
  • 1
  • 1
  • 6
  • What research have you done. How are refs like https://stackoverflow.com/q/43578991/1531971 related, if at all? –  Mar 01 '18 at 15:58
  • Code dumps are generally unhelpful. Please review how to create a [minimal, complete, and verifiable example](https://stackoverflow.com/help/mcve) – user3483203 Mar 01 '18 at 16:08
  • @jdv, I went through the all other stuffs including your ref earlier but since MyJsonType is not an entity so we can't map it. I mean this is just a custom class for jsonb datatype of postgresql. – Sairam Vinjamuri Mar 01 '18 at 16:18
  • You need to _show_ what research you have done, not just dump the problem in our laps. Details like what a typical solution will not work needs to be in the text of the question. You can [edit] a question for clarity. –  Mar 01 '18 at 16:21

2 Answers2

2

I had the same strange error for my own UserType. More interestingly, the error ocurred only, when executing my tests on the command line using Gradle, but not when executing my tests in the IDE (IntelliJ). Because auf that, I think it is some kind of bug.

I worked around this, by moving the @TypeDef annotations from package-info.java file directly to the entity files:

@Entity
@Table(name = "address")
@TypeDef(name = "MyJsonType", typeClass = com.comcast.model.MyJsonType.class)
public class Address implements Serializable {
 //...
}
TheMagican
  • 41
  • 3
0

It seems that is missing the MyJsonType mapping. try to check your problem with this solution: Class Mapping

Giacky
  • 181
  • 10
  • MyJsonType is a custom type for "jsonb" datatype in postgresql and it's not an entity / bean to be part of mapping. – Sairam Vinjamuri Mar 01 '18 at 16:13
  • But the same scenario I mean this custom usertype concept worked with JPA (persisted using EntityManager) but in this scenario I have used Hibernate API. – Sairam Vinjamuri Mar 01 '18 at 16:23
  • This is definitely a mapping problem, though. Based on the article you link, above, you're definitely treating this type as an entity. –  Mar 01 '18 at 16:34
  • @Giacky, I tried mapping MyJsonType in hibernate.cfg.xml file but did not work. I knew it won't work because we can map only entities. – Sairam Vinjamuri Mar 01 '18 at 18:16