diff --git a/hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/Hibernate4Module.java b/hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/Hibernate4Module.java
index 2b43d88e..fc935dfb 100644
--- a/hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/Hibernate4Module.java
+++ b/hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/Hibernate4Module.java
@@ -31,10 +31,19 @@ public enum Feature {
USE_TRANSIENT_ANNOTATION(true),
/**
- * If FORCE_LAZY_LOADING is false lazy-loaded object should be serialized as map IdentifierName=>IdentifierValue
- * instead of null (true); or serialized as nulls (false)
+ * If FORCE_LAZY_LOADING is false, this feature serializes uninitialized lazy loading proxies as
+ * {"identifierName":"identifierValue"}
rather than null
.
*
- * Default value is false.
+ * Default value is false.
+ *
+ * Note that the name of the identifier property can only be determined if
+ *
+ * - the {@link Mapping} is provided to the Hibernate4Module, or
+ * - the persistence context that loaded the proxy has not yet been closed, or
+ * - the id property is mapped with property access (for instance because the {@code @Id}
+ * annotation is applied to a method rather than a field)
+ *
+ * Otherwise, the entity name will be used instead.
*/
SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS(false),
diff --git a/hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/HibernateProxySerializer.java b/hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/HibernateProxySerializer.java
index 5b850beb..229aec19 100644
--- a/hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/HibernateProxySerializer.java
+++ b/hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/HibernateProxySerializer.java
@@ -1,6 +1,9 @@
package com.fasterxml.jackson.datatype.hibernate4;
+import java.beans.Introspector;
import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.util.HashMap;
import com.fasterxml.jackson.core.*;
@@ -18,6 +21,7 @@
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
+import org.hibernate.proxy.pojo.BasicLazyInitializer;
/**
* Serializer to use for values proxied using {@link org.hibernate.proxy.HibernateProxy}.
@@ -174,7 +178,7 @@ protected Object findProxied(HibernateProxy proxy)
LazyInitializer init = proxy.getHibernateLazyInitializer();
if (!_forceLazyLoading && init.isUninitialized()) {
if (_serializeIdentifier) {
- final String idName;
+ String idName;
if (_mapping != null) {
idName = _mapping.getIdentifierPropertyName(init.getEntityName());
} else {
@@ -182,7 +186,10 @@ protected Object findProxied(HibernateProxy proxy)
if (session != null) {
idName = session.getFactory().getIdentifierPropertyName(init.getEntityName());
} else {
- idName = init.getEntityName();
+ idName = ProxyReader.getIdentifierPropertyName(init);
+ if (idName == null) {
+ idName = init.getEntityName();
+ }
}
}
final Object idValue = init.getIdentifier();
@@ -194,4 +201,45 @@ protected Object findProxied(HibernateProxy proxy)
}
return init.getImplementation();
}
+
+ /**
+ * Inspects a Hibernate proxy to try and determine the name of the identifier property
+ * (Hibernate proxies know the getter of the identifier property because it receives special
+ * treatment in the invocation handler). Alas, the field storing the method reference is
+ * private and has no getter, so we must resort to ugly reflection hacks to read its value ...
+ */
+ protected static class ProxyReader {
+
+ // static final so the JVM can inline the lookup
+ private static final Field getIdentifierMethodField;
+
+ static {
+ try {
+ getIdentifierMethodField = BasicLazyInitializer.class.getDeclaredField("getIdentifierMethod");
+ getIdentifierMethodField.setAccessible(true);
+ } catch (Exception e) {
+ // should never happen: the field exists in all versions of hibernate 4 and 5
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @return the name of the identifier property, or null if the name could not be determined
+ */
+ static String getIdentifierPropertyName(LazyInitializer init) {
+ try {
+ Method idGetter = (Method) getIdentifierMethodField.get(init);
+ if (idGetter == null) {
+ return null;
+ }
+ String name = idGetter.getName();
+ if (name.startsWith("get")) {
+ name = Introspector.decapitalize(name.substring(3));
+ }
+ return name;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
}
diff --git a/hibernate4/src/test/java/com/fasterxml/jackson/datatype/hibernate4/LazyLoadingTest.java b/hibernate4/src/test/java/com/fasterxml/jackson/datatype/hibernate4/LazyLoadingTest.java
index b45a1f5a..0a638dbf 100644
--- a/hibernate4/src/test/java/com/fasterxml/jackson/datatype/hibernate4/LazyLoadingTest.java
+++ b/hibernate4/src/test/java/com/fasterxml/jackson/datatype/hibernate4/LazyLoadingTest.java
@@ -6,7 +6,9 @@
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module.Feature;
import com.fasterxml.jackson.datatype.hibernate4.data.Customer;
import com.fasterxml.jackson.datatype.hibernate4.data.Payment;
@@ -54,4 +56,25 @@ public void testGetCustomerJson() throws Exception
emf.close();
}
}
+
+ @Test
+ public void testSerializeIdentifierFeature() throws JsonProcessingException {
+ Hibernate4Module module = new Hibernate4Module();
+ module.enable(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
+ ObjectMapper objectMapper = new ObjectMapper().registerModule(module);
+
+ EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnit");
+ try {
+ EntityManager em = emf.createEntityManager();
+ Customer customerRef = em.getReference(Customer.class, 103);
+ em.close();
+ assertFalse(Hibernate.isInitialized(customerRef));
+
+ String json = objectMapper.writeValueAsString(customerRef);
+ assertFalse(Hibernate.isInitialized(customerRef));
+ assertEquals("{\"customerNumber\":103}", json);
+ } finally {
+ emf.close();
+ }
+ }
}
diff --git a/hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/Hibernate5Module.java b/hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/Hibernate5Module.java
index 0d0b1798..f4d727c8 100644
--- a/hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/Hibernate5Module.java
+++ b/hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/Hibernate5Module.java
@@ -31,10 +31,19 @@ public enum Feature {
USE_TRANSIENT_ANNOTATION(true),
/**
- * If FORCE_LAZY_LOADING is false lazy-loaded object should be serialized as map IdentifierName=>IdentifierValue
- * instead of null (true); or serialized as nulls (false)
+ * If FORCE_LAZY_LOADING is false, this feature serializes uninitialized lazy loading proxies as
+ * {"identifierName":"identifierValue"}
rather than null
.
*
- * Default value is false.
+ * Default value is false.
+ *
+ * Note that the name of the identifier property can only be determined if
+ *
+ * - the {@link Mapping} is provided to the Hibernate5Module, or
+ * - the persistence context that loaded the proxy has not yet been closed, or
+ * - the id property is mapped with property access (for instance because the {@code @Id}
+ * annotation is applied to a method rather than a field)
+ *
+ * Otherwise, the entity name will be used instead.
*/
SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS(false),
diff --git a/hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/HibernateProxySerializer.java b/hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/HibernateProxySerializer.java
index b25086f8..d12f5931 100644
--- a/hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/HibernateProxySerializer.java
+++ b/hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/HibernateProxySerializer.java
@@ -1,6 +1,9 @@
package com.fasterxml.jackson.datatype.hibernate5;
+import java.beans.Introspector;
import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.util.HashMap;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -18,6 +21,7 @@
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
+import org.hibernate.proxy.pojo.BasicLazyInitializer;
/**
* Serializer to use for values proxied using {@link org.hibernate.proxy.HibernateProxy}.
@@ -174,7 +178,7 @@ protected Object findProxied(HibernateProxy proxy)
LazyInitializer init = proxy.getHibernateLazyInitializer();
if (!_forceLazyLoading && init.isUninitialized()) {
if (_serializeIdentifier) {
- final String idName;
+ String idName;
if (_mapping != null) {
idName = _mapping.getIdentifierPropertyName(init.getEntityName());
} else {
@@ -182,7 +186,10 @@ protected Object findProxied(HibernateProxy proxy)
if (session != null) {
idName = session.getFactory().getIdentifierPropertyName(init.getEntityName());
} else {
- idName = init.getEntityName();
+ idName = ProxyReader.getIdentifierPropertyName(init);
+ if (idName == null) {
+ idName = init.getEntityName();
+ }
}
}
final Object idValue = init.getIdentifier();
@@ -194,4 +201,45 @@ protected Object findProxied(HibernateProxy proxy)
}
return init.getImplementation();
}
+
+ /**
+ * Inspects a Hibernate proxy to try and determine the name of the identifier property
+ * (Hibernate proxies know the getter of the identifier property because it receives special
+ * treatment in the invocation handler). Alas, the field storing the method reference is
+ * private and has no getter, so we must resort to ugly reflection hacks to read its value ...
+ */
+ protected static class ProxyReader {
+
+ // static final so the JVM can inline the lookup
+ private static final Field getIdentifierMethodField;
+
+ static {
+ try {
+ getIdentifierMethodField = BasicLazyInitializer.class.getDeclaredField("getIdentifierMethod");
+ getIdentifierMethodField.setAccessible(true);
+ } catch (Exception e) {
+ // should never happen: the field exists in all versions of hibernate 4 and 5
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @return the name of the identifier property, or null if the name could not be determined
+ */
+ static String getIdentifierPropertyName(LazyInitializer init) {
+ try {
+ Method idGetter = (Method) getIdentifierMethodField.get(init);
+ if (idGetter == null) {
+ return null;
+ }
+ String name = idGetter.getName();
+ if (name.startsWith("get")) {
+ name = Introspector.decapitalize(name.substring(3));
+ }
+ return name;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
}
diff --git a/hibernate5/src/test/java/com/fasterxml/jackson/datatype/hibernate5/LazyLoadingTest.java b/hibernate5/src/test/java/com/fasterxml/jackson/datatype/hibernate5/LazyLoadingTest.java
index 6a930a6a..0f8d1b0a 100644
--- a/hibernate5/src/test/java/com/fasterxml/jackson/datatype/hibernate5/LazyLoadingTest.java
+++ b/hibernate5/src/test/java/com/fasterxml/jackson/datatype/hibernate5/LazyLoadingTest.java
@@ -6,7 +6,9 @@
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module.Feature;
import com.fasterxml.jackson.datatype.hibernate5.data.Customer;
import com.fasterxml.jackson.datatype.hibernate5.data.Payment;
@@ -57,4 +59,25 @@ public void testGetCustomerJson() throws Exception
emf.close();
}
}
+
+ @Test
+ public void testSerializeIdentifierFeature() throws JsonProcessingException {
+ Hibernate5Module module = new Hibernate5Module();
+ module.enable(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
+ ObjectMapper objectMapper = new ObjectMapper().registerModule(module);
+
+ EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnit");
+ try {
+ EntityManager em = emf.createEntityManager();
+ Customer customerRef = em.getReference(Customer.class, 103);
+ em.close();
+ assertFalse(Hibernate.isInitialized(customerRef));
+
+ String json = objectMapper.writeValueAsString(customerRef);
+ assertFalse(Hibernate.isInitialized(customerRef));
+ assertEquals("{\"customerNumber\":103}", json);
+ } finally {
+ emf.close();
+ }
+ }
}