Now I learn about nested jsons and geojson and how to save them in PostgreSQL as JsonB directly in database and before starting.
Inspiration source: https://thorben-janssen.com/persist-postgresqls-jsonb-data-type-hibernate/
Error I get when running the app:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.boot.registry.classloading.spi.ClassLoadingException: Unable to load class [FeatureType] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1154) ~[spring-context-5.3.22.jar:5.3.22] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:908) ~[spring-context-5.3.22.jar:5.3.22] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.22.jar:5.3.22] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.2.jar:2.7.2] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-2.7.2.jar:2.7.2] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.2.jar:2.7.2] at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-2.7.2.jar:2.7.2] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-2.7.2.jar:2.7.2] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-2.7.2.jar:2.7.2] at com.example.GeojsonTest.GeojsonTestApplication.main(GeojsonTestApplication.java:10) ~[classes/:na] Caused by: org.hibernate.boot.registry.classloading.spi.ClassLoadingException: Unable to load class [FeatureType] at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.classForName(ClassLoaderServiceImpl.java:133) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.boot.internal.ClassLoaderAccessImpl.classForName(ClassLoaderAccessImpl.java:67) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.cfg.annotations.SimpleValueBinder.fillSimpleValue(SimpleValueBinder.java:536) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.cfg.SetSimpleValueTypeSecondPass.doSecondPass(SetSimpleValueTypeSecondPass.java:25) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1653) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1621) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:295) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1460) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1494) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58) ~[spring-orm-5.3.22.jar:5.3.22] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.3.22.jar:5.3.22] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-5.3.22.jar:5.3.22] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-5.3.22.jar:5.3.22] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863) ~[spring-beans-5.3.22.jar:5.3.22] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800) ~[spring-beans-5.3.22.jar:5.3.22] ... 16 common frames omitted Caused by: java.lang.ClassNotFoundException: Could not load requested class : FeatureType at org.hibernate.boot.registry.classloading.internal.AggregatedClassLoader.findClass(AggregatedClassLoader.java:210) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587) ~[na:na] at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[na:na] at java.base/java.lang.Class.forName0(Native Method) ~[na:na] at java.base/java.lang.Class.forName(Class.java:467) ~[na:na] at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.classForName(ClassLoaderServiceImpl.java:130) ~[hibernate-core-5.6.10.Final.jar:5.6.10.Final] ... 31 common frames omitted
Here is the Main Entity:
@Getter @Setter @ToString @RequiredArgsConstructor @Entity public class Geojson { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private int id; @Column(name = "type") private String type; @Column(name = "feature") @Type(type = "FeatureType") private Feature features;
Here is the geojson(nested json) I want to save as Jsonb in a column in database:
@Getter @Setter @ToString @RequiredArgsConstructor public class Feature implements Serializable { private String type; private Properties properties; private Geometry geometry; }
Here is the implementation of Usertype for my json:
package com.example.GeojsonTest.Entity; import com.fasterxml.jackson.databind.ObjectMapper; import org.hibernate.HibernateException; import org.hibernate.annotations.TypeDef; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.usertype.UserType; import java.io.*; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; @TypeDef(name = "FeatureType", typeClass = FeatureType.class) public class FeatureType implements UserType { @Override public int[] sqlTypes() { return new int[]{Types.JAVA_OBJECT}; } @Override public Class<Feature> returnedClass() { return Feature.class; } @Override public Object nullSafeGet(final ResultSet resultSet, final String[] strings, final SharedSessionContractImplementor sharedSessionContractImplementor, final Object o) throws HibernateException, SQLException { final String cellContent = resultSet.getString(strings[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 preparedStatement, final Object o, final int idx, final SharedSessionContractImplementor sharedSessionContractImplementor) throws HibernateException, SQLException { if (o == null) { preparedStatement.setNull(idx, Types.OTHER); return; } try { final ObjectMapper mapper = new ObjectMapper(); final StringWriter w = new StringWriter(); mapper.writeValue(w, o); w.flush(); preparedStatement.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(); } }
here is the registration of hibernate dialect:
public class MyPostgreSQL94Dialect extends PostgreSQL94Dialect { public MyPostgreSQL94Dialect() { this.registerColumnType(Types.JAVA_OBJECT, "jsonb"); } }
App.properties:
#postgres database config #spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect #spring.jpa.hibernate.ddl-auto=create spring.jpa.generate-ddl=true spring.jpa.hibernate.show-sql=true spring.datasource.url=jdbc:postgresql://localhost:5432/geojson_copy spring.datasource.username=postgres spring.datasource.password=******** spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL81Dialect
and finally, here is the POM.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>GeojsonTest</artifactId> <version>0.0.1-SNAPSHOT</version> <name>GeojsonTest</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20220320</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api --> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.javassist/javassist --> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.29.0-GA</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
I don’t understand why i get the error about the FeatureType, if anyone can help me. Thank you!
Advertisement
Answer
I found an way easier solution working for hibernate 5 or higher:
Found a different approach that worked as expected:
<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-55</artifactId> <version>2.18.0</version> </dependency>
Base Entity:
@TypeDefs({ @TypeDef(name = "json", typeClass = JsonStringType.class), @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) }) public class BaseEntity { //class members }
Main entity:
@Getter @Setter @ToString @RequiredArgsConstructor @Entity public class Geojson extends BaseEntity{ @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private int id; @Column(name = "type") private String type; @Type(type = "json") @Column(name = "feature", columnDefinition = "jsonb") private List<Feature> features;
Geojson(nested json) I want to save as Jsonb in a column in database:
@Getter @Setter @ToString @RequiredArgsConstructor public class Feature implements Serializable { private String type; private Properties properties; private Geometry geometry; }
I’m not sure yet why I was not able to use @TypeDefs directly on “Geojson” class, and i needed to create a BaseEntity, but it works.
PS: If you tried storing some other type of data before, please make sure that you drop the table and create a new one before running the app, since the table was created with a different variable type for that column.