From bfab823ad859d7bb4b2d0742a868881f743f42f0 Mon Sep 17 00:00:00 2001 From: Victor Noel Date: Tue, 28 Sep 2021 10:30:20 +0200 Subject: [PATCH] Avoid the unnecessary UPDATE for JsonNode entity mappings #348 --- .../type/util/ObjectMapperJsonSerializer.java | 5 + .../type/util/ObjectMapperWrapper.java | 5 +- .../util/ObjectMapperJsonSerializerTest.java | 37 +++--- .../type/util/ObjectMapperJsonSerializer.java | 5 + .../type/util/ObjectMapperWrapper.java | 5 +- .../type/json/MySQLJsonNodePropertyTest.java | 123 ++++++++++++++++++ .../util/ObjectMapperJsonSerializerTest.java | 37 +++--- .../type/util/ObjectMapperJsonSerializer.java | 5 + .../type/util/ObjectMapperWrapper.java | 5 +- .../type/json/MySQLJsonNodePropertyTest.java | 123 ++++++++++++++++++ .../util/ObjectMapperJsonSerializerTest.java | 29 +++-- .../type/util/ObjectMapperJsonSerializer.java | 11 +- .../type/util/ObjectMapperWrapper.java | 5 +- .../type/json/MySQLJsonNodePropertyTest.java | 114 ++++++++++++++++ .../util/ObjectMapperJsonSerializerTest.java | 29 +++-- .../type/util/ObjectMapperJsonSerializer.java | 5 + .../type/util/ObjectMapperWrapper.java | 5 +- .../type/json/MySQLJsonNodePropertyTest.java | 110 ++++++++++++++++ .../util/ObjectMapperJsonSerializerTest.java | 37 +++--- 19 files changed, 615 insertions(+), 80 deletions(-) create mode 100644 hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java create mode 100644 hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java create mode 100644 hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java create mode 100644 hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java diff --git a/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java b/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java index 7bb568db3..c29d9784f 100644 --- a/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java +++ b/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java @@ -1,6 +1,7 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.type.TypeFactory; import org.hibernate.internal.util.SerializationHelper; import org.hibernate.type.SerializationException; @@ -22,6 +23,10 @@ public ObjectMapperJsonSerializer(ObjectMapperWrapper objectMapperWrapper) { @Override public T clone(T object) { + if (object instanceof JsonNode) { + return (T) ((JsonNode) object).deepCopy(); + } + if (object instanceof Collection) { Object firstElement = findFirstNonNullElement((Collection) object); if (firstElement != null && !(firstElement instanceof Serializable)) { diff --git a/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java b/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java index 75c4d6d37..a60e140a1 100644 --- a/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java +++ b/hibernate-types-4/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java @@ -20,14 +20,15 @@ public class ObjectMapperWrapper { private final ObjectMapper objectMapper; - private JsonSerializer jsonSerializer = new ObjectMapperJsonSerializer(this); + private JsonSerializer jsonSerializer; public ObjectMapperWrapper() { - this.objectMapper = new ObjectMapper().findAndRegisterModules(); + this(new ObjectMapper().findAndRegisterModules()); } public ObjectMapperWrapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; + this.jsonSerializer = new ObjectMapperJsonSerializer(this); } public void setJsonSerializer(JsonSerializer jsonSerializer) { diff --git a/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java b/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java index 22fc18254..e9138e360 100644 --- a/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java +++ b/hibernate-types-4/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java @@ -1,26 +1,23 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.annotation.JsonProperty; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; - -import com.google.common.base.Objects; import com.google.common.collect.Lists; import org.hibernate.annotations.Type; import org.junit.Test; import javax.persistence.Column; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.math.BigDecimal; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; public class ObjectMapperJsonSerializerTest { - private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(new ObjectMapperWrapper()); + private ObjectMapperWrapper mapper = new ObjectMapperWrapper(); + + private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(mapper); @Test public void should_clone_serializable_object() { @@ -125,8 +122,8 @@ public void should_clone_serializable_complex_object_with_serializable_nested_ob Map> map = new LinkedHashMap>(); map.put("key1", Lists.newArrayList(new SerializableObject("name1"))); map.put("key2", Lists.newArrayList( - new SerializableObject("name2"), - new SerializableObject("name3") + new SerializableObject("name2"), + new SerializableObject("name3") )); Object original = new SerializableComplexObject(map); Object cloned = serializer.clone(original); @@ -139,8 +136,8 @@ public void should_clone_serializable_complex_object_with_non_serializable_neste Map> map = new LinkedHashMap>(); map.put("key1", Lists.newArrayList(new NonSerializableObject("name1"))); map.put("key2", Lists.newArrayList( - new NonSerializableObject("name2"), - new NonSerializableObject("name3") + new NonSerializableObject("name2"), + new NonSerializableObject("name3") )); Object original = new SerializableComplexObjectWithNonSerializableNestedObject(map); Object cloned = serializer.clone(original); @@ -148,6 +145,16 @@ public void should_clone_serializable_complex_object_with_non_serializable_neste assertNotSame(original, cloned); } + @Test + public void should_clone_jsonnode() { + Object original = mapper.getObjectMapper().createArrayNode() + .add(BigDecimal.ONE) + .add(1.0) + .add("string"); + Object cloned = serializer.clone(original); + assertEquals(original, cloned); + assertNotSame(original, cloned); + } private static class SerializableObject implements Serializable { private final String value; diff --git a/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java b/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java index 7bb568db3..c29d9784f 100644 --- a/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java +++ b/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java @@ -1,6 +1,7 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.type.TypeFactory; import org.hibernate.internal.util.SerializationHelper; import org.hibernate.type.SerializationException; @@ -22,6 +23,10 @@ public ObjectMapperJsonSerializer(ObjectMapperWrapper objectMapperWrapper) { @Override public T clone(T object) { + if (object instanceof JsonNode) { + return (T) ((JsonNode) object).deepCopy(); + } + if (object instanceof Collection) { Object firstElement = findFirstNonNullElement((Collection) object); if (firstElement != null && !(firstElement instanceof Serializable)) { diff --git a/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java b/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java index 75c4d6d37..a60e140a1 100644 --- a/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java +++ b/hibernate-types-43/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java @@ -20,14 +20,15 @@ public class ObjectMapperWrapper { private final ObjectMapper objectMapper; - private JsonSerializer jsonSerializer = new ObjectMapperJsonSerializer(this); + private JsonSerializer jsonSerializer; public ObjectMapperWrapper() { - this.objectMapper = new ObjectMapper().findAndRegisterModules(); + this(new ObjectMapper().findAndRegisterModules()); } public ObjectMapperWrapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; + this.jsonSerializer = new ObjectMapperJsonSerializer(this); } public void setJsonSerializer(JsonSerializer jsonSerializer) { diff --git a/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java new file mode 100644 index 000000000..5221f1aa0 --- /dev/null +++ b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java @@ -0,0 +1,123 @@ +package com.vladmihalcea.hibernate.type.json; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vladmihalcea.hibernate.type.util.AbstractMySQLIntegrationTest; +import com.vladmihalcea.hibernate.type.util.transaction.JPATransactionFunction; +import net.ttddyy.dsproxy.QueryCount; +import net.ttddyy.dsproxy.QueryCountHolder; +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.assertEquals; + +/** + * @author Victor Noël + */ +public class MySQLJsonNodePropertyTest extends AbstractMySQLIntegrationTest { + + private final ObjectMapper mapper = newMapper(); + + @Override + protected Class[] entities() { + return new Class[]{ + Book.class + }; + } + + + @Override + protected void afterInit() { + doInJPA(new JPATransactionFunction() { + + @Override + public Void apply(EntityManager entityManager) { + try { + entityManager.persist( + new Book() + .setIsbn("978-9730228236") + .setProperties(mapper.readTree("{\"field\": 0.05}")) + ); + } catch (Exception e) { + throw new IllegalStateException(e); + } + + return null; + } + }); + } + + @Test + public void test() { + QueryCountHolder.clear(); + + doInJPA(new JPATransactionFunction() { + + @Override + public Void apply(EntityManager entityManager) { + Book book = (Book) entityManager.unwrap(Session.class) + .bySimpleNaturalId(Book.class) + .load("978-9730228236"); + assertEquals(0.05, book.getProperties().get("field").asDouble(), 0.0); + + return null; + } + }); + + QueryCount queryCount = QueryCountHolder.getGrandTotal(); + assertEquals(0, queryCount.getUpdate()); + } + + public static class MyJsonType extends JsonType { + public MyJsonType() { + super(newMapper()); + } + } + + @Entity(name = "Book") + @Table(name = "book") + @TypeDef(name = "json", typeClass = MyJsonType.class) + public static class Book { + + @Id + @GeneratedValue + private Long id; + + @NaturalId + private String isbn; + + @Type(type = "json") + @Column(columnDefinition = "json") + private JsonNode properties; + + public String getIsbn() { + return isbn; + } + + public Book setIsbn(String isbn) { + this.isbn = isbn; + return this; + } + + public JsonNode getProperties() { + return properties; + } + + public Book setProperties(JsonNode properties) { + this.properties = properties; + return this; + } + } + + private static ObjectMapper newMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + return mapper; + } +} diff --git a/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java index 22fc18254..e9138e360 100644 --- a/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java +++ b/hibernate-types-43/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java @@ -1,26 +1,23 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.annotation.JsonProperty; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; - -import com.google.common.base.Objects; import com.google.common.collect.Lists; import org.hibernate.annotations.Type; import org.junit.Test; import javax.persistence.Column; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.math.BigDecimal; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; public class ObjectMapperJsonSerializerTest { - private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(new ObjectMapperWrapper()); + private ObjectMapperWrapper mapper = new ObjectMapperWrapper(); + + private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(mapper); @Test public void should_clone_serializable_object() { @@ -125,8 +122,8 @@ public void should_clone_serializable_complex_object_with_serializable_nested_ob Map> map = new LinkedHashMap>(); map.put("key1", Lists.newArrayList(new SerializableObject("name1"))); map.put("key2", Lists.newArrayList( - new SerializableObject("name2"), - new SerializableObject("name3") + new SerializableObject("name2"), + new SerializableObject("name3") )); Object original = new SerializableComplexObject(map); Object cloned = serializer.clone(original); @@ -139,8 +136,8 @@ public void should_clone_serializable_complex_object_with_non_serializable_neste Map> map = new LinkedHashMap>(); map.put("key1", Lists.newArrayList(new NonSerializableObject("name1"))); map.put("key2", Lists.newArrayList( - new NonSerializableObject("name2"), - new NonSerializableObject("name3") + new NonSerializableObject("name2"), + new NonSerializableObject("name3") )); Object original = new SerializableComplexObjectWithNonSerializableNestedObject(map); Object cloned = serializer.clone(original); @@ -148,6 +145,16 @@ public void should_clone_serializable_complex_object_with_non_serializable_neste assertNotSame(original, cloned); } + @Test + public void should_clone_jsonnode() { + Object original = mapper.getObjectMapper().createArrayNode() + .add(BigDecimal.ONE) + .add(1.0) + .add("string"); + Object cloned = serializer.clone(original); + assertEquals(original, cloned); + assertNotSame(original, cloned); + } private static class SerializableObject implements Serializable { private final String value; diff --git a/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java b/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java index 7bb568db3..c29d9784f 100644 --- a/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java +++ b/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java @@ -1,6 +1,7 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.type.TypeFactory; import org.hibernate.internal.util.SerializationHelper; import org.hibernate.type.SerializationException; @@ -22,6 +23,10 @@ public ObjectMapperJsonSerializer(ObjectMapperWrapper objectMapperWrapper) { @Override public T clone(T object) { + if (object instanceof JsonNode) { + return (T) ((JsonNode) object).deepCopy(); + } + if (object instanceof Collection) { Object firstElement = findFirstNonNullElement((Collection) object); if (firstElement != null && !(firstElement instanceof Serializable)) { diff --git a/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java b/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java index 75c4d6d37..a60e140a1 100644 --- a/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java +++ b/hibernate-types-5/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java @@ -20,14 +20,15 @@ public class ObjectMapperWrapper { private final ObjectMapper objectMapper; - private JsonSerializer jsonSerializer = new ObjectMapperJsonSerializer(this); + private JsonSerializer jsonSerializer; public ObjectMapperWrapper() { - this.objectMapper = new ObjectMapper().findAndRegisterModules(); + this(new ObjectMapper().findAndRegisterModules()); } public ObjectMapperWrapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; + this.jsonSerializer = new ObjectMapperJsonSerializer(this); } public void setJsonSerializer(JsonSerializer jsonSerializer) { diff --git a/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java new file mode 100644 index 000000000..9261beb5d --- /dev/null +++ b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java @@ -0,0 +1,123 @@ +package com.vladmihalcea.hibernate.type.json; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vladmihalcea.hibernate.type.util.AbstractMySQLIntegrationTest; +import com.vladmihalcea.hibernate.type.util.transaction.JPATransactionFunction; +import net.ttddyy.dsproxy.QueryCount; +import net.ttddyy.dsproxy.QueryCountHolder; +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.assertEquals; + +/** + * @author Victor Noël + */ +public class MySQLJsonNodePropertyTest extends AbstractMySQLIntegrationTest { + + private final ObjectMapper mapper = newMapper(); + + @Override + protected Class[] entities() { + return new Class[]{ + Book.class + }; + } + + + @Override + protected void afterInit() { + doInJPA(new JPATransactionFunction() { + + @Override + public Void apply(EntityManager entityManager) { + try { + entityManager.persist( + new Book() + .setIsbn("978-9730228236") + .setProperties(mapper.readTree("{\"field\": 0.05}")) + ); + } catch (Exception e) { + throw new IllegalStateException(e); + } + + return null; + } + }); + } + + @Test + public void test() { + QueryCountHolder.clear(); + + doInJPA(new JPATransactionFunction() { + + @Override + public Void apply(EntityManager entityManager) { + Book book = entityManager.unwrap(Session.class) + .bySimpleNaturalId(Book.class) + .load("978-9730228236"); + assertEquals(0.05, book.getProperties().get("field").asDouble(), 0.0); + + return null; + } + }); + + QueryCount queryCount = QueryCountHolder.getGrandTotal(); + assertEquals(0, queryCount.getUpdate()); + } + + public static class MyJsonType extends JsonType { + public MyJsonType() { + super(newMapper()); + } + } + + @Entity(name = "Book") + @Table(name = "book") + @TypeDef(name = "json", typeClass = MyJsonType.class) + public static class Book { + + @Id + @GeneratedValue + private Long id; + + @NaturalId + private String isbn; + + @Type(type = "json") + @Column(columnDefinition = "json") + private JsonNode properties; + + public String getIsbn() { + return isbn; + } + + public Book setIsbn(String isbn) { + this.isbn = isbn; + return this; + } + + public JsonNode getProperties() { + return properties; + } + + public Book setProperties(JsonNode properties) { + this.properties = properties; + return this; + } + } + + private static ObjectMapper newMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + return mapper; + } +} diff --git a/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java index 22fc18254..158e039db 100644 --- a/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java +++ b/hibernate-types-5/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java @@ -1,26 +1,23 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.annotation.JsonProperty; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; - -import com.google.common.base.Objects; import com.google.common.collect.Lists; import org.hibernate.annotations.Type; import org.junit.Test; import javax.persistence.Column; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.math.BigDecimal; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; public class ObjectMapperJsonSerializerTest { - private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(new ObjectMapperWrapper()); + private ObjectMapperWrapper mapper = new ObjectMapperWrapper(); + + private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(mapper); @Test public void should_clone_serializable_object() { @@ -148,6 +145,16 @@ public void should_clone_serializable_complex_object_with_non_serializable_neste assertNotSame(original, cloned); } + @Test + public void should_clone_jsonnode() { + Object original = mapper.getObjectMapper().createArrayNode() + .add(BigDecimal.ONE) + .add(1.0) + .add("string"); + Object cloned = serializer.clone(original); + assertEquals(original, cloned); + assertNotSame(original, cloned); + } private static class SerializableObject implements Serializable { private final String value; diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java index a7d5bcd1d..673bfb385 100644 --- a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java @@ -1,6 +1,7 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.type.TypeFactory; import org.hibernate.internal.util.SerializationHelper; import org.hibernate.type.SerializationException; @@ -22,6 +23,10 @@ public ObjectMapperJsonSerializer(ObjectMapperWrapper objectMapperWrapper) { @Override public T clone(T object) { + if (object instanceof JsonNode) { + return (T) ((JsonNode) object).deepCopy(); + } + if (object instanceof Collection) { Object firstElement = findFirstNonNullElement((Collection) object); if (firstElement != null && !(firstElement instanceof Serializable)) { @@ -38,17 +43,17 @@ public T clone(T object) { if (!(key instanceof Serializable) || !(value instanceof Serializable)) { JavaType type = TypeFactory.defaultInstance().constructParametricType(object.getClass(), key.getClass(), value.getClass()); return (T) objectMapperWrapper.fromBytes(objectMapperWrapper.toBytes(object), type); - } } } + } if (object instanceof Serializable) { try { return (T) SerializationHelper.clone((Serializable) object); } catch (SerializationException e) { //it is possible that object itself implements java.io.Serializable, but underlying structure does not //in this case we switch to the other JSON marshaling strategy which doesn't use the Java serialization - return jsonClone(object); - } + return jsonClone(object); + } } else { return jsonClone(object); } diff --git a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java index 75c4d6d37..a60e140a1 100644 --- a/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java +++ b/hibernate-types-52/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java @@ -20,14 +20,15 @@ public class ObjectMapperWrapper { private final ObjectMapper objectMapper; - private JsonSerializer jsonSerializer = new ObjectMapperJsonSerializer(this); + private JsonSerializer jsonSerializer; public ObjectMapperWrapper() { - this.objectMapper = new ObjectMapper().findAndRegisterModules(); + this(new ObjectMapper().findAndRegisterModules()); } public ObjectMapperWrapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; + this.jsonSerializer = new ObjectMapperJsonSerializer(this); } public void setJsonSerializer(JsonSerializer jsonSerializer) { diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java new file mode 100644 index 000000000..6561f6444 --- /dev/null +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java @@ -0,0 +1,114 @@ +package com.vladmihalcea.hibernate.type.json; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vladmihalcea.hibernate.type.util.AbstractMySQLIntegrationTest; +import net.ttddyy.dsproxy.QueryCount; +import net.ttddyy.dsproxy.QueryCountHolder; +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.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import static org.junit.Assert.assertEquals; + +/** + * @author Victor Noël + */ +public class MySQLJsonNodePropertyTest extends AbstractMySQLIntegrationTest { + + private final ObjectMapper mapper = newMapper(); + + @Override + protected Class[] entities() { + return new Class[]{ + Book.class + }; + } + + @Override + protected void afterInit() { + doInJPA(entityManager -> { + try { + entityManager.persist( + new Book() + .setIsbn("978-9730228236") + .setProperties(mapper.readTree("{\"field\": 0.05}")) + ); + } catch (JsonProcessingException e) { + throw new IllegalStateException(e); + } + }); + } + + @Test + public void test() { + QueryCountHolder.clear(); + + doInJPA(entityManager -> { + Book book = entityManager.unwrap(Session.class) + .bySimpleNaturalId(Book.class) + .load("978-9730228236"); + assertEquals(0.05, book.getProperties().get("field").asDouble(), 0.0); + }); + + QueryCount queryCount = QueryCountHolder.getGrandTotal(); + assertEquals(0, queryCount.getUpdate()); + } + + public static class MyJsonType extends JsonType { + public MyJsonType() { + super(newMapper()); + } + } + + @Entity(name = "Book") + @Table(name = "book") + @TypeDef(name = "json", typeClass = MyJsonType.class) + public static class Book { + + @Id + @GeneratedValue + private Long id; + + @NaturalId + private String isbn; + + @Type(type = "json") + @Column(columnDefinition = "json") + private JsonNode properties; + + public String getIsbn() { + return isbn; + } + + public Book setIsbn(String isbn) { + this.isbn = isbn; + return this; + } + + public JsonNode getProperties() { + return properties; + } + + public Book setProperties(JsonNode properties) { + this.properties = properties; + return this; + } + } + + private static ObjectMapper newMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + return mapper; + } +} diff --git a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java index 395acd0c8..ad85fe489 100644 --- a/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java +++ b/hibernate-types-52/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java @@ -1,26 +1,23 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.annotation.JsonProperty; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; - -import com.google.common.base.Objects; import com.google.common.collect.Lists; import org.hibernate.annotations.Type; import org.junit.Test; import javax.persistence.Column; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.math.BigDecimal; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; public class ObjectMapperJsonSerializerTest { - private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(new ObjectMapperWrapper()); + private ObjectMapperWrapper mapper = new ObjectMapperWrapper(); + + private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(mapper); @Test public void should_clone_serializable_object() { @@ -148,6 +145,16 @@ public void should_clone_serializable_complex_object_with_non_serializable_neste assertNotSame(original, cloned); } + @Test + public void should_clone_jsonnode() { + Object original = mapper.getObjectMapper().createArrayNode() + .add(BigDecimal.ONE) + .add(1.0) + .add("string"); + Object cloned = serializer.clone(original); + assertEquals(original, cloned); + assertNotSame(original, cloned); + } private static class SerializableObject implements Serializable { private final String value; diff --git a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java index a7d5bcd1d..acd1e46bd 100644 --- a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java +++ b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializer.java @@ -1,6 +1,7 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.type.TypeFactory; import org.hibernate.internal.util.SerializationHelper; import org.hibernate.type.SerializationException; @@ -22,6 +23,10 @@ public ObjectMapperJsonSerializer(ObjectMapperWrapper objectMapperWrapper) { @Override public T clone(T object) { + if (object instanceof JsonNode) { + return (T) ((JsonNode) object).deepCopy(); + } + if (object instanceof Collection) { Object firstElement = findFirstNonNullElement((Collection) object); if (firstElement != null && !(firstElement instanceof Serializable)) { diff --git a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java index 75c4d6d37..a60e140a1 100644 --- a/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java +++ b/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/util/ObjectMapperWrapper.java @@ -20,14 +20,15 @@ public class ObjectMapperWrapper { private final ObjectMapper objectMapper; - private JsonSerializer jsonSerializer = new ObjectMapperJsonSerializer(this); + private JsonSerializer jsonSerializer; public ObjectMapperWrapper() { - this.objectMapper = new ObjectMapper().findAndRegisterModules(); + this(new ObjectMapper().findAndRegisterModules()); } public ObjectMapperWrapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; + this.jsonSerializer = new ObjectMapperJsonSerializer(this); } public void setJsonSerializer(JsonSerializer jsonSerializer) { diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java new file mode 100644 index 000000000..145d514a1 --- /dev/null +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/json/MySQLJsonNodePropertyTest.java @@ -0,0 +1,110 @@ +package com.vladmihalcea.hibernate.type.json; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vladmihalcea.hibernate.type.util.AbstractMySQLIntegrationTest; +import net.ttddyy.dsproxy.QueryCount; +import net.ttddyy.dsproxy.QueryCountHolder; +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.assertEquals; + +/** + * @author Victor Noël + */ +public class MySQLJsonNodePropertyTest extends AbstractMySQLIntegrationTest { + + private final ObjectMapper mapper = newMapper(); + + @Override + protected Class[] entities() { + return new Class[]{ + Book.class + }; + } + + @Override + protected void afterInit() { + doInJPA(entityManager -> { + try { + entityManager.persist( + new Book() + .setIsbn("978-9730228236") + .setProperties(mapper.readTree("{\"field\": 0.05}")) + ); + } catch (JsonProcessingException e) { + throw new IllegalStateException(e); + } + }); + } + + @Test + public void test() { + QueryCountHolder.clear(); + + doInJPA(entityManager -> { + Book book = entityManager.unwrap(Session.class) + .bySimpleNaturalId(Book.class) + .load("978-9730228236"); + assertEquals(0.05, book.getProperties().get("field").asDouble(), 0.0); + }); + + QueryCount queryCount = QueryCountHolder.getGrandTotal(); + assertEquals(0, queryCount.getUpdate()); + } + + public static class MyJsonType extends JsonType { + public MyJsonType() { + super(newMapper()); + } + } + + @Entity(name = "Book") + @Table(name = "book") + @TypeDef(name = "json", typeClass = MyJsonType.class) + public static class Book { + + @Id + @GeneratedValue + private Long id; + + @NaturalId + private String isbn; + + @Type(type = "json") + @Column(columnDefinition = "json") + private JsonNode properties; + + public String getIsbn() { + return isbn; + } + + public Book setIsbn(String isbn) { + this.isbn = isbn; + return this; + } + + public JsonNode getProperties() { + return properties; + } + + public Book setProperties(JsonNode properties) { + this.properties = properties; + return this; + } + } + + private static ObjectMapper newMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true); + return mapper; + } +} diff --git a/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java index 395acd0c8..2a45bb79c 100644 --- a/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java +++ b/hibernate-types-55/src/test/java/com/vladmihalcea/hibernate/type/util/ObjectMapperJsonSerializerTest.java @@ -1,26 +1,23 @@ package com.vladmihalcea.hibernate.type.util; import com.fasterxml.jackson.annotation.JsonProperty; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; - -import com.google.common.base.Objects; import com.google.common.collect.Lists; import org.hibernate.annotations.Type; import org.junit.Test; import javax.persistence.Column; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.math.BigDecimal; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; public class ObjectMapperJsonSerializerTest { - private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(new ObjectMapperWrapper()); + private ObjectMapperWrapper mapper = new ObjectMapperWrapper(); + + private ObjectMapperJsonSerializer serializer = new ObjectMapperJsonSerializer(mapper); @Test public void should_clone_serializable_object() { @@ -125,8 +122,8 @@ public void should_clone_serializable_complex_object_with_serializable_nested_ob Map> map = new LinkedHashMap<>(); map.put("key1", Lists.newArrayList(new SerializableObject("name1"))); map.put("key2", Lists.newArrayList( - new SerializableObject("name2"), - new SerializableObject("name3") + new SerializableObject("name2"), + new SerializableObject("name3") )); Object original = new SerializableComplexObject(map); Object cloned = serializer.clone(original); @@ -139,8 +136,8 @@ public void should_clone_serializable_complex_object_with_non_serializable_neste Map> map = new LinkedHashMap<>(); map.put("key1", Lists.newArrayList(new NonSerializableObject("name1"))); map.put("key2", Lists.newArrayList( - new NonSerializableObject("name2"), - new NonSerializableObject("name3") + new NonSerializableObject("name2"), + new NonSerializableObject("name3") )); Object original = new SerializableComplexObjectWithNonSerializableNestedObject(map); Object cloned = serializer.clone(original); @@ -148,6 +145,16 @@ public void should_clone_serializable_complex_object_with_non_serializable_neste assertNotSame(original, cloned); } + @Test + public void should_clone_jsonnode() { + Object original = mapper.getObjectMapper().createArrayNode() + .add(BigDecimal.ONE) + .add(1.0) + .add("string"); + Object cloned = serializer.clone(original); + assertEquals(original, cloned); + assertNotSame(original, cloned); + } private static class SerializableObject implements Serializable { private final String value;