From a4a5be44cf8570586117697ed03b8132acfaf8f4 Mon Sep 17 00:00:00 2001 From: Jan-Willem Gmelig Meyling Date: Wed, 10 Jul 2019 15:42:06 +0200 Subject: [PATCH] Use newer JSON-P & JSON-B standards as alternative to Jackson, fixes #29 --- hibernate-types-52/pom.xml | 34 ++++ .../hibernate/type/json/JsonBinaryType.java | 5 +- .../type/json/JsonNodeBinaryType.java | 5 +- .../type/json/JsonNodeStringType.java | 5 +- .../hibernate/type/json/JsonStringType.java | 5 +- .../type/json/internal/JacksonUtil.java | 57 ++++++ .../hibernate/type/jsonp/JsonBinaryType.java | 77 ++++++++ .../type/jsonp/JsonNodeBinaryType.java | 54 ++++++ .../type/jsonp/JsonNodeStringType.java | 55 ++++++ .../hibernate/type/jsonp/JsonStringType.java | 81 ++++++++ .../AbstractJsonSqlTypeDescriptor.java | 60 ++++++ .../internal/JsonBinarySqlTypeDescriptor.java | 35 ++++ .../internal/JsonNodeTypeDescriptor.java | 106 ++++++++++ .../internal/JsonStringSqlTypeDescriptor.java | 50 +++++ .../jsonp/internal/JsonTypeDescriptor.java | 130 +++++++++++++ .../type/jsonp/internal/JsonbUtil.java | 85 ++++++++ .../hibernate/type/util/Configuration.java | 56 +----- .../type/util/JsonbJsonSerializer.java | 27 +++ .../hibernate/type/util/JsonbSupplier.java | 19 ++ .../hibernate/type/util/JsonbWrapper.java | 76 ++++++++ .../jsonp/EhcacheMySQLJsonBinaryTypeTest.java | 120 ++++++++++++ .../type/jsonp/MySQLGenericJsonTypeTest.java | 123 ++++++++++++ .../type/jsonp/MySQLJsonTypeSetTest.java | 181 ++++++++++++++++++ .../type/jsonp/MySQLJsonTypeTest.java | 137 +++++++++++++ .../PostgreSQLGenericJsonBinaryTypeTest.java | 130 +++++++++++++ ...PostgreSQLJsonBinaryTypeLazyGroupTest.java | 130 +++++++++++++ .../PostgreSQLJsonBinaryTypeSetTest.java | 180 +++++++++++++++++ .../jsonp/PostgreSQLJsonBinaryTypeTest.java | 172 +++++++++++++++++ .../PostgreSQLJsonNodeBinaryTypeTest.java | 114 +++++++++++ .../PostgreSQLJsonStringPropertyTest.java | 100 ++++++++++ .../jsonp/PostgreSQLJsonStringTypeTest.java | 131 +++++++++++++ ...greSQLJsonBinaryTypeConfigurationTest.java | 114 +++++++++++ .../type/jsonp/internal/JsonbUtilTest.java | 168 ++++++++++++++++ .../hibernate/type/model/BaseEntity.java | 4 + 34 files changed, 2764 insertions(+), 62 deletions(-) create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonBinaryType.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonNodeBinaryType.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonNodeStringType.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonStringType.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/AbstractJsonSqlTypeDescriptor.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonBinarySqlTypeDescriptor.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonNodeTypeDescriptor.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonStringSqlTypeDescriptor.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonTypeDescriptor.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonbUtil.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbJsonSerializer.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbSupplier.java create mode 100644 hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbWrapper.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/EhcacheMySQLJsonBinaryTypeTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLGenericJsonTypeTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLJsonTypeSetTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLJsonTypeTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLGenericJsonBinaryTypeTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeLazyGroupTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeSetTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonNodeBinaryTypeTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonStringPropertyTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonStringTypeTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/configuration/PostgreSQLJsonBinaryTypeConfigurationTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonbUtilTest.java diff --git a/hibernate-types-52/pom.xml b/hibernate-types-52/pom.xml index 7e6286c95..2772e89b5 100644 --- a/hibernate-types-52/pom.xml +++ b/hibernate-types-52/pom.xml @@ -40,6 +40,36 @@ true + + javax.json.bind + javax.json.bind-api + ${json.bind-api.version} + provided + true + + + + javax.json + javax.json-api + ${json-api.version} + provided + true + + + + org.eclipse + yasson + ${yasson.version} + test + + + + org.glassfish + javax.json + ${glassfish.json.version} + test + + org.hibernate hibernate-ehcache @@ -69,6 +99,10 @@ 8.0.13 2.9.9 + 1.0 + 1.1.4 + 1.0.1 + 1.1.2 8 ${env.JAVA_HOME_8} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBinaryType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBinaryType.java index bbf661f04..bd5f6cd06 100644 --- a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBinaryType.java +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonBinaryType.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.vladmihalcea.hibernate.type.AbstractHibernateType; +import com.vladmihalcea.hibernate.type.json.internal.JacksonUtil; import com.vladmihalcea.hibernate.type.json.internal.JsonBinarySqlTypeDescriptor; import com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor; import com.vladmihalcea.hibernate.type.util.Configuration; @@ -25,14 +26,14 @@ public class JsonBinaryType extends AbstractHibernateType implements Dyn public JsonBinaryType() { super( JsonBinarySqlTypeDescriptor.INSTANCE, - new JsonTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper()) + new JsonTypeDescriptor(JacksonUtil.getObjectMapperWrapper(Configuration.INSTANCE)) ); } public JsonBinaryType(Configuration configuration) { super( JsonBinarySqlTypeDescriptor.INSTANCE, - new JsonTypeDescriptor(configuration.getObjectMapperWrapper()), + new JsonTypeDescriptor(JacksonUtil.getObjectMapperWrapper(configuration)), configuration ); } diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeBinaryType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeBinaryType.java index 0f5ec8a9e..84086e32a 100644 --- a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeBinaryType.java +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeBinaryType.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.vladmihalcea.hibernate.type.AbstractHibernateType; +import com.vladmihalcea.hibernate.type.json.internal.JacksonUtil; import com.vladmihalcea.hibernate.type.json.internal.JsonBinarySqlTypeDescriptor; import com.vladmihalcea.hibernate.type.json.internal.JsonNodeTypeDescriptor; import com.vladmihalcea.hibernate.type.util.Configuration; @@ -23,14 +24,14 @@ public class JsonNodeBinaryType extends AbstractHibernateType { public JsonNodeBinaryType() { super( JsonBinarySqlTypeDescriptor.INSTANCE, - new JsonNodeTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper()) + new JsonNodeTypeDescriptor(JacksonUtil.getObjectMapperWrapper(Configuration.INSTANCE)) ); } public JsonNodeBinaryType(Configuration configuration) { super( JsonBinarySqlTypeDescriptor.INSTANCE, - new JsonNodeTypeDescriptor(configuration.getObjectMapperWrapper()), + new JsonNodeTypeDescriptor(JacksonUtil.getObjectMapperWrapper(configuration)), configuration ); } diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeStringType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeStringType.java index c0ed543ce..78c196c0b 100644 --- a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeStringType.java +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonNodeStringType.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.vladmihalcea.hibernate.type.AbstractHibernateType; +import com.vladmihalcea.hibernate.type.json.internal.JacksonUtil; import com.vladmihalcea.hibernate.type.json.internal.JsonNodeTypeDescriptor; import com.vladmihalcea.hibernate.type.json.internal.JsonStringSqlTypeDescriptor; import com.vladmihalcea.hibernate.type.util.Configuration; @@ -22,14 +23,14 @@ public class JsonNodeStringType extends AbstractHibernateType { public JsonNodeStringType() { super( JsonStringSqlTypeDescriptor.INSTANCE, - new JsonNodeTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper()) + new JsonNodeTypeDescriptor(JacksonUtil.getObjectMapperWrapper(Configuration.INSTANCE)) ); } public JsonNodeStringType(Configuration configuration) { super( JsonStringSqlTypeDescriptor.INSTANCE, - new JsonNodeTypeDescriptor(configuration.getObjectMapperWrapper()), + new JsonNodeTypeDescriptor(JacksonUtil.getObjectMapperWrapper(configuration)), configuration ); } diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonStringType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonStringType.java index c7db79c6e..3065fdef9 100644 --- a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonStringType.java +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/JsonStringType.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.vladmihalcea.hibernate.type.AbstractHibernateType; +import com.vladmihalcea.hibernate.type.json.internal.JacksonUtil; import com.vladmihalcea.hibernate.type.json.internal.JsonStringSqlTypeDescriptor; import com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor; import com.vladmihalcea.hibernate.type.util.Configuration; @@ -25,14 +26,14 @@ public class JsonStringType extends AbstractHibernateType implements Dyn public JsonStringType() { super( JsonStringSqlTypeDescriptor.INSTANCE, - new JsonTypeDescriptor(Configuration.INSTANCE.getObjectMapperWrapper()) + new JsonTypeDescriptor(JacksonUtil.getObjectMapperWrapper(Configuration.INSTANCE)) ); } public JsonStringType(Configuration configuration) { super( JsonStringSqlTypeDescriptor.INSTANCE, - new JsonTypeDescriptor(configuration.getObjectMapperWrapper()), + new JsonTypeDescriptor(JacksonUtil.getObjectMapperWrapper(configuration)), configuration ); } diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JacksonUtil.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JacksonUtil.java index 05770cc49..e430d02a1 100644 --- a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JacksonUtil.java +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JacksonUtil.java @@ -1,9 +1,15 @@ package com.vladmihalcea.hibernate.type.json.internal; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vladmihalcea.hibernate.type.util.Configuration; +import com.vladmihalcea.hibernate.type.util.JsonSerializer; +import com.vladmihalcea.hibernate.type.util.JsonSerializerSupplier; +import com.vladmihalcea.hibernate.type.util.ObjectMapperSupplier; import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper; import java.lang.reflect.Type; +import java.util.function.Supplier; /** * @author Vlad Mihalcea @@ -29,4 +35,55 @@ public static JsonNode toJsonNode(String value) { public static T clone(T value) { return ObjectMapperWrapper.INSTANCE.clone(value); } + + /** + * Get {@link ObjectMapperWrapper} reference + * + * @return {@link ObjectMapperWrapper} reference + */ + public static ObjectMapperWrapper getObjectMapperWrapper(Configuration configuration) { + Object objectMapperPropertyInstance = configuration.instantiateClass(Configuration.PropertyKey.JACKSON_OBJECT_MAPPER); + + ObjectMapperWrapper objectMapperWrapper = new ObjectMapperWrapper(); + + if (objectMapperPropertyInstance != null) { + if(objectMapperPropertyInstance instanceof ObjectMapperSupplier) { + ObjectMapper objectMapper = ((ObjectMapperSupplier) objectMapperPropertyInstance).get(); + if(objectMapper != null) { + objectMapperWrapper = new ObjectMapperWrapper(objectMapper); + } + } + else if (objectMapperPropertyInstance instanceof Supplier) { + Supplier objectMapperSupplier = (Supplier) objectMapperPropertyInstance; + objectMapperWrapper = new ObjectMapperWrapper(objectMapperSupplier.get()); + } + else if (objectMapperPropertyInstance instanceof ObjectMapper) { + ObjectMapper objectMapper = (ObjectMapper) objectMapperPropertyInstance; + objectMapperWrapper = new ObjectMapperWrapper(objectMapper); + } + } + + Object jsonSerializerPropertyInstance = configuration.instantiateClass(Configuration.PropertyKey.JSON_SERIALIZER); + + if (jsonSerializerPropertyInstance != null) { + JsonSerializer jsonSerializer = null; + + if(jsonSerializerPropertyInstance instanceof JsonSerializerSupplier) { + jsonSerializer = ((JsonSerializerSupplier) jsonSerializerPropertyInstance).get(); + } + else if (jsonSerializerPropertyInstance instanceof Supplier) { + Supplier jsonSerializerSupplier = (Supplier) jsonSerializerPropertyInstance; + jsonSerializer = jsonSerializerSupplier.get(); + } + else if (jsonSerializerPropertyInstance instanceof JsonSerializer) { + jsonSerializer = (JsonSerializer) jsonSerializerPropertyInstance; + } + + if (jsonSerializer != null) { + objectMapperWrapper.setJsonSerializer(jsonSerializer); + } + } + + return objectMapperWrapper; + } } diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonBinaryType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonBinaryType.java new file mode 100644 index 000000000..a7a2b4710 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonBinaryType.java @@ -0,0 +1,77 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.AbstractHibernateType; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonBinarySqlTypeDescriptor; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonTypeDescriptor; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonbUtil; +import com.vladmihalcea.hibernate.type.util.Configuration; +import com.vladmihalcea.hibernate.type.util.JsonbWrapper; +import org.hibernate.usertype.DynamicParameterizedType; + +import javax.json.bind.Jsonb; +import java.lang.reflect.Type; +import java.util.Properties; + +/** + * Maps any given Java object on a JSON column type that is managed via {@link java.sql.PreparedStatement#setObject(int, Object)} at JDBC Driver level. For instance, if you are using PostgreSQL, you should be using this {@link JsonBinaryType} to map both {@code jsonb} and {@code json} column types. + *

+ * + * @author Jan-Willem Gmelig Meyling + */ +public class JsonBinaryType extends AbstractHibernateType implements DynamicParameterizedType { + + public static final JsonBinaryType INSTANCE = new JsonBinaryType(); + + public JsonBinaryType() { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(JsonbUtil.getObjectMapperWrapper(Configuration.INSTANCE)) + ); + } + + public JsonBinaryType(Configuration configuration) { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(JsonbUtil.getObjectMapperWrapper(configuration)), + configuration + ); + } + + public JsonBinaryType(Jsonb objectMapper) { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(new JsonbWrapper(objectMapper)) + ); + } + + public JsonBinaryType(JsonbWrapper jsonbWrapper) { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(jsonbWrapper) + ); + } + + public JsonBinaryType(Jsonb objectMapper, Type javaType) { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(new JsonbWrapper(objectMapper), javaType) + ); + } + + public JsonBinaryType(JsonbWrapper jsonbWrapper, Type javaType) { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(jsonbWrapper, javaType) + ); + } + + public String getName() { + return "jsonb-p"; + } + + @Override + public void setParameterValues(Properties parameters) { + ((JsonTypeDescriptor) getJavaTypeDescriptor()).setParameterValues(parameters); + } + +} \ No newline at end of file diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonNodeBinaryType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonNodeBinaryType.java new file mode 100644 index 000000000..0985614ee --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonNodeBinaryType.java @@ -0,0 +1,54 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.AbstractHibernateType; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonBinarySqlTypeDescriptor; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonNodeTypeDescriptor; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonbUtil; +import com.vladmihalcea.hibernate.type.util.Configuration; +import com.vladmihalcea.hibernate.type.util.JsonbWrapper; + +import javax.json.JsonValue; +import javax.json.bind.Jsonb; + +/** + * Maps a Json-P {@link JsonValue} object on a JSON column type that is managed via {@link java.sql.PreparedStatement#setObject(int, Object)} at JDBC Driver level. For instance, if you are using PostgreSQL, you should be using {@link JsonNodeBinaryType} to map both {@code jsonb} and {@code json} column types to a Jackson {@link JsonNode} object. + * + * @author Jan-Willem Gmelig Meyling + */ +public class JsonNodeBinaryType extends AbstractHibernateType { + + public static final JsonNodeBinaryType INSTANCE = new JsonNodeBinaryType(); + + public JsonNodeBinaryType() { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonNodeTypeDescriptor(JsonbUtil.getObjectMapperWrapper(Configuration.INSTANCE)) + ); + } + + public JsonNodeBinaryType(Configuration configuration) { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonNodeTypeDescriptor(JsonbUtil.getObjectMapperWrapper(configuration)), + configuration + ); + } + + public JsonNodeBinaryType(Jsonb objectMapper) { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonNodeTypeDescriptor(new JsonbWrapper(objectMapper)) + ); + } + + public JsonNodeBinaryType(JsonbWrapper jsonbWrapper) { + super( + JsonBinarySqlTypeDescriptor.INSTANCE, + new JsonNodeTypeDescriptor(jsonbWrapper) + ); + } + + public String getName() { + return "jsonb-p-value"; + } +} \ No newline at end of file diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonNodeStringType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonNodeStringType.java new file mode 100644 index 000000000..475b14f60 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonNodeStringType.java @@ -0,0 +1,55 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.AbstractHibernateType; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonNodeTypeDescriptor; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonStringSqlTypeDescriptor; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonbUtil; +import com.vladmihalcea.hibernate.type.util.Configuration; +import com.vladmihalcea.hibernate.type.util.JsonbWrapper; + +import javax.json.JsonValue; +import javax.json.bind.Jsonb; + +/** + * Maps a Json-P {@link JsonValue} object on a JSON column type that is managed via {@link java.sql.PreparedStatement#setString(int, String)} at JDBC Driver level. For instance, if you are using MySQL, you should be using {@link JsonNodeStringType} to map the {@code json} column type to a Jackson {@link JsonNode} object. + * + * @author Jan-Willem Gmelig Meyling + */ +public class JsonNodeStringType extends AbstractHibernateType { + + public static final JsonNodeStringType INSTANCE = new JsonNodeStringType(); + + public JsonNodeStringType() { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonNodeTypeDescriptor(JsonbUtil.getObjectMapperWrapper(Configuration.INSTANCE)) + ); + } + + public JsonNodeStringType(Configuration configuration) { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonNodeTypeDescriptor(JsonbUtil.getObjectMapperWrapper(configuration)), + configuration + ); + } + + public JsonNodeStringType(Jsonb objectMapper) { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonNodeTypeDescriptor(new JsonbWrapper(objectMapper)) + ); + } + + public JsonNodeStringType(JsonbWrapper jsonbWrapper) { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonNodeTypeDescriptor(jsonbWrapper) + ); + } + + @Override + public String getName() { + return "jsonb-p-value"; + } +} \ No newline at end of file diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonStringType.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonStringType.java new file mode 100644 index 000000000..2e737e913 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/JsonStringType.java @@ -0,0 +1,81 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.AbstractHibernateType; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonStringSqlTypeDescriptor; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonTypeDescriptor; +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonbUtil; +import com.vladmihalcea.hibernate.type.util.Configuration; +import com.vladmihalcea.hibernate.type.util.JsonbWrapper; +import org.hibernate.usertype.DynamicParameterizedType; + +import javax.json.bind.Jsonb; +import java.lang.reflect.Type; +import java.util.Properties; + +/** + * Maps any given Java object on a JSON column type that is managed via {@link java.sql.PreparedStatement#setString(int, String)} at JDBC Driver level. For instance, if you are using MySQL, you should be using this {@link JsonStringType} to map the {@code json} column type. + * + * @author Jan-Willem Gmelig Meyling + */ +public class JsonStringType extends AbstractHibernateType implements DynamicParameterizedType { + + public static final JsonStringType INSTANCE = new JsonStringType(); + + public JsonStringType() { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(JsonbUtil.getObjectMapperWrapper(Configuration.INSTANCE)) + ); + } + + public JsonStringType(Configuration configuration) { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(JsonbUtil.getObjectMapperWrapper(configuration)), + configuration + ); + } + + public JsonStringType(Jsonb objectMapper) { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(new JsonbWrapper(objectMapper)) + ); + } + + public JsonStringType(JsonbWrapper jsonbWrapper) { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(jsonbWrapper) + ); + } + + public JsonStringType(Jsonb objectMapper, Type javaType) { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(new JsonbWrapper(objectMapper), javaType) + ); + } + + public JsonStringType(JsonbWrapper jsonbWrapper, Type javaType) { + super( + JsonStringSqlTypeDescriptor.INSTANCE, + new JsonTypeDescriptor(jsonbWrapper, javaType) + ); + } + + @Override + public String getName() { + return "json-p"; + } + + @Override + protected boolean registerUnderJavaType() { + return true; + } + + @Override + public void setParameterValues(Properties parameters) { + ((JsonTypeDescriptor) getJavaTypeDescriptor()).setParameterValues(parameters); + } +} \ No newline at end of file diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/AbstractJsonSqlTypeDescriptor.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/AbstractJsonSqlTypeDescriptor.java new file mode 100644 index 000000000..b7e08891a --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/AbstractJsonSqlTypeDescriptor.java @@ -0,0 +1,60 @@ +package com.vladmihalcea.hibernate.type.jsonp.internal; + +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.BasicExtractor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +/** + * @author Jan-Willem Gmelig Meyling + */ +public abstract class AbstractJsonSqlTypeDescriptor implements SqlTypeDescriptor { + + @Override + public int getSqlType() { + return Types.OTHER; + } + + @Override + public boolean canBeRemapped() { + return true; + } + + @Override + public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor(javaTypeDescriptor, this) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap(extractJson(rs, name), options); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap(extractJson(statement, index), options); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap(extractJson(statement, name), options); + } + }; + } + + protected Object extractJson(ResultSet rs, String name) throws SQLException { + return rs.getObject(name); + } + + protected Object extractJson(CallableStatement statement, int index) throws SQLException { + return statement.getObject(index); + } + + protected Object extractJson(CallableStatement statement, String name) throws SQLException { + return statement.getObject(name); + } +} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonBinarySqlTypeDescriptor.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonBinarySqlTypeDescriptor.java new file mode 100644 index 000000000..2c427ef7b --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonBinarySqlTypeDescriptor.java @@ -0,0 +1,35 @@ +package com.vladmihalcea.hibernate.type.jsonp.internal; + +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.BasicBinder; + +import javax.json.JsonValue; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * @author Jan-Willem Gmelig Meyling + */ +public class JsonBinarySqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor { + + public static final JsonBinarySqlTypeDescriptor INSTANCE = new JsonBinarySqlTypeDescriptor(); + + @Override + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder(javaTypeDescriptor, this) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + st.setObject(index, javaTypeDescriptor.unwrap(value, JsonValue.class, options), getSqlType()); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setObject(name, javaTypeDescriptor.unwrap(value, JsonValue.class, options), getSqlType()); + } + }; + } +} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonNodeTypeDescriptor.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonNodeTypeDescriptor.java new file mode 100644 index 000000000..b8ad805d7 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonNodeTypeDescriptor.java @@ -0,0 +1,106 @@ +package com.vladmihalcea.hibernate.type.jsonp.internal; + +import com.vladmihalcea.hibernate.type.util.JsonbWrapper; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.AbstractTypeDescriptor; +import org.hibernate.type.descriptor.java.MutableMutabilityPlan; + +import javax.json.JsonValue; +import java.io.Serializable; + +/** + * @author Jan-Willem Gmelig Meyling + */ +public class JsonNodeTypeDescriptor + extends AbstractTypeDescriptor { + + public static final JsonNodeTypeDescriptor INSTANCE = new JsonNodeTypeDescriptor(); + + private JsonbWrapper jsonbWrapper; + + public JsonNodeTypeDescriptor() { + super(JsonValue.class, new MutableMutabilityPlan() { + @Override + public Serializable disassemble(JsonValue value) { + return JsonbUtil.toString(value); + } + + @Override + public JsonValue assemble(Serializable cached) { + return JsonbUtil.toJsonNode((String) cached); + } + + @Override + protected JsonValue deepCopyNotNull(JsonValue value) { + return JsonbWrapper.INSTANCE.clone(value); + } + }); + this.jsonbWrapper = JsonbWrapper.INSTANCE; + } + + public JsonNodeTypeDescriptor(final JsonbWrapper jsonbWrapper) { + super(JsonValue.class, new MutableMutabilityPlan() { + @Override + public Serializable disassemble(JsonValue value) { + return JsonbUtil.toString(value); + } + + @Override + public JsonValue assemble(Serializable cached) { + return JsonbUtil.toJsonNode((String) cached); + } + + @Override + protected JsonValue deepCopyNotNull(JsonValue value) { + return jsonbWrapper.clone(value); + } + }); + this.jsonbWrapper = jsonbWrapper; + } + + @Override + public boolean areEqual(JsonValue one, JsonValue another) { + if (one == another) { + return true; + } + if (one == null || another == null) { + return false; + } + return jsonbWrapper.toJsonNode(jsonbWrapper.toString(one)).equals( + jsonbWrapper.toJsonNode(jsonbWrapper.toString(another))); + } + + @Override + public String toString(JsonValue value) { + return jsonbWrapper.toString(value); + } + + @Override + public JsonValue fromString(String string) { + return jsonbWrapper.toJsonNode(string); + } + + @SuppressWarnings({"unchecked"}) + @Override + public X unwrap(JsonValue value, Class type, WrapperOptions options) { + if (value == null) { + return null; + } + if (String.class.isAssignableFrom(type)) { + return (X) toString(value); + } + if (JsonValue.class.isAssignableFrom(type)) { + return (X) jsonbWrapper.toJsonNode(toString(value)); + } + throw unknownUnwrap(type); + } + + @Override + public JsonValue wrap(X value, WrapperOptions options) { + if (value == null) { + return null; + } + return fromString(value.toString()); + } + +} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonStringSqlTypeDescriptor.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonStringSqlTypeDescriptor.java new file mode 100644 index 000000000..216bdeaf1 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonStringSqlTypeDescriptor.java @@ -0,0 +1,50 @@ +package com.vladmihalcea.hibernate.type.jsonp.internal; + +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.BasicBinder; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * @author Jan-Willem Gmelig Meyling + */ +public class JsonStringSqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor { + + public static final JsonStringSqlTypeDescriptor INSTANCE = new JsonStringSqlTypeDescriptor(); + + @Override + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder(javaTypeDescriptor, this) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { + st.setString(index, javaTypeDescriptor.unwrap(value, String.class, options)); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setString(name, javaTypeDescriptor.unwrap(value, String.class, options)); + } + }; + } + + @Override + protected Object extractJson(ResultSet rs, String name) throws SQLException { + return rs.getString(name); + } + + @Override + protected Object extractJson(CallableStatement statement, int index) throws SQLException { + return statement.getString(index); + } + + @Override + protected Object extractJson(CallableStatement statement, String name) throws SQLException { + return statement.getString(name); + } +} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonTypeDescriptor.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonTypeDescriptor.java new file mode 100644 index 000000000..02e073197 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonTypeDescriptor.java @@ -0,0 +1,130 @@ +package com.vladmihalcea.hibernate.type.jsonp.internal; + +import com.vladmihalcea.hibernate.type.util.JsonbWrapper; +import com.vladmihalcea.hibernate.type.util.ReflectionUtils; +import org.hibernate.annotations.common.reflection.XProperty; +import org.hibernate.annotations.common.reflection.java.JavaXMember; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.AbstractTypeDescriptor; +import org.hibernate.type.descriptor.java.MutableMutabilityPlan; +import org.hibernate.usertype.DynamicParameterizedType; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Objects; +import java.util.Properties; + +/** + * @author Jan-Willem Gmelig Meyling + */ +public class JsonTypeDescriptor + extends AbstractTypeDescriptor implements DynamicParameterizedType { + + private Type type; + + private JsonbWrapper jsonbWrapper; + + public JsonTypeDescriptor() { + super(Object.class, new MutableMutabilityPlan() { + @Override + protected Object deepCopyNotNull(Object value) { + return JsonbWrapper.INSTANCE.clone(value); + } + }); + } + + public JsonTypeDescriptor(final JsonbWrapper jsonbWrapper) { + super(Object.class, new MutableMutabilityPlan() { + @Override + protected Object deepCopyNotNull(Object value) { + return jsonbWrapper.clone(value); + } + }); + this.jsonbWrapper = jsonbWrapper; + } + + public JsonTypeDescriptor(final JsonbWrapper jsonbWrapper, Type type) { + super(Object.class, new MutableMutabilityPlan() { + @Override + protected Object deepCopyNotNull(Object value) { + return jsonbWrapper.clone(value); + } + }); + this.jsonbWrapper = jsonbWrapper; + this.type = type; + } + + @Override + public void setParameterValues(Properties parameters) { + final XProperty xProperty = (XProperty) parameters.get(DynamicParameterizedType.XPROPERTY); + if (xProperty instanceof JavaXMember) { + type = ReflectionUtils.invokeGetter(xProperty, "javaType"); + } else { + type = ((ParameterType) parameters.get(PARAMETER_TYPE)).getReturnedClass(); + } + } + + @Override + public boolean areEqual(Object one, Object another) { + if (one == another) { + return true; + } + if (one == null || another == null) { + return false; + } + if (one instanceof String && another instanceof String) { + return one.equals(another); + } + if (one instanceof Collection && another instanceof Collection) { + return Objects.equals(one, another); + } + return jsonbWrapper.toJsonNode(jsonbWrapper.toString(one)).equals( + jsonbWrapper.toJsonNode(jsonbWrapper.toString(another))); + } + + @Override + public String toString(Object value) { + return jsonbWrapper.toString(value); + } + + @Override + public Object fromString(String string) { + if (String.class.isAssignableFrom(typeToClass())) { + return string; + } + return jsonbWrapper.fromString(string, type); + } + + @SuppressWarnings({"unchecked"}) + @Override + public X unwrap(Object value, Class type, WrapperOptions options) { + if (value == null) { + return null; + } + if (String.class.isAssignableFrom(type)) { + return (X) toString(value); + } + if (Object.class.isAssignableFrom(type)) { + String stringValue = (value instanceof String) ? (String) value : toString(value); + return (X) jsonbWrapper.toJsonNode(stringValue); + } + throw unknownUnwrap(type); + } + + @Override + public Object wrap(X value, WrapperOptions options) { + if (value == null) { + return null; + } + return fromString(value.toString()); + } + + private Class typeToClass() { + Type classType = type; + if(type instanceof ParameterizedType) { + classType = ((ParameterizedType) type).getRawType(); + } + return (Class) classType; + } +} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonbUtil.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonbUtil.java new file mode 100644 index 000000000..28860d552 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonbUtil.java @@ -0,0 +1,85 @@ +package com.vladmihalcea.hibernate.type.jsonp.internal; + +import com.vladmihalcea.hibernate.type.util.Configuration; +import com.vladmihalcea.hibernate.type.util.JsonSerializer; +import com.vladmihalcea.hibernate.type.util.JsonSerializerSupplier; +import com.vladmihalcea.hibernate.type.util.JsonbSupplier; +import com.vladmihalcea.hibernate.type.util.JsonbWrapper; + +import javax.json.JsonValue; +import javax.json.bind.Jsonb; +import java.lang.reflect.Type; +import java.util.function.Supplier; + +/** + * @author Jan-Willem Gmelig Meyling + */ +public class JsonbUtil { + + public static T fromString(String string, Class clazz) { + return JsonbWrapper.INSTANCE.fromString(string, clazz); + } + + public static T fromString(String string, Type type) { + return JsonbWrapper.INSTANCE.fromString(string, type); + } + + public static String toString(Object value) { + return JsonbWrapper.INSTANCE.toString(value); + } + + public static JsonValue toJsonNode(String value) { + return JsonbWrapper.INSTANCE.toJsonNode(value); + } + + public static T clone(T value) { + return JsonbWrapper.INSTANCE.clone(value); + } + + public static JsonbWrapper getObjectMapperWrapper(Configuration configuration) { + Object objectMapperPropertyInstance = configuration.instantiateClass(Configuration.PropertyKey.JSONB); + + JsonbWrapper jsonbWrapper = new JsonbWrapper(); + + if (objectMapperPropertyInstance != null) { + if(objectMapperPropertyInstance instanceof JsonbSupplier) { + Jsonb objectMapper = ((JsonbSupplier) objectMapperPropertyInstance).get(); + if(objectMapper != null) { + jsonbWrapper = new JsonbWrapper(objectMapper); + } + } + else if (objectMapperPropertyInstance instanceof Supplier) { + Supplier objectMapperSupplier = (Supplier) objectMapperPropertyInstance; + jsonbWrapper = new JsonbWrapper(objectMapperSupplier.get()); + } + else if (objectMapperPropertyInstance instanceof Jsonb) { + Jsonb objectMapper = (Jsonb) objectMapperPropertyInstance; + jsonbWrapper = new JsonbWrapper(objectMapper); + } + } + + Object jsonSerializerPropertyInstance = configuration.instantiateClass(Configuration.PropertyKey.JSON_SERIALIZER); + + if (jsonSerializerPropertyInstance != null) { + JsonSerializer jsonSerializer = null; + + if(jsonSerializerPropertyInstance instanceof JsonSerializerSupplier) { + jsonSerializer = ((JsonSerializerSupplier) jsonSerializerPropertyInstance).get(); + } + else if (jsonSerializerPropertyInstance instanceof Supplier) { + Supplier jsonSerializerSupplier = (Supplier) jsonSerializerPropertyInstance; + jsonSerializer = jsonSerializerSupplier.get(); + } + else if (jsonSerializerPropertyInstance instanceof JsonSerializer) { + jsonSerializer = (JsonSerializer) jsonSerializerPropertyInstance; + } + + if (jsonSerializer != null) { + jsonbWrapper.setJsonSerializer(jsonSerializer); + } + } + + return jsonbWrapper; + } + +} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/Configuration.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/Configuration.java index 4c4a8c423..ee3b8345f 100644 --- a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/Configuration.java +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/Configuration.java @@ -1,6 +1,5 @@ package com.vladmihalcea.hibernate.type.util; -import com.fasterxml.jackson.databind.ObjectMapper; import org.hibernate.cfg.Environment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,7 +10,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Properties; -import java.util.function.Supplier; /** * Configuration - It allows declarative configuration through the hibernate.properties file @@ -38,6 +36,7 @@ public class Configuration { */ public enum PropertyKey { JACKSON_OBJECT_MAPPER("hibernate.types.jackson.object.mapper"), + JSONB("hibernate.types.jsonb"), JSON_SERIALIZER("hibernate.types.json.serializer"), PRINT_BANNER("hibernate.types.print.banner"); @@ -126,57 +125,6 @@ public Properties getProperties() { return properties; } - /** - * Get {@link ObjectMapperWrapper} reference - * - * @return {@link ObjectMapperWrapper} reference - */ - public ObjectMapperWrapper getObjectMapperWrapper() { - Object objectMapperPropertyInstance = instantiateClass(PropertyKey.JACKSON_OBJECT_MAPPER); - - ObjectMapperWrapper objectMapperWrapper = new ObjectMapperWrapper(); - - if (objectMapperPropertyInstance != null) { - if(objectMapperPropertyInstance instanceof ObjectMapperSupplier) { - ObjectMapper objectMapper = ((ObjectMapperSupplier) objectMapperPropertyInstance).get(); - if(objectMapper != null) { - objectMapperWrapper = new ObjectMapperWrapper(objectMapper); - } - } - else if (objectMapperPropertyInstance instanceof Supplier) { - Supplier objectMapperSupplier = (Supplier) objectMapperPropertyInstance; - objectMapperWrapper = new ObjectMapperWrapper(objectMapperSupplier.get()); - } - else if (objectMapperPropertyInstance instanceof ObjectMapper) { - ObjectMapper objectMapper = (ObjectMapper) objectMapperPropertyInstance; - objectMapperWrapper = new ObjectMapperWrapper(objectMapper); - } - } - - Object jsonSerializerPropertyInstance = instantiateClass(PropertyKey.JSON_SERIALIZER); - - if (jsonSerializerPropertyInstance != null) { - JsonSerializer jsonSerializer = null; - - if(jsonSerializerPropertyInstance instanceof JsonSerializerSupplier) { - jsonSerializer = ((JsonSerializerSupplier) jsonSerializerPropertyInstance).get(); - } - else if (jsonSerializerPropertyInstance instanceof Supplier) { - Supplier jsonSerializerSupplier = (Supplier) jsonSerializerPropertyInstance; - jsonSerializer = jsonSerializerSupplier.get(); - } - else if (jsonSerializerPropertyInstance instanceof JsonSerializer) { - jsonSerializer = (JsonSerializer) jsonSerializerPropertyInstance; - } - - if (jsonSerializer != null) { - objectMapperWrapper.setJsonSerializer(jsonSerializer); - } - } - - return objectMapperWrapper; - } - /** * Get Integer property value * @@ -249,7 +197,7 @@ public Class classProperty(PropertyKey propertyKey) { * @param class parameter type * @return class instance */ - private T instantiateClass(PropertyKey propertyKey) { + public T instantiateClass(PropertyKey propertyKey) { T object = null; String property = properties.getProperty(propertyKey.getKey()); if (property != null) { diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbJsonSerializer.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbJsonSerializer.java new file mode 100644 index 000000000..9b9f941c4 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbJsonSerializer.java @@ -0,0 +1,27 @@ +package com.vladmihalcea.hibernate.type.util; + +import org.hibernate.internal.util.SerializationHelper; + +import java.io.Serializable; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class JsonbJsonSerializer implements JsonSerializer { + + private final JsonbWrapper jsonbWrapper; + + public JsonbJsonSerializer(JsonbWrapper jsonbWrapper) { + this.jsonbWrapper = jsonbWrapper; + } + + @Override + public T clone(T value) { + return (value instanceof Serializable) ? + (T) SerializationHelper.clone((Serializable) value) : + jsonbWrapper.fromString( + jsonbWrapper.toString(value), (Class) value.getClass() + ); + } +} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbSupplier.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbSupplier.java new file mode 100644 index 000000000..4b320cae1 --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbSupplier.java @@ -0,0 +1,19 @@ +package com.vladmihalcea.hibernate.type.util; + +import javax.json.bind.Jsonb; + +/** + * Supplies a custom reference of a Jackson {@link Jsonb} + * + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public interface JsonbSupplier { + + /** + * Get custom {@link Jsonb} reference + * + * @return custom {@link Jsonb} reference + */ + Jsonb get(); +} diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbWrapper.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbWrapper.java new file mode 100644 index 000000000..0682f6bad --- /dev/null +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/JsonbWrapper.java @@ -0,0 +1,76 @@ +package com.vladmihalcea.hibernate.type.util; + +import javax.json.Json; +import javax.json.JsonValue; +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbException; +import java.io.StringReader; +import java.lang.reflect.Type; + +/** + * Wraps a Jackson {@link Jsonb} so that you can supply your own {@link Jsonb} reference. + * + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class JsonbWrapper { + + public static final JsonbWrapper INSTANCE = new JsonbWrapper(); + + private final Jsonb objectMapper; + + private JsonSerializer jsonSerializer = new JsonbJsonSerializer(this); + + public JsonbWrapper() { + this.objectMapper = JsonbBuilder.create(); + } + + public JsonbWrapper(Jsonb objectMapper) { + this.objectMapper = objectMapper; + } + + public void setJsonSerializer(JsonSerializer jsonSerializer) { + this.jsonSerializer = jsonSerializer; + } + + public Jsonb getObjectMapper() { + return objectMapper; + } + + public T fromString(String string, Class clazz) { + try { + return objectMapper.fromJson(string, clazz); + } catch (JsonbException e) { + throw new IllegalArgumentException("The given string value: " + string + " cannot be transformed to Json object", e); + } + } + + public T fromString(String string, Type type) { + try { + return objectMapper.fromJson(string, type); + } catch (JsonbException e) { + throw new IllegalArgumentException("The given string value: " + string + " cannot be transformed to Json object", e); + } + } + + public String toString(Object value) { + try { + return objectMapper.toJson(value); + } catch (JsonbException e) { + throw new IllegalArgumentException("The given Json object value: " + value + " cannot be transformed to a String", e); + } + } + + public JsonValue toJsonNode(String value) { + try { + return Json.createReader(new StringReader(value)).readValue(); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + public T clone(T value) { + return jsonSerializer.clone(value); + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/EhcacheMySQLJsonBinaryTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/EhcacheMySQLJsonBinaryTypeTest.java new file mode 100644 index 000000000..8638d4cd3 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/EhcacheMySQLJsonBinaryTypeTest.java @@ -0,0 +1,120 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonbUtil; +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.model.Location; +import com.vladmihalcea.hibernate.type.model.Ticket; +import com.vladmihalcea.hibernate.type.util.AbstractMySQLIntegrationTest; +import com.vladmihalcea.hibernate.type.util.transaction.JPATransactionFunction; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.json.JsonValue; +import javax.persistence.*; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class EhcacheMySQLJsonBinaryTypeTest extends AbstractMySQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Event.class, + }; + } + + @Override + protected String[] packages() { + return new String[]{ + Event.class.getPackage().getName() + }; + } + + protected void additionalProperties(Properties properties) { + properties.setProperty("hibernate.cache.use_second_level_cache", "true"); + properties.setProperty("hibernate.cache.use_query_cache", "true"); + properties.setProperty("hibernate.cache.region.factory_class", "ehcache"); + } + + @Test + public void test() { + final AtomicReference eventHolder = new AtomicReference<>(); + + doInJPA((JPATransactionFunction) entityManager -> { + Event nullEvent = new Event(); + nullEvent.setId(0L); + entityManager.persist(nullEvent); + + Location location = new Location(); + location.setCountry("Romania"); + location.setCity("Cluj-Napoca"); + + Event event = new Event(); + event.setId(1L); + + event.setProperties( + JsonbUtil.toJsonNode( + "{" + + " \"title\": \"High-Performance Java Persistence\"," + + " \"author\": \"Vlad Mihalcea\"," + + " \"publisher\": \"Amazon\"," + + " \"price\": 44.99" + + "}" + ) + ); + entityManager.persist(event); + + Ticket ticket = new Ticket(); + ticket.setPrice(12.34d); + ticket.setRegistrationCode("ABC123"); + + eventHolder.set(event); + + return null; + }); + doInJPA((JPATransactionFunction) entityManager -> { + Event event = entityManager.find(Event.class, eventHolder.get().getId()); + assertNotNull(event.getProperties()); + + List properties = entityManager.createNativeQuery( + "select CAST(e.properties AS CHAR(1000)) " + + "from event e " + + "where JSON_EXTRACT(e.properties, \"$.price\") > 1 ") + .getResultList(); + + assertEquals(1, properties.size()); + JsonValue jsonNode = JsonbUtil.toJsonNode(properties.get(0)); + assertEquals("High-Performance Java Persistence", jsonNode.asJsonObject().get("title").toString()); + + return null; + }); + } + + @Entity(name = "Event") + @Table(name = "event") + @Cacheable(true) + @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + public static class Event extends BaseEntity { + + @Type(type = "json-p-value") + @Column(columnDefinition = "json") + private JsonValue properties; + + public JsonValue getProperties() { + return properties; + } + + public void setProperties(JsonValue properties) { + this.properties = properties; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLGenericJsonTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLGenericJsonTypeTest.java new file mode 100644 index 000000000..bd3724e8d --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLGenericJsonTypeTest.java @@ -0,0 +1,123 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.model.Location; +import com.vladmihalcea.hibernate.type.model.Ticket; +import com.vladmihalcea.hibernate.type.util.AbstractMySQLIntegrationTest; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class MySQLGenericJsonTypeTest extends AbstractMySQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Event.class, + Participant.class + }; + } + + @Override + protected String[] packages() { + return new String[]{ + Location.class.getPackage().getName() + }; + } + + @Test + public void test() { + final AtomicReference eventHolder = new AtomicReference<>(); + + doInJPA(entityManager -> { + Location location = new Location(); + location.setCountry("Romania"); + location.setCity("Cluj-Napoca"); + + Event event = new Event(); + event.setId(1L); + event.setAlternativeLocations(Arrays.asList(location)); + entityManager.persist(event); + + eventHolder.set(event); + }); + doInJPA(entityManager -> { + Event event = entityManager.find(Event.class, eventHolder.get().getId()); + assertEquals(1, event.getAlternativeLocations().size()); + assertEquals("Cluj-Napoca", event.getAlternativeLocations().get(0).getCity()); + assertEquals("Romania", event.getAlternativeLocations().get(0).getCountry()); + }); + } + + @Entity(name = "Event") + @Table(name = "event") + public static class Event extends BaseEntity { + + @Type(type = "json-p") + @Column(columnDefinition = "json") + private Location location; + + @Type( + type = "json-p" + ) + @Column(columnDefinition = "json") + private List alternativeLocations; + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public List getAlternativeLocations() { + return alternativeLocations; + } + + public void setAlternativeLocations(List alternativeLocations) { + this.alternativeLocations = alternativeLocations; + } + } + + @Entity(name = "Participant") + @Table(name = "participant") + public static class Participant extends BaseEntity { + + @Type(type = "json-p") + @Column(columnDefinition = "json") + private Ticket ticket; + + @ManyToOne + private Event event; + + public Ticket getTicket() { + return ticket; + } + + public void setTicket(Ticket ticket) { + this.ticket = ticket; + } + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLJsonTypeSetTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLJsonTypeSetTest.java new file mode 100644 index 000000000..04e4547cb --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLJsonTypeSetTest.java @@ -0,0 +1,181 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.util.AbstractMySQLIntegrationTest; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.json.bind.annotation.JsonbCreator; +import javax.json.bind.annotation.JsonbProperty; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.beans.ConstructorProperties; +import java.io.Serializable; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +/** + * @author Sergei Poznanski + * @author Jan-Willem Gmelig Meyling + */ +public class MySQLJsonTypeSetTest extends AbstractMySQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + User.class + }; + } + + @Test + public void test() { + final AtomicReference userHolder = new AtomicReference<>(); + + doInJPA(entityManager -> { + User user = new User(); + + user.setId(1L); + user.setPhones(new HashSet<>(asList("7654321", "1234567"))); + user.setRoles(EnumSet.of(Role.ADMIN, Role.USER)); + user.setChildren(new HashSet<>(asList( + new Child("Jane", 2, new HashSet<>(asList("toy1", "toy2"))), + new Child("John", 1, new HashSet<>(asList("toy3", "toy4"))) + ))); + + entityManager.persist(user); + userHolder.set(user); + }); + + doInJPA(entityManager -> { + User user = entityManager.find(User.class, userHolder.get().getId()); + assertEquals(new HashSet<>(asList("1234567", "7654321")), user.getPhones()); + assertEquals(EnumSet.of(Role.USER, Role.ADMIN), user.getRoles()); + assertEquals(new HashSet<>(asList( + new Child("John", 1, new HashSet<>(asList("toy4", "toy3"))), + new Child("Jane", 2, new HashSet<>(asList("toy2", "toy1"))) + )), user.getChildren()); + assertEquals(Integer.valueOf(0), user.getVersion()); + }); + + doInJPA(entityManager -> { + User user = entityManager.find(User.class, userHolder.get().getId()); + user.setPhones(new HashSet<>(asList("1592637", "9518473"))); + user.setRoles(EnumSet.of(Role.USER, Role.DEV)); + user.setChildren(new HashSet<>(asList( + new Child("Jinny", 1, new HashSet<>(asList("toy5", "toy6"))), + new Child("Jenny", 2, new HashSet<>(asList("toy7", "toy8"))) + ))); + }); + + doInJPA(entityManager -> { + User user = entityManager.find(User.class, userHolder.get().getId()); + assertEquals(new HashSet<>(asList("9518473", "1592637")), user.getPhones()); + assertEquals(EnumSet.of(Role.DEV, Role.USER), user.getRoles()); + assertEquals(new HashSet<>(asList( + new Child("Jenny", 2, new HashSet<>(asList("toy8", "toy7"))), + new Child("Jinny", 1, new HashSet<>(asList("toy6", "toy5"))) + )), user.getChildren()); + assertEquals(Integer.valueOf(1), user.getVersion()); + }); + } + + @Entity + @Table(name = "users") + @DynamicUpdate + public static class User extends BaseEntity { + + @Type(type = "json-p") + @Column(nullable = false, columnDefinition = "json") + private Set phones; + + @Type(type = "json-p") + @Column(nullable = false, columnDefinition = "json") + private Set roles; + + @Type(type = "json-p") + @Column(nullable = false, columnDefinition = "json") + private Set children; + + public Set getPhones() { + return phones; + } + + public void setPhones(Set phones) { + this.phones = phones; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public Set getChildren() { + return children; + } + + public void setChildren(final Set children) { + this.children = children; + } + } + + public enum Role { + USER, ADMIN, DEV + } + + public static class Child implements Serializable { + + private final String name; + private final Integer age; + private final Set toys; + + + @JsonbCreator + @ConstructorProperties({"name", "age", "toys"}) + public Child(@JsonbProperty("name") String name, @JsonbProperty("age") Integer age, @JsonbProperty("toys") final Set toys) { + this.name = Objects.requireNonNull(name); + this.age = Objects.requireNonNull(age); + this.toys = Objects.requireNonNull(toys); + } + + public String getName() { + return name; + } + + public Integer getAge() { + return age; + } + + public Set getToys() { + return toys; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Child child = (Child) o; + if (!name.equals(child.name)) return false; + if (!age.equals(child.age)) return false; + return toys.equals(child.toys); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + age.hashCode(); + result = 31 * result + toys.hashCode(); + return result; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLJsonTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLJsonTypeTest.java new file mode 100644 index 000000000..c863421dc --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/MySQLJsonTypeTest.java @@ -0,0 +1,137 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.model.Location; +import com.vladmihalcea.hibernate.type.model.Ticket; +import com.vladmihalcea.hibernate.type.util.AbstractMySQLIntegrationTest; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class MySQLJsonTypeTest extends AbstractMySQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Event.class, + Participant.class + }; + } + + @Override + protected String[] packages() { + return new String[]{ + Location.class.getPackage().getName() + }; + } + + @Test + public void test() { + final AtomicReference eventHolder = new AtomicReference<>(); + final AtomicReference participantHolder = new AtomicReference<>(); + + doInJPA(entityManager -> { + Event nullEvent = new Event(); + nullEvent.setId(0L); + entityManager.persist(nullEvent); + + Location location = new Location(); + location.setCountry("Romania"); + location.setCity("Cluj-Napoca"); + + Event event = new Event(); + event.setId(1L); + event.setLocation(location); + entityManager.persist(event); + + Ticket ticket = new Ticket(); + ticket.setPrice(12.34d); + ticket.setRegistrationCode("ABC123"); + + Participant participant = new Participant(); + participant.setId(1L); + participant.setTicket(ticket); + participant.setEvent(event); + + entityManager.persist(participant); + + eventHolder.set(event); + participantHolder.set(participant); + }); + doInJPA(entityManager -> { + Event event = entityManager.find(Event.class, eventHolder.get().getId()); + assertEquals("Cluj-Napoca", event.getLocation().getCity()); + + Participant participant = entityManager.find(Participant.class, participantHolder.get().getId()); + assertEquals("ABC123", participant.getTicket().getRegistrationCode()); + + List participants = entityManager.createNativeQuery( + "select p.ticket -> \"$.registrationCode\" " + + "from participant p " + + "where JSON_EXTRACT(p.ticket, \"$.price\") > 1 ") + .getResultList(); + + event.getLocation().setCity("Constanța"); + entityManager.flush(); + + assertEquals(1, participants.size()); + }); + } + + @Entity(name = "Event") + @Table(name = "event") + public static class Event extends BaseEntity { + + @Type(type = "json-p") + @Column(columnDefinition = "json") + private Location location; + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + } + + @Entity(name = "Participant") + @Table(name = "participant") + public static class Participant extends BaseEntity { + + @Type(type = "json-p") + @Column(columnDefinition = "json") + private Ticket ticket; + + @ManyToOne + private Event event; + + public Ticket getTicket() { + return ticket; + } + + public void setTicket(Ticket ticket) { + this.ticket = ticket; + } + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLGenericJsonBinaryTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLGenericJsonBinaryTypeTest.java new file mode 100644 index 000000000..c82b07403 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLGenericJsonBinaryTypeTest.java @@ -0,0 +1,130 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.model.Location; +import com.vladmihalcea.hibernate.type.model.Ticket; +import com.vladmihalcea.hibernate.type.util.AbstractPostgreSQLIntegrationTest; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class PostgreSQLGenericJsonBinaryTypeTest extends AbstractPostgreSQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Event.class, + Participant.class + }; + } + + @Test + public void test() { + final AtomicReference eventHolder = new AtomicReference<>(); + + doInJPA(entityManager -> { + Location cluj = new Location(); + cluj.setCountry("Romania"); + cluj.setCity("Cluj-Napoca"); + + Location newYork = new Location(); + newYork.setCountry("US"); + newYork.setCity("New-York"); + + Location london = new Location(); + london.setCountry("UK"); + london.setCity("London"); + + Event event = new Event(); + event.setId(1L); + event.setLocation(cluj); + event.setAlternativeLocations(Arrays.asList(newYork, london)); + + entityManager.persist(event); + + eventHolder.set(event); + }); + doInJPA(entityManager -> { + Event event = entityManager.find(Event.class, eventHolder.get().getId()); + + assertEquals("Cluj-Napoca", event.getLocation().getCity()); + + assertEquals(2, event.getAlternativeLocations().size()); + assertEquals("New-York", event.getAlternativeLocations().get(0).getCity()); + assertEquals("London", event.getAlternativeLocations().get(1).getCity()); + }); + } + + + @Entity(name = "Event") + @Table(name = "event") + public static class Event extends BaseEntity { + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + private Location location; + + @Type( + type = "jsonb-p" + ) + @Column(columnDefinition = "jsonb") + private List alternativeLocations; + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public List getAlternativeLocations() { + return alternativeLocations; + } + + public void setAlternativeLocations(List alternativeLocations) { + this.alternativeLocations = alternativeLocations; + } + } + + @Entity(name = "Participant") + @Table(name = "participant") + public static class Participant extends BaseEntity { + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + private Ticket ticket; + + @ManyToOne + private Event event; + + public Ticket getTicket() { + return ticket; + } + + public void setTicket(Ticket ticket) { + this.ticket = ticket; + } + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeLazyGroupTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeLazyGroupTest.java new file mode 100644 index 000000000..791d256fa --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeLazyGroupTest.java @@ -0,0 +1,130 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.model.Location; +import com.vladmihalcea.hibernate.type.model.Ticket; +import com.vladmihalcea.hibernate.type.util.AbstractPostgreSQLIntegrationTest; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class PostgreSQLJsonBinaryTypeLazyGroupTest extends AbstractPostgreSQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Event.class, + Participant.class + }; + } + + @Test + public void test() { + final AtomicReference eventHolder = new AtomicReference<>(); + final AtomicReference participantHolder = new AtomicReference<>(); + + doInJPA(entityManager -> { + Event nullEvent = new Event(); + nullEvent.setId(0L); + entityManager.persist(nullEvent); + + Location location = new Location(); + location.setCountry("Romania"); + location.setCity("Cluj-Napoca"); + + Event event = new Event(); + event.setId(1L); + event.setLocation(location); + entityManager.persist(event); + + Ticket ticket = new Ticket(); + ticket.setPrice(12.34d); + ticket.setRegistrationCode("ABC123"); + + Participant participant = new Participant(); + participant.setId(1L); + participant.setTicket(ticket); + participant.setEvent(event); + + entityManager.persist(participant); + + eventHolder.set(event); + participantHolder.set(participant); + }); + doInJPA(entityManager -> { + Event event = entityManager.find(Event.class, eventHolder.get().getId()); + LOGGER.debug("Fetched event"); + assertEquals("Cluj-Napoca", event.getLocation().getCity()); + + Participant participant = entityManager.find(Participant.class, participantHolder.get().getId()); + LOGGER.debug("Fetched participant"); + assertEquals("ABC123", participant.getTicket().getRegistrationCode()); + }); + } + + /** + * @author Vlad Mihalcea + */ + @Entity(name = "Event") + @Table(name = "event") + public static class Event extends BaseEntity { + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + @Basic(fetch = FetchType.LAZY) + private Location location; + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + } + + /** + * @author Vlad Mihalcea + */ + @Entity(name = "Participant") + @Table(name = "participant") + public static class Participant extends BaseEntity { + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + @Basic(fetch = FetchType.LAZY) + private Ticket ticket; + + @ManyToOne(fetch = FetchType.LAZY) + private Event event; + + public Ticket getTicket() { + return ticket; + } + + public void setTicket(Ticket ticket) { + this.ticket = ticket; + } + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeSetTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeSetTest.java new file mode 100644 index 000000000..2920e6667 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeSetTest.java @@ -0,0 +1,180 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.util.AbstractPostgreSQLIntegrationTest; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.json.bind.annotation.JsonbCreator; +import javax.json.bind.annotation.JsonbProperty; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.beans.ConstructorProperties; +import java.io.Serializable; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +/** + * @author Sergei Poznanski + * @author Jan-Willem Gmelig Meyling + */ +public class PostgreSQLJsonBinaryTypeSetTest extends AbstractPostgreSQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + User.class + }; + } + + @Test + public void test() { + final AtomicReference userHolder = new AtomicReference<>(); + + doInJPA(entityManager -> { + User user = new User(); + + user.setId(1L); + user.setPhones(new HashSet<>(asList("7654321", "1234567"))); + user.setRoles(EnumSet.of(Role.ADMIN, Role.USER)); + user.setChildren(new HashSet<>(asList( + new Child("Jane", 2, new HashSet<>(asList("toy1", "toy2"))), + new Child("John", 1, new HashSet<>(asList("toy3", "toy4"))) + ))); + + entityManager.persist(user); + userHolder.set(user); + }); + + doInJPA(entityManager -> { + User user = entityManager.find(User.class, userHolder.get().getId()); + assertEquals(new HashSet<>(asList("1234567", "7654321")), user.getPhones()); + assertEquals(EnumSet.of(Role.USER, Role.ADMIN), user.getRoles()); + assertEquals(new HashSet<>(asList( + new Child("John", 1, new HashSet<>(asList("toy4", "toy3"))), + new Child("Jane", 2, new HashSet<>(asList("toy2", "toy1"))) + )), user.getChildren()); + assertEquals(Integer.valueOf(0), user.getVersion()); + }); + + doInJPA(entityManager -> { + User user = entityManager.find(User.class, userHolder.get().getId()); + user.setPhones(new HashSet<>(asList("1592637", "9518473"))); + user.setRoles(EnumSet.of(Role.USER, Role.DEV)); + user.setChildren(new HashSet<>(asList( + new Child("Jinny", 1, new HashSet<>(asList("toy5", "toy6"))), + new Child("Jenny", 2, new HashSet<>(asList("toy7", "toy8"))) + ))); + }); + + doInJPA(entityManager -> { + User user = entityManager.find(User.class, userHolder.get().getId()); + assertEquals(new HashSet<>(asList("9518473", "1592637")), user.getPhones()); + assertEquals(EnumSet.of(Role.DEV, Role.USER), user.getRoles()); + assertEquals(new HashSet<>(asList( + new Child("Jenny", 2, new HashSet<>(asList("toy8", "toy7"))), + new Child("Jinny", 1, new HashSet<>(asList("toy6", "toy5"))) + )), user.getChildren()); + assertEquals(Integer.valueOf(1), user.getVersion()); + }); + } + + @Entity + @Table(name = "users") + @DynamicUpdate + public static class User extends BaseEntity { + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + private Set phones; + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + private Set roles; + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + private Set children; + + public Set getPhones() { + return phones; + } + + public void setPhones(Set phones) { + this.phones = phones; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public Set getChildren() { + return children; + } + + public void setChildren(final Set children) { + this.children = children; + } + } + + public enum Role { + USER, ADMIN, DEV + } + + public static class Child implements Serializable { + + private final String name; + private final Integer age; + private final Set toys; + + @JsonbCreator + @ConstructorProperties({"name", "age", "toys"}) + public Child(@JsonbProperty("name") String name, @JsonbProperty("age") Integer age, @JsonbProperty("toys") final Set toys) { + this.name = Objects.requireNonNull(name); + this.age = Objects.requireNonNull(age); + this.toys = Objects.requireNonNull(toys); + } + + public String getName() { + return name; + } + + public Integer getAge() { + return age; + } + + public Set getToys() { + return toys; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Child child = (Child) o; + if (!name.equals(child.name)) return false; + if (!age.equals(child.age)) return false; + return toys.equals(child.toys); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + age.hashCode(); + result = 31 * result + toys.hashCode(); + return result; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeTest.java new file mode 100644 index 000000000..1e70cb918 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonBinaryTypeTest.java @@ -0,0 +1,172 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonbUtil; +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.model.Location; +import com.vladmihalcea.hibernate.type.model.Ticket; +import com.vladmihalcea.hibernate.type.util.AbstractPostgreSQLIntegrationTest; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class PostgreSQLJsonBinaryTypeTest extends AbstractPostgreSQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Event.class, + Participant.class + }; + } + + @Test + public void test() { + final AtomicReference eventHolder = new AtomicReference<>(); + final AtomicReference participantHolder = new AtomicReference<>(); + + doInJPA(entityManager -> { + Event nullEvent = new Event(); + nullEvent.setId(0L); + entityManager.persist(nullEvent); + + Location location = new Location(); + location.setCountry("Romania"); + location.setCity("Cluj-Napoca"); + + Event event = new Event(); + event.setId(1L); + event.setLocation(location); + entityManager.persist(event); + + Ticket ticket = new Ticket(); + ticket.setPrice(12.34d); + ticket.setRegistrationCode("ABC123"); + + Participant participant = new Participant(); + participant.setId(1L); + participant.setTicket(ticket); + participant.setEvent(event); + participant.setMetaData(JsonbUtil.toString(location)); + + entityManager.persist(participant); + + eventHolder.set(event); + participantHolder.set(participant); + }); + + doInJPA(entityManager -> { + Event event = entityManager.find(Event.class, eventHolder.get().getId()); + assertEquals("Cluj-Napoca", event.getLocation().getCity()); + + Participant participant = entityManager.find(Participant.class, participantHolder.get().getId()); + assertEquals("ABC123", participant.getTicket().getRegistrationCode()); + + List participants = entityManager.createNativeQuery( + "select jsonb_pretty(p.ticket) " + + "from participant p " + + "where p.ticket ->> 'price' > :price") + .setParameter("price", "10") + .getResultList(); + + List countries = entityManager.createNativeQuery( + "select p.metadata ->> 'country' " + + "from participant p ") + .getResultList(); + + event.getLocation().setCity("Constanța"); + assertEquals(Integer.valueOf(0), event.getVersion()); + entityManager.flush(); + assertEquals(Integer.valueOf(1), event.getVersion()); + + assertEquals(1, participants.size()); + assertEquals(1, countries.size()); + assertNotNull(countries.get(0)); + }); + + doInJPA(entityManager -> { + Event event = entityManager.find(Event.class, eventHolder.get().getId()); + event.getLocation().setCity(null); + assertEquals(Integer.valueOf(1), event.getVersion()); + entityManager.flush(); + assertEquals(Integer.valueOf(2), event.getVersion()); + }); + + doInJPA(entityManager -> { + Event event = entityManager.find(Event.class, eventHolder.get().getId()); + event.setLocation(null); + assertEquals(Integer.valueOf(2), event.getVersion()); + entityManager.flush(); + assertEquals(Integer.valueOf(3), event.getVersion()); + }); + } + + @Entity(name = "Event") + @Table(name = "event") + public static class Event extends BaseEntity { + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + private Location location; + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + } + + @Entity(name = "Participant") + @Table(name = "participant") + public static class Participant extends BaseEntity { + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + private Ticket ticket; + + @ManyToOne + private Event event; + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + private String metaData; + + public Ticket getTicket() { + return ticket; + } + + public void setTicket(Ticket ticket) { + this.ticket = ticket; + } + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + + public String getMetaData() { + return metaData; + } + + public void setMetaData(String metaData) { + this.metaData = metaData; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonNodeBinaryTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonNodeBinaryTypeTest.java new file mode 100644 index 000000000..822664eeb --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonNodeBinaryTypeTest.java @@ -0,0 +1,114 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.jsonp.internal.JsonbUtil; +import com.vladmihalcea.hibernate.type.util.AbstractPostgreSQLIntegrationTest; +import org.hibernate.Session; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.junit.Test; + +import javax.json.JsonValue; +import javax.persistence.*; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class PostgreSQLJsonNodeBinaryTypeTest extends AbstractPostgreSQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Book.class + }; + } + + @Test + public void test() { + + doInJPA(entityManager -> { + + Book book = new Book(); + book.setIsbn("978-9730228236"); + book.setProperties( + JsonbUtil.toJsonNode( + "{" + + " \"title\": \"High-Performance Java Persistence\"," + + " \"author\": \"Vlad Mihalcea\"," + + " \"publisher\": \"Amazon\"," + + " \"price\": 44.99" + + "}" + ) + ); + + entityManager.persist(book); + }); + + doInJPA(entityManager -> { + Session session = entityManager.unwrap(Session.class); + Book book = session + .bySimpleNaturalId(Book.class) + .load("978-9730228236"); + + LOGGER.info("Book details: {}", book.getProperties()); + + assertEquals(expectedPrice(), book.getProperties().asJsonObject().get("price").toString()); + + book.setProperties( + JsonbUtil.toJsonNode( + "{" + + " \"title\": \"High-Performance Java Persistence\"," + + " \"author\": \"Vlad Mihalcea\"," + + " \"publisher\": \"Amazon\"," + + " \"price\": 44.99," + + " \"url\": \"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/\"" + + "}" + ) + ); + }); + } + + protected String initialPrice() { + return "44.99"; + } + + protected String expectedPrice() { + return "44.99"; + } + + @Entity(name = "Book") + @Table(name = "book") + @TypeDef(name = "jsonb-p-value", typeClass = JsonNodeBinaryType.class) + public static class Book { + + @Id + @GeneratedValue + private Long id; + + @NaturalId + private String isbn; + + @Type(type = "jsonb-p-value") + @Column(columnDefinition = "jsonb") + private JsonValue properties; + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public JsonValue getProperties() { + return properties; + } + + public void setProperties(JsonValue properties) { + this.properties = properties; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonStringPropertyTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonStringPropertyTest.java new file mode 100644 index 000000000..5cba4b6dd --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonStringPropertyTest.java @@ -0,0 +1,100 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.util.AbstractPostgreSQLIntegrationTest; +import org.hibernate.Session; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.junit.Test; + +import javax.persistence.*; + +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class PostgreSQLJsonStringPropertyTest extends AbstractPostgreSQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Book.class + }; + } + + @Test + public void test() { + + doInJPA(entityManager -> { + entityManager.persist( + new Book() + .setIsbn("978-9730228236") + .setProperties( + "{" + + " \"title\": \"High-Performance Java Persistence\"," + + " \"author\": \"Vlad Mihalcea\"," + + " \"publisher\": \"Amazon\"," + + " \"price\": 44.99" + + "}" + ) + ); + }); + + doInJPA(entityManager -> { + Book book = entityManager.unwrap(Session.class) + .bySimpleNaturalId(Book.class) + .load("978-9730228236"); + + LOGGER.info("Book details: {}", book.getProperties()); + + assertTrue(book.getProperties().contains("\"price\": 44.99")); + + book.setProperties( + "{" + + " \"title\": \"High-Performance Java Persistence\"," + + " \"author\": \"Vlad Mihalcea\"," + + " \"publisher\": \"Amazon\"," + + " \"price\": 44.99," + + " \"url\": \"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/\"" + + "}" + ); + }); + } + + @Entity(name = "Book") + @Table(name = "book") + @TypeDef(name = "jsonb-p", typeClass = JsonBinaryType.class) + public static class Book { + + @Id + @GeneratedValue + private Long id; + + @NaturalId + private String isbn; + + @Type(type = "jsonb-p") + @Column(columnDefinition = "jsonb") + private String properties; + + public String getIsbn() { + return isbn; + } + + public Book setIsbn(String isbn) { + this.isbn = isbn; + return this; + } + + public String getProperties() { + return properties; + } + + public Book setProperties(String properties) { + this.properties = properties; + return this; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonStringTypeTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonStringTypeTest.java new file mode 100644 index 000000000..1b733e975 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/PostgreSQLJsonStringTypeTest.java @@ -0,0 +1,131 @@ +package com.vladmihalcea.hibernate.type.jsonp; + +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.model.Location; +import com.vladmihalcea.hibernate.type.model.Ticket; +import com.vladmihalcea.hibernate.type.util.AbstractPostgreSQLIntegrationTest; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class PostgreSQLJsonStringTypeTest extends AbstractPostgreSQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Event.class, + Participant.class + }; + } + + @Test + public void test() { + final AtomicReference eventHolder = new AtomicReference<>(); + final AtomicReference participantHolder = new AtomicReference<>(); + + doInJPA(entityManager -> { + Event nullEvent = new Event(); + nullEvent.setId(0L); + entityManager.persist(nullEvent); + + Location location = new Location(); + location.setCountry("Romania"); + location.setCity("Cluj-Napoca"); + + Event event = new Event(); + event.setId(1L); + event.setLocation(location); + entityManager.persist(event); + + Ticket ticket = new Ticket(); + ticket.setPrice(12.34d); + ticket.setRegistrationCode("ABC123"); + + Participant participant = new Participant(); + participant.setId(1L); + participant.setTicket(ticket); + participant.setEvent(event); + + entityManager.persist(participant); + + eventHolder.set(event); + participantHolder.set(participant); + }); + doInJPA(entityManager -> { + Event event = entityManager.find(Event.class, eventHolder.get().getId()); + assertEquals("Cluj-Napoca", event.getLocation().getCity()); + + Participant participant = entityManager.find(Participant.class, participantHolder.get().getId()); + assertEquals("ABC123", participant.getTicket().getRegistrationCode()); + + List participants = entityManager.createNativeQuery( + "select p.ticket ->>'registrationCode' " + + "from participant p " + + "where p.ticket ->> 'price' > '10'") + .getResultList(); + + event.getLocation().setCity("Constanța"); + entityManager.flush(); + + assertEquals(1, participants.size()); + }); + } + + @Entity(name = "Event") + @Table(name = "event") + public static class Event extends BaseEntity { + + @Type(type = "jsonb-p") + @Column(columnDefinition = "json") + private Location location; + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + } + + @Entity(name = "Participant") + @Table(name = "participant") + public static class Participant extends BaseEntity { + + @Type(type = "jsonb-p") + @Column(columnDefinition = "json") + private Ticket ticket; + + @ManyToOne + private Event event; + + public Ticket getTicket() { + return ticket; + } + + public void setTicket(Ticket ticket) { + this.ticket = ticket; + } + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + } + +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/configuration/PostgreSQLJsonBinaryTypeConfigurationTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/configuration/PostgreSQLJsonBinaryTypeConfigurationTest.java new file mode 100644 index 000000000..8a41b723c --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/configuration/PostgreSQLJsonBinaryTypeConfigurationTest.java @@ -0,0 +1,114 @@ +package com.vladmihalcea.hibernate.type.jsonp.configuration; + +import com.vladmihalcea.hibernate.type.model.BaseEntity; +import com.vladmihalcea.hibernate.type.util.AbstractPostgreSQLIntegrationTest; +import com.vladmihalcea.hibernate.type.util.Configuration; +import org.hibernate.annotations.Type; +import org.junit.Test; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.io.Serializable; +import java.math.BigDecimal; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + * @author Jan-Willem Gmelig Meyling + */ +public class PostgreSQLJsonBinaryTypeConfigurationTest extends AbstractPostgreSQLIntegrationTest { + + @Override + protected Class[] entities() { + return new Class[]{ + Event.class, + }; + } + + @Override + public void init() { + System.setProperty( + Configuration.PROPERTIES_FILE_PATH, + "PostgreSQLJsonBinaryTypeConfigurationTest.properties" + ); + super.init(); + } + + @Override + public void destroy() { + super.destroy(); + System.getProperties().remove(Configuration.PROPERTIES_FILE_PATH); + } + + @Test + public void test() { + doInJPA(entityManager -> { + Location location = new Location(); + location.setCountry("Romania"); + location.setCity("Cluj-Napoca"); + location.setReference(BigDecimal.valueOf(2.25262562526626D)); + + Event event = new Event(); + event.setId(1L); + event.setLocation(location); + entityManager.persist(event); + }); + + doInJPA(entityManager -> { + Event event = entityManager.find(Event.class, 1L); + assertEquals("2.25", event.getLocation().getReference().toString()); + }); + } + + @Entity(name = "Event") + @Table(name = "event") + public static class Event extends BaseEntity { + + @Type(type = "jsonb") + @Column(columnDefinition = "jsonb") + private Location location; + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + } + + public static class Location implements Serializable { + + private String country; + + private String city; + + private BigDecimal reference; + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public BigDecimal getReference() { + return reference; + } + + public void setReference(BigDecimal reference) { + this.reference = reference; + } + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonbUtilTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonbUtilTest.java new file mode 100644 index 000000000..90cd784be --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/jsonp/internal/JsonbUtilTest.java @@ -0,0 +1,168 @@ +package com.vladmihalcea.hibernate.type.jsonp.internal; + +import org.junit.Test; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Currency; +import java.util.List; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; + +public class JsonbUtilTest { + + @Test + public void cloneDeserializeStepErrorTest() { + MyEntity entity = new MyEntity(); + + entity.setValue("some value"); + entity.setPojos(Arrays.asList( + createMyPojo("first value", MyType.A, "1.1", createOtherPojo("USD")), + createMyPojo("second value", MyType.B, "1.2", createOtherPojo("BRL")) + )); + + MyEntity clone = JsonbUtil.clone(entity); + assertEquals(clone, entity); + + List clonePojos = JsonbUtil.clone(entity.getPojos()); + assertEquals(clonePojos, entity.getPojos()); + } + + private MyPojo createMyPojo(String value, MyType myType, String number, OtherPojo otherPojo) { + MyPojo myPojo = new MyPojo(); + myPojo.setValue(value); + myPojo.setType(myType); + myPojo.setNumber(new BigDecimal(number)); + myPojo.setOtherPojo(otherPojo); + return myPojo; + } + + private OtherPojo createOtherPojo(String currency) { + OtherPojo otherPojo = new OtherPojo(); + otherPojo.setCurrency(currency); + return otherPojo; + } + + public static class MyEntity { + private String value; + private List pojos; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getPojos() { + return pojos; + } + + public void setPojos(List pojos) { + this.pojos = pojos; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MyEntity myEntity = (MyEntity) o; + return Objects.equals(value, myEntity.value) && + Objects.equals(pojos, myEntity.pojos); + } + + @Override + public int hashCode() { + return Objects.hash(value, pojos); + } + } + + public static class MyPojo implements Serializable { + private String value; + private MyType type; + private BigDecimal number; + private OtherPojo otherPojo; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public MyType getType() { + return type; + } + + public void setType(MyType type) { + this.type = type; + } + + public BigDecimal getNumber() { + return number; + } + + public void setNumber(BigDecimal number) { + this.number = number; + } + + public OtherPojo getOtherPojo() { + return otherPojo; + } + + public void setOtherPojo(OtherPojo otherPojo) { + this.otherPojo = otherPojo; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MyPojo myPojo = (MyPojo) o; + return Objects.equals(value, myPojo.value) && + type == myPojo.type && + Objects.equals(number, myPojo.number) && + Objects.equals(otherPojo, myPojo.otherPojo); + } + + @Override + public int hashCode() { + return Objects.hash(value, type, number, otherPojo); + } + } + + public enum MyType { + A, B, C + } + + public static class OtherPojo implements Serializable { + private String currency; + + public String getCurrency() { + return currency; + } + + public void setCurrency(String currency) { + this.currency = currency; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OtherPojo otherPojo = (OtherPojo) o; + return Objects.equals(currency, otherPojo.currency); + } + + @Override + public int hashCode() { + return Objects.hash(currency); + } + } + +} \ No newline at end of file diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/model/BaseEntity.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/model/BaseEntity.java index e88fc6404..ac427d68c 100644 --- a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/model/BaseEntity.java +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/model/BaseEntity.java @@ -27,6 +27,10 @@ @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class), @TypeDef(name = "jsonb-node", typeClass = JsonNodeBinaryType.class), @TypeDef(name = "json-node", typeClass = JsonNodeStringType.class), + @TypeDef(name = "json-p", typeClass = com.vladmihalcea.hibernate.type.jsonp.JsonStringType.class), + @TypeDef(name = "jsonb-p", typeClass = com.vladmihalcea.hibernate.type.jsonp.JsonBinaryType.class), + @TypeDef(name = "jsonb-p-value", typeClass = com.vladmihalcea.hibernate.type.jsonp.JsonNodeBinaryType.class), + @TypeDef(name = "json-p-value", typeClass = com.vladmihalcea.hibernate.type.jsonp.JsonNodeStringType.class), }) @MappedSuperclass public class BaseEntity {