Spring Data JPA Proxies for interface projection not serializable to Redis - spring

I'm attempting to store the results of a Repository query to a Redis cache, and running into the following error:
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `com.sun.proxy.$Proxy173` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"["java.util.ArrayList",[{"#class":"com.sun.proxy.$Proxy173","busDate":"2022-09-12","vendorMasterAccount":"emily#emilylark.com","vendor":"btlife","affiliate":"5w1ft3bg","itemNo":null,"amount":158.55,"netAmount":null,"affAmount":null,"saleCount":null},{"#class":"com.sun.proxy.$Proxy173","busDate":"2022-09-16","vendorMasterAccount":"emily#emilylark.com","vendor":"btlife","affiliate":"5w1ft3bg","itemNo":null,"amount":51.58,"netAmount":null,"affAmount":null,"saleCount":null},{"#class":"com.sun.proxy."[truncated 191979 bytes]; line: 1, column: 61] (through reference chain: java.util.ArrayList[0]); nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.sun.proxy.$Proxy173` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"["java.util.ArrayList",[{"#class":"com.sun.proxy.$Proxy173","busDate":"2022-09-12","vendorMasterAccount":"emily#emilylark.com","vendor":"btlife","affiliate":"5w1ft3bg","itemNo":null,"amount":158.55,"netAmount":null,"affAmount":null,"saleCount":null},{"#class":"com.sun.proxy.$Proxy173","busDate":"2022-09-16","vendorMasterAccount":"emily#emilylark.com","vendor":"btlife","affiliate":"5w1ft3bg","itemNo":null,"amount":51.58,"netAmount":null,"affAmount":null,"saleCount":null},{"#class":"com.sun.proxy."[truncated 191979 bytes]; line: 1, column: 61] (through reference chain: java.util.ArrayList[0])
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:152)
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:130)
at org.springframework.data.redis.serializer.DefaultRedisElementReader.read(DefaultRedisElementReader.java:49)
at org.springframework.data.redis.serializer.RedisSerializationContext$SerializationPair.read(RedisSerializationContext.java:272)
at org.springframework.data.redis.cache.RedisCache.deserializeCacheValue(RedisCache.java:280)
at org.springframework.data.redis.cache.RedisCache.lookup(RedisCache.java:94)
at org.springframework.cache.support.AbstractValueAdaptingCache.get(AbstractValueAdaptingCache.java:58)
at com.clickbank.clientanalytics.components.WebFluxCache.lambda$lookupWithMetrics$3(WebFluxCache.java:54)
at reactor.cache.CacheMono.lambda$null$4(CacheMono.java:158)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)
at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:157)
at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:199)
at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:100)
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:120)
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:73)
at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2397)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2193)
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2067)
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54)
at reactor.core.publisher.Mono.subscribe(Mono.java:4150)
at reactor.core.publisher.MonoSubscribeOn$SubscribeOnSubscriber.run(MonoSubscribeOn.java:126)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.sun.proxy.$Proxy173` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"["java.util.ArrayList",[{"#class":"com.sun.proxy.$Proxy173","busDate":"2022-09-12","vendorMasterAccount":"emily#emilylark.com","vendor":"btlife","affiliate":"5w1ft3bg","itemNo":null,"amount":158.55,"netAmount":null,"affAmount":null,"saleCount":null},{"#class":"com.sun.proxy.$Proxy173","busDate":"2022-09-16","vendorMasterAccount":"emily#emilylark.com","vendor":"btlife","affiliate":"5w1ft3bg","itemNo":null,"amount":51.58,"netAmount":null,"affAmount":null,"saleCount":null},{"#class":"com.sun.proxy."[truncated 191979 bytes]; line: 1, column: 61] (through reference chain: java.util.ArrayList[0])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1615)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1077)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1332)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:199)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:166)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:132)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:99)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromAny(AsPropertyTypeDeserializer.java:195)
at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:710)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:292)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:249)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26)
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:120)
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromArray(AsArrayTypeDeserializer.java:53)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromAny(AsPropertyTypeDeserializer.java:193)
at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:710)
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4526)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3529)
at org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer.deserialize(GenericJackson2JsonRedisSerializer.java:150)
It seems to be an issue with Jackson not knowing how to serialize proxied instances of the interface.
Repository:
public interface AccountSalesHealthRepository extends CrudRepository<AccountSalesHealth, String> {
#Query(
value = "select vendor_master_account as vendorMasterAccount" +
" , vendor" +
" , affiliate" +
" , bus_date as busDate" +
" , sum(sale_amount) as amount " +
"from mv_ca_account_sales_health " +
"where vendor_master_account = :vendorMasterAccount" +
" and bus_date >= :start" +
" and bus_date <= :end " +
"group by 1, 3, 2, 4",
nativeQuery = true
)
Collection<AccountSalesHealthDto> getGrossSalesByAffiliate(#Param("vendorMasterAccount") String vendorMasterAccount, #Param("start") String start, #Param("end") String end);
Entity model:
#Entity
#Table(name = "mv_ca_account_sales_health")
public class AccountSalesHealth {
#Id
private Integer id;
}
DTO interface:
#JsonPropertyOrder({"busDate", "vendorMasterAccount", "vendor", "affiliate", "itemNo", "amount", "netAmount"})
public interface AccountSalesHealthDto {
String getVendorMasterAccount();
String getVendor();
String getBusDate();
String getAffiliate();
String getItemNo();
Double getAmount();
Double getAffAmount();
Double getNetAmount();
Integer getSaleCount();
}
Is there any way to specify the way to create these or otherwise influence the serialization behavior?

Related

SqlResultSetMapping to POJO class from a NamedNativeQuery throwing 'could not locate appropriate constructor'

I made a #NamedNativeQuery and attached it to an entity 'Doctor', on the same entity I attached a #SqlResultSetMapping which takes the columns of the query's result and maps them to a constructor of a specifically made POJO class. This query is also connected to a JPA method, which resides in the repository of the same entity.
However I keep getting an error that the appropriate constructor could not be located, as if the #SqlResultSetMapping or the POJO constructors are not in sync. (stack trace is at the bottom)
My entity, #NamedNativeQuery and #SqlResultSetMapping:
I tried the query directly on the DB and it gave the expected result, so I am just writing the select clause
#Entity
#NamedNativeQuery(
name =
"Doctor.findFreeExaminationTimes", // name of the JPA method in entity's repository (definition below)
query =
"SELECT on_date AS onDate, LAG(to_time, 1, '00:00') OVER mojWindow AS fromTime, from_time AS toTime " +
"...",
resultSetMapping = "freeTimesByDoctorId" // name of the mapping below
)
#SqlResultSetMapping(
name = "freeTimesByDoctorId", // result set mapping name
classes = #ConstructorResult(
targetClass = DoctorAvailabilityResponse.class, // my POJO class (definition below)
columns = { // order and types are the same as in the select clause above and the POJO constructor below
#ColumnResult(name = "onDate", type = java.sql.Date.class),
#ColumnResult(name = "fromTime", type = java.sql.Time.class),
#ColumnResult(name = "toTime",type = java.sql.Time.class)
}
)
)
public class Doctor extends User {...}
The POJO class which I mention in the #ConstructorResult under 'targetClass' has a constructor with the exact order, number and type, of arguments, specified under 'columns'
My POJO class which should be mapped to the query's result:
public class DoctorAvailabilityResponse {
final private java.sql.Date onDate;
final private java.sql.Time fromTime;
final private java.sql.Time toTime;
public DoctorAvailabilityResponse(java.sql.Date onDate, java.sql.Time fromTime, java.sql.Time toTime) {
this.onDate = onDate;
this.fromTime = fromTime;
this.toTime = toTime;
}
// getters
}
My repository:
#RepositoryRestResource
public interface DoctorRepository extends UserRepository<Doctor> {
// JPA method mapped to the named native query above
List<DoctorAvailabilityResponse> findFreeExaminationTimes(#Param("doctorId") Long doctorId);
}
However when testing this JPA method I get an exception with the message 'could not locate appropriate constructor'.
My test:
#SpringBootTest
public class DoctorTests {
#Autowired
private DoctorRepository doctorRepository;
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
#Test
public void shouldReturnDoctorAvailability() {
// Exception thrown here
List<DoctorAvailabilityResponse> freeTimes = doctorRepository.findFreeExaminationTimes(4L);
LOGGER.info(freeTimes.toString());
}
}
I cannot understand why is this happening. Is there a way to manually map this result set to the POJO while maintaining the JPA repository method?
Stack trace:
org.springframework.dao.InvalidDataAccessApiUsageException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse; nested exception is java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:256)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
...
...
Caused by: java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.resolveConstructor(ConstructorResultColumnProcessor.java:92)
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.performDiscovery(ConstructorResultColumnProcessor.java:45)
at org.hibernate.loader.custom.CustomLoader.autoDiscoverTypes(CustomLoader.java:494)
at org.hibernate.loader.Loader.processResultSet(Loader.java:2333)
at org.hibernate.loader.Loader.getResultSet(Loader.java:2289)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2045)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2007)
at org.hibernate.loader.Loader.doQuery(Loader.java:953)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354)
at org.hibernate.loader.Loader.doList(Loader.java:2810)
at org.hibernate.loader.Loader.doList(Loader.java:2792)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2624)
at org.hibernate.loader.Loader.list(Loader.java:2619)
at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:338)
at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:2137)
at org.hibernate.internal.AbstractSharedSessionContract.list(AbstractSharedSessionContract.java:1134)
at org.hibernate.query.internal.NativeQueryImpl.doList(NativeQueryImpl.java:173)
at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1526)
at org.hibernate.query.Query.getResultList(Query.java:165)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:409)
at com.sun.proxy.$Proxy212.getResultList(Unknown Source)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:126)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:618)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:353)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 73 more
Sanity check and alternative approach
To make a sanity check I deleted the #SqlResultSetMapping, in which case the query is supposed to return a list of 'Object[]' values, and then I tested each individual value in that array to check its type, it showed me that the types are what I assumed them to be 'java.sql.Date' and 'java.sql.Time' twice, and all three of them were in the expected order, (Date, Time, Time), which matches the order of the constructor parameters of my POJO class.
My entity and namedNativeQuery:
#Entity
#NamedNativeQuery(
name =
"Doctor.findFreeExaminationTimes",
query =
"SELECT on_date AS onDate, LAG(to_time, 1, '00:00') OVER mojWindow AS fromTime, from_time AS toTime " +
"..."
)
public class Doctor extends User {...}
My repository with a new return type:
#RepositoryRestResource
public interface DoctorRepository extends UserRepository<Doctor> {
List<Object[]> findFreeExaminationTimes(#Param("doctorId") Long doctorId);
}
My test:
#SpringBootTest
public class DoctorTests {
#Autowired
private DoctorRepository doctorRepository;
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
#Test
public void shouldReturnDoctorAvailability() {
// Exception thrown here
List<DoctorAvailabilityResponse> freeTimes = doctorRepository.findFreeExaminationTimes(4L);
freeTimes.stream().forEach((ft) -> {
// Values are in expected order and of expected types
String classNameOnDate = ft[0].getClass().toString(); // java.sql.Date
String classNameFromTime = ft[1].getClass().toString(); // java.sql.Time
String classNameToTime = ft[1].getClass().toString(); // java.sql.Time
// I suppose the mapping mechanism is supposed to do something like this, but fails for some reason
DoctorAvailabilityResponse dar = new DoctorAvailabilityResponse((Date)ft[0], (Time)ft[1], (Time)ft[2]);
});
LOGGER.info(freeTimes.toString());
}
}
Running this test works perfectly which supposedly shows that the problem is in the #SqlResultSetMapping or in POJO class.
I would appreciate any feedback. Thanks!
SOLUTION
I had to change the types in the #SqlResultSetMapping and in the constructor of my POJO class.
Changed #SqlResultSetMapping
#SqlResultSetMapping(
name = "freeTimesByDoctorId",
classes = #ConstructorResult(
targetClass = DoctorAvailabilityResponse.class,
columns = {
#ColumnResult(name = "onDate", type = String.class),
#ColumnResult(name = "fromTime", type = String.class),
#ColumnResult(name = "toTime",type = String.class)
}
)
)
Changed POJO class constructor
public DoctorAvailabilityResponse(String onDate, String fromTime, String toTime) {
this.onDate = Date.valueOf(onDate);
this.fromTime = Time.valueOf(fromTime);
this.toTime = Time.valueOf(toTime);
}
This alone did not solve my problem however as I got a hibernate exception as mentioned and solved in this SO question. According to this answer I also changed my repository and added an additional annotation.
Changed repository
#RepositoryRestResource
public interface DoctorRepository extends UserRepository<Doctor> {
#Query(nativeQuery = true) // This is added
List<DoctorAvailabilityResponse> findFreeExaminationTimes(#Param("doctorId") Long doctorId);
}
Now everything works, though the question remains why didn't #SqlResultSetMapping map java.sql.* types to the constructor in the first place.
#ConstructorResult does not work very well with java.sql.Date.class or java.sql.Time.class type. A way to fix your issue is to use String.class instead and then convert the String values to Date/Time in DoctorAvailabilityResponse's constructor

JSON parse error: Cannot deserialize instance of `java.lang.Long

I am using Spring boot I try to create a new Student.
#RequestMapping(path="/student/create", method=RequestMethod.POST)<br>
public ResponseEntity<String> createStudent(#RequestBody Long nummer, String firstname, String lastname){<br>
service.createStudent(matrikelnummer, vorname, nachname);<br>
return new ResponseEntity<String>("gut gemacht", HttpStatus.OK);
Here my RequestBody with RESTCLIENT
{"nummer":15557,"firstname":"toti","lastname":"Innna"}
I have this Error:
{"timestamp":"2019-12-20T19:41:30.083+0000","status":400,"error":"Bad
Request","message":"JSON parse error: Cannot deserialize instance of
java.lang.Long out of START_OBJECT token; nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.lang.Long out of START_OBJECT token\n
at [Source: (PushbackInputStream); line: 1, column:
1]","trace":"org.springframework.http.converter.HttpMessageNotReadableException:
JSON parse error: Cannot deserialize instance of java.lang.Long out
of START_OBJECT token; nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of java.lang.Long out of START_OBJECT token\n
at [Source: (PushbackInputStream); line: 1, column: 1]\r\n\tat
In your example the mapper expects the body to present a Long object, but you pass it a Student object. This does not match, so it throws an exception.
It is not necessary to list all the fields of the students as separate method arguments, you can just pass a Student object as RequestBody argument.
The object mapper will then try to resolve a Student instance from the provided JSON.
Example:
#RequestMapping(path="/student/create", method=RequestMethod.POST)
public ResponseEntity<String> createStudent(#RequestBody Student student){
service.createStudent(student);
return new ResponseEntity<String>("gut gemacht", HttpStatus.OK);
}

How to create a new entity with association

Let's say i have a form on the frontend with usual fields and dropdowns.
In those dropdowns user is able to select an option, and each option is linked to an entity in Spring data JPA;
Dropdowns contain some label and a link to corresponding entity as a value.
This value is then passed in a POST-request to a PagingAndSorting repository of an entity which we wish to create.
Let's say it's a user with username and he must be associated with one of the offices (Also an entity):
#Data
#Builder
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name="users")
public class User{
#Id
#Coluemn(name="USER_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
#Column(name="USER_NAME", nullable=false)
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="OFFICE_ID", **nullable=false**)
private Office office;
}
My first guess would be:
Sending POST-request to http://localhost:8080/api/users/
contentType:'application/json'
{"userName":"Anton","office":"http://localhost:8080/api/offices/1"}
But it throws an exception
{
"cause": {
"cause": null,
"message": "Cannot construct instance of `test.domain.Office` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/offices/1')\n at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 160] (through reference chain: test.domain.User[\"office\"])"
},
"message": "JSON parse error: Cannot construct instance of `test.domain.Office` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/offices/1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `test.domain.Office` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/office/1')\n at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 160] (through reference chain: test.domain.User[\"office\"])"
}
What am i doing wrong?
You are sending a URL resource as a string in place of a JSON object and expecting some magic to happen between Spring and jackson to look up the value. Naturally this is not what is happening and Jackson is attempting to bind the string value of the URL to the Office field. This fails of course because it does not know how to create an Office object from a string.
A possible solution is to make a distinction between your Entity Objects (those which represent your database tables) and DTO's (Data Transfer Objects) which in this cause represent your contract with your client. When doing this you could receive a User Object like so:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class User{
private Long userId;
private String userName;
private Long officeId;
}
Now you can simply send an office id instead of a URL and in your code use a Spring data repository to lookup the office object. After that you can then construct your Entity User object like the one you have shown above and persist it.
Turns out it was because i used Lombok, which generated it's own constructor.
To make it work i just needed to set #AllArgsConstructor like this:
#AllArgsConstructor(suppressConstructorProperties = true)
Now it works as i expected:
Json to send to http://localhost:8080/api/users:
{
"userName":"Anton",
"office":"http://localhost:8080/api/offices/1"
}
Which returns:
{
"userName":"Anton",
"_links": {
"self": {
"href": "http://localhost:8080/api/users/28"
},
"user": {
"href": "http://localhost:8080/api/users/28"
},
"office": {
"href": "http://localhost:8080/api/users/28/office"
}
}
}

NPE from Jackson trying to serializing a field that does not exists?

Here is my simple bean
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Foo {
private String firstName;
private String lastName;
public String getFullName(){
return firstName + lastName;
}
}
when this object gets serialized in Spring-boot controller with Jackson,
I get the following error
j.l.NullPointerException: null
com.example.foobar.foo.getFullName(Foo.java:28)
s.r.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java)
s.r.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
s.r.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
j.l.reflect.Method.invoke(Method.java:498)
c.f.j.d.s.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:653)
c.f.j.d.s.s.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690)..
28 common frames omitted\nWrapped by: c.f.j.d.JsonMappingException: (was java.lang.NullPointerException)
(through reference chain: com.example.foobar.foo[\"fullName\"])
c.f.j.d.JsonMappingException.wrapWithPath(JsonMappingException.java:379)
c.f.j.d.JsonMappingException.wrapWithPath(JsonMappingException.java:339)
c.f.j.d.s.s.StdSerializer.wrapAndThrow(StdSerializer.java:343)
c.f.j.d.s.s.BeanSerializerBase.serializeFields(BeanSerializerBase.java:698)
c.f.j.d.s.BeanSerializer.serialize(BeanSerializer.java:155)
c.f.j.d.s.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
c.f.j.d.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1419)
c.f.j.d.ObjectWriter.writeValue(ObjectWriter.java:940)
o.s.h.c.j.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:267)...
23 common frames omitted\nWrapped by: o.s.h.c.HttpMessageNotWritableException: Could not write content: (was java.lang.NullPointerException) (through reference chain: com.example.foobar.foo[\"fullName\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.example.foobar.foo[\"fullName\"])
o.s.h.c.j.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:274 ..."
Here is the requestBody that I sent
{"firstName": "foo",
"lastName: null
}
Is Jackson trying to serialize fullName property ? but I have no such field declared. is this expected behavior? I am unable to find documentation that supports this behavior.
Thanks
Your guess is right, Jackson is trying to find the field name by its getter method, which it cannot find. And hence NPE.
Different solutions
use #JsonIgnore on the getFullName method.
you can disable this feature by setting this property in spring boot application.properties file
spring.jackson.mapper.use-getters-as-setters=false
If you wish to do it with the java code (If not using spring boot), you can do it with a bean declaration like this
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToDisable(MapperFeature.USE_GETTERS_AS_SETTERS);
return builder;
}

Passing an object to a controller with Ajax - which contains an Enum

I'm trying to pass an object Consumer to my Controller. A Consumer has a one-to-many relationship with Policy. This is what I tried:
function changeConsumer(){
var newConsumer = {
"id" : /*[[${consumer.id}]]*/,
"name" : document.getElementById('0').value,
"endpoint" : document.getElementById('1').value,
"policies" : /*[[${policies}]]*/
}
$.ajax({
type: "PUT",
url: "/rest/consumer/update",
data: JSON.stringify(newConsumer),
contentType: "application/json; charset=utf-8",
success : function(response) {
...
});
}
If I pass the Consumer like this, with an empty array of Policies - it works:
function changeConsumer(){
var newConsumer = {
"id" : /*[[${consumer.id}]]*/,
"name" : document.getElementById('0').value,
"endpoint" : document.getElementById('1').value,
"policies" : []
}
$.ajax({
type: "PUT",
url: "/rest/consumer/update",
data: JSON.stringify(newConsumer),
contentType: "application/json; charset=utf-8",
success : function(response) {
...
});
}
This is the stacktrace I'm receiving, which leads me to think there a problem with the Enum "Effect" in the Policy object:
2017-11-18 16:28:22.255 WARN 7884 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize instance of com.policyMgmt.policy.Effect out of START_OBJECT token
at [Source: java.io.PushbackInputStream#56bd85f4; line: 1, column: 93] (through reference chain: com.policyMgmt.consumer.Consumer["policies"]->java.util.ArrayList[0]->com.policyMgmt.policy.Policy["effect"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.policyMgmt.policy.Effect out of START_OBJECT token
at [Source: java.io.PushbackInputStream#56bd85f4; line: 1, column: 93] (through reference chain: com.policyMgmt.consumer.Consumer["policies"]->java.util.ArrayList[0]->com.policyMgmt.policy.Policy["effect"])
2017-11-18 16:28:22.255 WARN 7884 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize instance of com.policyMgmt.policy.Effect out of START_OBJECT token
at [Source: java.io.PushbackInputStream#56bd85f4; line: 1, column: 93] (through reference chain: com.policyMgmt.consumer.Consumer["policies"]->java.util.ArrayList[0]->com.policyMgmt.policy.Policy["effect"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.policyMgmt.policy.Effekt out of START_OBJECT token
at [Source: java.io.PushbackInputStream#56bd85f4; line: 1, column: 93] (through reference chain: com.policyMgmt.consumer.Consumer["policies"]->java.util.ArrayList[0]->com.policyMgmt.policy.Policy["effect"])
The Policy object which looks like this:
#Entity
public class Policy {
#Id
#GeneratedValue
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#Column (name = "consumer_id")
private Integer consumer;
#Enumerated(EnumType.STRING)
#Column(name="effect")
private Effect effect;
public Policy() {}
....
}
Maybe it helps, this is what a Consumer looks like:
#Entity
public class Consumer {
#Id
#GeneratedValue
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "endpoint")
private String endpoint;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name ="consumer_id")
private List<Policy> policies;
public Consumer() {
}
}
Rest API
#Controller
#RequestMapping(value = "/rest/consumer")
public class ConsumerController {
...
#PutMapping(value = "/update")
#ResponseBody
public List<Consumer> updateConsumer(#RequestBody Consumer consumer) {}
...
}
Does anyone have experience passing enums within Ajax to a Controller?
I can't say for certain why #RequestBody is having difficulty deserializing JSON like { "$type": "Effekt", "$name": "deny" } into an instance of the Effekt enum, but I think you should try writing a custom JsonDeserializer for your enum.
Here's another Stackoverflow question that is similar, but not identical to the issue you're facing. Take a look at the second answer -- the one that includes a code example of a JsonDeserializer.
With a bit of trail-and-error, you can probably come up with something that successfully processes JSON like you're already getting into instances of Effekt.
The other option would be to change the JSON representation of Effekt in that gets sent to the server. Perhaps if it looked like this...
"effekt": "deny"
it would work directly with #RequestBody. You could try hard-coding it in the JavaScript to test.
If you want to go this route, you will probably need to implement a custom JsonSerializer -- the reverse of the earlier option.

Resources