Skip to content

Commit a331270

Browse files
committed
improve SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS feature
- don't require session or mapping for ids mapped with property access - add a testcase - improve documentation
1 parent c119de4 commit a331270

File tree

3 files changed

+85
-5
lines changed

3 files changed

+85
-5
lines changed

hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/Hibernate5Module.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,19 @@ public enum Feature {
3131
USE_TRANSIENT_ANNOTATION(true),
3232

3333
/**
34-
* If FORCE_LAZY_LOADING is false lazy-loaded object should be serialized as map IdentifierName=>IdentifierValue
35-
* instead of null (true); or serialized as nulls (false)
34+
* If FORCE_LAZY_LOADING is false, this feature serializes uninitialized lazy loading proxies as
35+
* <code>{"identifierName":"identifierValue"}</code> rather than <code>null</code>.
3636
* <p>
37-
* Default value is false.
37+
* Default value is false.
38+
* <p>
39+
* Note that the name of the identifier property can only be determined if
40+
* <ul>
41+
* <li>the {@link Mapping} is provided to the Hibernate5Module, or </li>
42+
* <li>the persistence context that loaded the proxy has not yet been closed, or</li>
43+
* <li>the id property is mapped with property access (for instance because the {@code @Id}
44+
* annotation is applied to a method rather than a field)</li>
45+
* </ul>
46+
* Otherwise, the entity name will be used instead.
3847
*/
3948
SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS(false),
4049

hibernate5/src/main/java/com/fasterxml/jackson/datatype/hibernate5/HibernateProxySerializer.java

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.fasterxml.jackson.datatype.hibernate5;
22

3+
import java.beans.Introspector;
34
import java.io.IOException;
5+
import java.lang.reflect.Field;
6+
import java.lang.reflect.Method;
47
import java.util.HashMap;
58

69
import com.fasterxml.jackson.core.JsonGenerator;
@@ -18,6 +21,7 @@
1821
import org.hibernate.engine.spi.SessionImplementor;
1922
import org.hibernate.proxy.HibernateProxy;
2023
import org.hibernate.proxy.LazyInitializer;
24+
import org.hibernate.proxy.pojo.BasicLazyInitializer;
2125

2226
/**
2327
* Serializer to use for values proxied using {@link org.hibernate.proxy.HibernateProxy}.
@@ -174,15 +178,18 @@ protected Object findProxied(HibernateProxy proxy)
174178
LazyInitializer init = proxy.getHibernateLazyInitializer();
175179
if (!_forceLazyLoading && init.isUninitialized()) {
176180
if (_serializeIdentifier) {
177-
final String idName;
181+
String idName;
178182
if (_mapping != null) {
179183
idName = _mapping.getIdentifierPropertyName(init.getEntityName());
180184
} else {
181185
final SessionImplementor session = init.getSession();
182186
if (session != null) {
183187
idName = session.getFactory().getIdentifierPropertyName(init.getEntityName());
184188
} else {
185-
idName = init.getEntityName();
189+
idName = ProxyReader.getIdentifierPropertyName(init);
190+
if (idName == null) {
191+
idName = init.getEntityName();
192+
}
186193
}
187194
}
188195
final Object idValue = init.getIdentifier();
@@ -194,4 +201,45 @@ protected Object findProxied(HibernateProxy proxy)
194201
}
195202
return init.getImplementation();
196203
}
204+
205+
/**
206+
* Inspects a Hibernate proxy to try and determine the name of the identifier property
207+
* (Hibernate proxies know the getter of the identifier property because it receives special
208+
* treatment in the invocation handler). Alas, the field storing the method reference is
209+
* private and has no getter, so we must resort to ugly reflection hacks to read its value ...
210+
*/
211+
protected static class ProxyReader {
212+
213+
// static final so the JVM can inline the lookup
214+
private static final Field getIdentifierMethodField;
215+
216+
static {
217+
try {
218+
getIdentifierMethodField = BasicLazyInitializer.class.getDeclaredField("getIdentifierMethod");
219+
getIdentifierMethodField.setAccessible(true);
220+
} catch (Exception e) {
221+
// should never happen: the field exists in all versions of hibernate 4 and 5
222+
throw new RuntimeException(e);
223+
}
224+
}
225+
226+
/**
227+
* @return the name of the identifier property, or null if the name could not be determined
228+
*/
229+
static String getIdentifierPropertyName(LazyInitializer init) {
230+
try {
231+
Method idGetter = (Method) getIdentifierMethodField.get(init);
232+
if (idGetter == null) {
233+
return null;
234+
}
235+
String name = idGetter.getName();
236+
if (name.startsWith("get")) {
237+
name = Introspector.decapitalize(name.substring(3));
238+
}
239+
return name;
240+
} catch (IllegalAccessException e) {
241+
throw new RuntimeException(e);
242+
}
243+
}
244+
}
197245
}

hibernate5/src/test/java/com/fasterxml/jackson/datatype/hibernate5/LazyLoadingTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import javax.persistence.EntityManagerFactory;
77
import javax.persistence.Persistence;
88

9+
import com.fasterxml.jackson.core.JsonProcessingException;
910
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module.Feature;
1012
import com.fasterxml.jackson.datatype.hibernate5.data.Customer;
1113
import com.fasterxml.jackson.datatype.hibernate5.data.Payment;
1214

@@ -57,4 +59,25 @@ public void testGetCustomerJson() throws Exception
5759
emf.close();
5860
}
5961
}
62+
63+
@Test
64+
public void testSerializeIdentifierFeature() throws JsonProcessingException {
65+
Hibernate5Module module = new Hibernate5Module();
66+
module.enable(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
67+
ObjectMapper objectMapper = new ObjectMapper().registerModule(module);
68+
69+
EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnit");
70+
try {
71+
EntityManager em = emf.createEntityManager();
72+
Customer customerRef = em.getReference(Customer.class, 103);
73+
em.close();
74+
assertFalse(Hibernate.isInitialized(customerRef));
75+
76+
String json = objectMapper.writeValueAsString(customerRef);
77+
assertFalse(Hibernate.isInitialized(customerRef));
78+
assertEquals("{\"customerNumber\":103}", json);
79+
} finally {
80+
emf.close();
81+
}
82+
}
6083
}

0 commit comments

Comments
 (0)