Skip to content

Commit 21ff609

Browse files
authored
Merge pull request #126 from mobreza/missing-as-null
Ignore missing entities when lazy loading is enabled
2 parents 523d2f5 + afea510 commit 21ff609

File tree

14 files changed

+377
-20
lines changed

14 files changed

+377
-20
lines changed

hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/Hibernate4Module.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,14 @@ public enum Feature {
7878
*
7979
* @since 2.8.2
8080
*/
81-
REPLACE_PERSISTENT_COLLECTIONS(false)
81+
REPLACE_PERSISTENT_COLLECTIONS(false),
82+
83+
/**
84+
* Using {@link #FORCE_LAZY_LOADING} may result in
85+
* `javax.persistence.EntityNotFoundException`. This flag configures Jackson to
86+
* ignore the error and serialize a `null`.
87+
*/
88+
WRITE_MISSING_ENTITIES_AS_NULL(false)
8289
;
8390

8491
final boolean _defaultState;

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import java.lang.reflect.Method;
77
import java.util.HashMap;
88

9+
import javax.persistence.EntityNotFoundException;
10+
911
import com.fasterxml.jackson.core.*;
1012
import com.fasterxml.jackson.databind.BeanProperty;
1113
import com.fasterxml.jackson.databind.JavaType;
@@ -43,6 +45,7 @@ public class HibernateProxySerializer
4345

4446
protected final boolean _forceLazyLoading;
4547
protected final boolean _serializeIdentifier;
48+
protected final boolean _nullMissingEntities;
4649
protected final Mapping _mapping;
4750

4851
/**
@@ -59,29 +62,34 @@ public class HibernateProxySerializer
5962

6063
public HibernateProxySerializer(boolean forceLazyLoading)
6164
{
62-
this(forceLazyLoading, false, null, null);
65+
this(forceLazyLoading, false, false, null, null);
6366
}
6467

6568
public HibernateProxySerializer(boolean forceLazyLoading, boolean serializeIdentifier) {
66-
this(forceLazyLoading, serializeIdentifier, null, null);
69+
this(forceLazyLoading, serializeIdentifier, false, null, null);
6770
}
6871

6972
public HibernateProxySerializer(boolean forceLazyLoading, boolean serializeIdentifier, Mapping mapping) {
70-
this(forceLazyLoading, serializeIdentifier, mapping, null);
73+
this(forceLazyLoading, serializeIdentifier, false, mapping, null);
74+
}
75+
76+
public HibernateProxySerializer(boolean forceLazyLoading, boolean serializeIdentifier, boolean nullMissingEntities, Mapping mapping) {
77+
this(forceLazyLoading, serializeIdentifier, nullMissingEntities, mapping, null);
7178
}
7279

73-
public HibernateProxySerializer(boolean forceLazyLoading, boolean serializeIdentifier, Mapping mapping,
80+
public HibernateProxySerializer(boolean forceLazyLoading, boolean serializeIdentifier, boolean nullMissingEntities, Mapping mapping,
7481
BeanProperty property) {
7582
_forceLazyLoading = forceLazyLoading;
7683
_serializeIdentifier = serializeIdentifier;
84+
_nullMissingEntities = nullMissingEntities;
7785
_mapping = mapping;
7886
_dynamicSerializers = PropertySerializerMap.emptyForProperties();
7987
_property = property;
8088
}
8189

8290
@Override
8391
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
84-
return new HibernateProxySerializer(this._forceLazyLoading, _serializeIdentifier,
92+
return new HibernateProxySerializer(this._forceLazyLoading, _serializeIdentifier, _nullMissingEntities,
8593
_mapping, property);
8694
}
8795

@@ -199,7 +207,15 @@ protected Object findProxied(HibernateProxy proxy)
199207
}
200208
return null;
201209
}
202-
return init.getImplementation();
210+
try {
211+
return init.getImplementation();
212+
} catch (EntityNotFoundException e) {
213+
if (_nullMissingEntities) {
214+
return null;
215+
} else {
216+
throw e;
217+
}
218+
}
203219
}
204220

205221
/**

hibernate4/src/main/java/com/fasterxml/jackson/datatype/hibernate4/HibernateSerializers.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class HibernateSerializers extends Serializers.Base
1313
{
1414
protected final boolean _forceLoading;
1515
protected final boolean _serializeIdentifiers;
16+
protected final boolean _nullMissingEntities;
1617
protected final Mapping _mapping;
1718

1819
public HibernateSerializers(int features) {
@@ -23,6 +24,7 @@ public HibernateSerializers(Mapping mapping, int features)
2324
{
2425
_forceLoading = Feature.FORCE_LAZY_LOADING.enabledIn(features);
2526
_serializeIdentifiers = Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS.enabledIn(features);
27+
_nullMissingEntities = Feature.WRITE_MISSING_ENTITIES_AS_NULL.enabledIn(features);
2628
_mapping = mapping;
2729
}
2830

@@ -32,7 +34,7 @@ public JsonSerializer<?> findSerializer(SerializationConfig config,
3234
{
3335
Class<?> raw = type.getRawClass();
3436
if (HibernateProxy.class.isAssignableFrom(raw)) {
35-
return new HibernateProxySerializer(_forceLoading, _serializeIdentifiers, _mapping);
37+
return new HibernateProxySerializer(_forceLoading, _serializeIdentifiers, _nullMissingEntities, _mapping);
3638
}
3739
return null;
3840
}

hibernate4/src/test/java/com/fasterxml/jackson/datatype/hibernate4/BaseTest.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,23 @@ protected BaseTest() { }
1111

1212
protected ObjectMapper mapperWithModule(boolean forceLazyLoading)
1313
{
14-
return new ObjectMapper().registerModule(hibernateModule(forceLazyLoading));
14+
return new ObjectMapper().registerModule(hibernateModule(forceLazyLoading, false));
1515
}
1616

17-
protected Hibernate4Module hibernateModule(boolean forceLazyLoading)
17+
protected ObjectMapper mapperWithModule(boolean forceLazyLoading, boolean nullMissingEntities)
18+
{
19+
return new ObjectMapper().registerModule(hibernateModule(forceLazyLoading, nullMissingEntities));
20+
}
21+
22+
protected Hibernate4Module hibernateModule(boolean forceLazyLoading) {
23+
return hibernateModule(forceLazyLoading, false);
24+
}
25+
26+
protected Hibernate4Module hibernateModule(boolean forceLazyLoading, boolean nullMissingEntities)
1827
{
1928
Hibernate4Module mod = new Hibernate4Module();
2029
mod.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING, forceLazyLoading);
30+
mod.configure(Hibernate4Module.Feature.WRITE_MISSING_ENTITIES_AS_NULL, nullMissingEntities);
2131
return mod;
2232
}
2333

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.fasterxml.jackson.datatype.hibernate4;
2+
3+
import java.util.Map;
4+
5+
import javax.persistence.EntityManager;
6+
import javax.persistence.EntityManagerFactory;
7+
import javax.persistence.Persistence;
8+
9+
import org.hibernate.Hibernate;
10+
11+
import com.fasterxml.jackson.databind.JsonMappingException;
12+
import com.fasterxml.jackson.databind.ObjectMapper;
13+
import com.fasterxml.jackson.datatype.hibernate4.data.Customer;
14+
15+
// [Issue#125]
16+
public class MissingEntitiesAsNullTest extends BaseTest {
17+
public void testMissingProductWhenMissing() throws Exception {
18+
EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnit");
19+
20+
try {
21+
EntityManager em = emf.createEntityManager();
22+
23+
// false -> no forcing of lazy loading
24+
ObjectMapper mapper = mapperWithModule(true);
25+
26+
Customer customer = em.find(Customer.class, 103);
27+
assertFalse(Hibernate.isInitialized(customer.getPayments()));
28+
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
29+
assertNull(customer.getMissingProductCode());
30+
assertNotNull(json);
31+
32+
Map<?, ?> stuff = mapper.readValue(json, Map.class);
33+
34+
assertNull(stuff.get("missingProductCode"));
35+
assertNull(stuff.get("missingProduct"));
36+
37+
} finally {
38+
emf.close();
39+
}
40+
}
41+
42+
public void testProductWithValidForeignKey() throws Exception {
43+
EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnit");
44+
45+
try {
46+
EntityManager em = emf.createEntityManager();
47+
48+
// false -> no forcing of lazy loading
49+
ObjectMapper mapper = mapperWithModule(true);
50+
51+
Customer customer = em.find(Customer.class, 500);
52+
assertFalse(Hibernate.isInitialized(customer.getPayments()));
53+
assertNotNull(customer.getMissingProductCode());
54+
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
55+
assertNotNull(json);
56+
57+
Map<?, ?> stuff = mapper.readValue(json, Map.class);
58+
59+
assertNotNull(stuff.get("missingProductCode"));
60+
assertNotNull(stuff.get("missingProduct"));
61+
62+
} finally {
63+
emf.close();
64+
}
65+
}
66+
67+
// caused by javax.persistence.EntityNotFoundException: Unable to find
68+
// com.fasterxml.jackson.datatype.hibernate4.data.Product with id X10_1678
69+
public void testExceptionWithInvalidForeignKey() throws Exception {
70+
EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnit");
71+
72+
try {
73+
EntityManager em = emf.createEntityManager();
74+
75+
// false -> no forcing of lazy loading
76+
ObjectMapper mapper = mapperWithModule(true);
77+
78+
Customer customer = em.find(Customer.class, 501);
79+
assertFalse(Hibernate.isInitialized(customer.getPayments()));
80+
81+
// javax.persistence.EntityNotFoundException thrown here
82+
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
83+
// JUnit 3.8
84+
fail("Expected EntityNotFoundException exception");
85+
86+
} catch (JsonMappingException e) {
87+
assertEquals("Unable to find com.fasterxml.jackson.datatype.hibernate4.data.Product with id X10_1678", e.getCause().getMessage());
88+
} finally {
89+
emf.close();
90+
}
91+
}
92+
93+
public void testWriteAsNullWithInvalidForeignKey() throws Exception {
94+
EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnit");
95+
96+
try {
97+
EntityManager em = emf.createEntityManager();
98+
99+
// false -> no forcing of lazy loading
100+
ObjectMapper mapper = mapperWithModule(true, true);
101+
102+
Customer customer = em.find(Customer.class, 501);
103+
assertFalse(Hibernate.isInitialized(customer.getPayments()));
104+
// javax.persistence.EntityNotFoundException thrown here
105+
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
106+
assertNotNull(json);
107+
108+
Map<?, ?> stuff = mapper.readValue(json, Map.class);
109+
110+
assertNotNull(stuff.get("missingProductCode"));
111+
assertNull(stuff.get("missingProduct"));
112+
113+
} finally {
114+
emf.close();
115+
}
116+
}
117+
}

hibernate4/src/test/java/com/fasterxml/jackson/datatype/hibernate4/data/Customer.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public class Customer implements java.io.Serializable
2525
private String postalCode;
2626
private String country;
2727
private Double creditLimit;
28+
private String missingProductCode;
29+
private Product missingProduct;
2830
private Set<Payment> payments = new HashSet<Payment>();
2931
private Set<Order> orders = new HashSet<Order>();
3032

@@ -195,4 +197,24 @@ public Set<Payment> getPayments() {
195197
public void setPayments(Set<Payment> payments) {
196198
this.payments = payments;
197199
}
200+
201+
@Column(name="missingProductCode")
202+
public String getMissingProductCode() {
203+
return missingProductCode;
204+
}
205+
206+
public void setMissingProductCode(String missingProductCode) {
207+
this.missingProductCode = missingProductCode;
208+
}
209+
210+
@ManyToOne(cascade = {}, fetch = FetchType.LAZY, optional = true)
211+
@JoinColumn(name = "missingProductCode", nullable = true, insertable = false, updatable = false)
212+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
213+
public Product getMissingProduct() {
214+
return missingProduct;
215+
}
216+
217+
public void setMissingProduct(Product missingProduct) {
218+
this.missingProduct = missingProduct;
219+
}
198220
}

hibernate4/src/test/resources/classicmodels.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ CREATE TABLE `classicmodels`.`Customer` (
4040
`country` varchar(50) NOT NULL,
4141
`salesRepEmployeeNumber` int(11) DEFAULT NULL,
4242
`creditLimit` double DEFAULT NULL,
43+
`missingProductCode` varchar(50) NULL,
4344
PRIMARY KEY (`customerNumber`)
4445
) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
4546
INSERT INTO `classicmodels`.`Customer` (`customerNumber`,`customerName`,`contactLastName`,`contactFirstName`,`phone`,`addressLine1`,`addressLine2`,`city`,`state`,`postalCode`,`country`,`salesRepEmployeeNumber`,`creditLimit`) VALUES
@@ -165,6 +166,9 @@ INSERT INTO `classicmodels`.`Customer` (`customerNumber`,`customerName`,`contact
165166
(489,'Double Decker Gift Stores, Ltd','Hardy','Thomas ','(171) 555-7555','120 Hanover Sq.',NULL,'London',NULL,'WA1 1DP','UK',1501,43300),
166167
(495,'Diecast Collectables','Franco','Valarie','6175552555','6251 Ingle Ln.',NULL,'Boston','MA','51003','USA',1188,85100),
167168
(496,'Kellys Gift Shop','Snowden','Tony','+64 9 5555500','Arenales 1938 3A',NULL,'Auckland ',NULL,'','New Zealand',1612,110000);
169+
INSERT INTO `classicmodels`.`Customer` (`customerNumber`,`customerName`,`contactLastName`,`contactFirstName`,`phone`,`addressLine1`,`addressLine2`,`city`,`state`,`postalCode`,`country`,`salesRepEmployeeNumber`,`creditLimit`, `missingProductCode`) VALUES
170+
(500,'Customer with valid product','Schmitt','Carine ','40.32.2555','54, rue Royale',NULL,'Nantes',NULL,'44000','France',1370,21000, 'S10_1678'),
171+
(501,'Customer with invalid product','Schmitt','Carine ','40.32.2555','54, rue Royale',NULL,'Nantes',NULL,'44000','France',1370,21000, 'X10_1678'),
168172

169173
DROP TABLE IF EXISTS `classicmodels`.`Employee`;
170174
CREATE TABLE `classicmodels`.`Employee` (

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,14 @@ public enum Feature {
7777
*
7878
* @since 2.8.2
7979
*/
80-
REPLACE_PERSISTENT_COLLECTIONS(false)
80+
REPLACE_PERSISTENT_COLLECTIONS(false),
81+
82+
/**
83+
* Using {@link #FORCE_LAZY_LOADING} may result in
84+
* `javax.persistence.EntityNotFoundException`. This flag configures Jackson to
85+
* ignore the error and serialize a `null`.
86+
*/
87+
WRITE_MISSING_ENTITIES_AS_NULL(false)
8188
;
8289

8390
final boolean _defaultState;

0 commit comments

Comments
 (0)