I'm having a problem de deserializing a class in Spring Boot. When my controller tries to deserialize it, it crashes. Here is the class:
#Entity
#Table(name="trash_cans")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TrashCan {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="TRASH_CAN_ID")
long id;
#Column(name="TRASH_CAN_NAME")
String name;
#ManyToOne
#JoinColumn(name="PLACE_ID")
private Place place;
#OneToMany(mappedBy="trashCan", targetEntity=TrashMeter.class, fetch=FetchType.LAZY)
private List<TrashCan> trashMeterList;
#OneToMany(mappedBy="trashCan", targetEntity=TrashSensor.class, fetch=FetchType.LAZY)
private List<TrashSensor> trashSensorList;
public TrashCan() {
}
public TrashCan(long id, String name) {
super();
this.id = id;
this.name = name;
}
[getters and setters]
}
That depends on this one:
#Entity
#Table(name="trash_sensor")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TrashSensor {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
#ManyToOne
#JoinColumn(name="TRASH_CAN_ID")
private TrashCan trashCan;
#OneToMany(mappedBy = "trashSensor", targetEntity = Measurement.class, fetch = FetchType.LAZY)
private List<Measurement> measurementList;
public TrashSensor() {
super();
}
And Trash Sensor Depends on this Class:
#Entity
#Table(name="measurement")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Measurement {
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column(name="value")
private float value;
#Column(name="last_measure")
private LocalDateTime dateTime;
#ManyToOne
#JoinColumn(name="trash_sensor_id")
private TrashCan trashSensor;
public Measurement() {
}
}
My Controler:
#RequestMapping(value="/trashCan", method=RequestMethod.GET)
public ResponseEntity<Iterable<TrashCan>> getPlaces(){
Iterable<TrashCan> trashCanIterable = trashCansRepository.findAll();
return new ResponseEntity<>(trashCanIterable, HttpStatus.OK);
}
When I call the webservice, I get this error:
Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: could not deserialize (through reference chain: java.util.ArrayList[0]-br.com.simplepass.cleanerway.domain.TrashCan["trashSensorList"]-org.hibernate.collection.internal.PersistentBag[0]-br.com.simplepass.cleanerway.domain.TrashSensor["measurementList"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not deserialize (through reference chain: java.util.ArrayList[0]-br.com.simplepass.cleanerway.domain.TrashCan["trashSensorList"]-org.hibernate.collection.internal.PersistentBag[0]-br.com.simplepass.cleanerway.domain.TrashSensor["measurementList"])
I can't interpret this error =/. Any help with this problem greatly appreciated.
You are getting this error since your json is entering a loop, to avoid this, use #JsonIgnore annotation:
#OneToMany(mappedBy = "trashSensor", targetEntity = Measurement.class, fetch = FetchType.LAZY)
#JsonIgnore
private List<Measurement> measurementList;
It happens when you use relations between entities. Imagine that your TrashCan has link to Trash in it. And your trash has link to it's wrapper - trashcan. So what you try to serialize TrashCan entity you also serializing Trash. And then when you are serializing trash trashcan is serialized again inside it. And so on. It's a loop. You can use #JsonIgnore on every entity that may cause loop.
#JsonIgnore
#ManyToOne
#JoinColumn(name="PLACE_ID")
private Place place;
#JsonIgnore
#OneToMany(mappedBy="trashCan", targetEntity=TrashMeter.class, fetch=FetchType.LAZY)
private List<TrashCan> trashMeterList;
#JsonIgnore
#OneToMany(mappedBy="trashCan", targetEntity=TrashSensor.class, fetch=FetchType.LAZY)
private List<TrashSensor> trashSensorList;
But it's a bad way. It's strongly recommended to use DTO (Data transfer object) pattern for you serialization/deserialization. It also gives you more flexibility. You can read about it here
If you need trashMeterList and trashSensorList in response then follow this answer.
Due to hibernate lazy loading and no session while deserialisation, you are getting this exception.
To fix just change your controller:
#RequestMapping(value="/trashCan", method=RequestMethod.GET)
public ResponseEntity<Iterable<TrashCan>> getPlaces(){
Iterable<TrashCan> trashCanIterable = trashCansRepository.findAll();
List<TrashCan> responseList = new ArrayList<TrashCan>(trashCanIterable.size())
while(trashCanIterable.hasNext()){
TrashCan trashCan = trashCanIterable.next();
for(TrashMeter trashMeter : trashCan.trashMeterList){
}
for(TrashSensor trashSensor : trashCan.trashSensorList){
}
responseList.add(trashCan);
}
return new ResponseEntity<>(responseList, HttpStatus.OK);
}
Related
Tables:
pattient ( id, name, id_status, ...) -> FK to pattient_status
pattient_status (id, description) -> target table
All I need is to obtain pattient_status.description inside my pattient.class, because my GET method needs this information on the JSON return.
Code:
#Entity
#Table(name="cad_paciente")
public class Paciente {
... (other columns)
#OneToOne
#JoinColumn(insertable=false, updatable=false, name = "id_status_paciente", referencedColumnName = "id")
private StatusPaciente status;
public String getStatusPaciente(){
return status.getStatus();
}
----
#Entity
#Table(name="cad_status_paciente")
public class StatusPaciente {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="ds_status")
#Size(max=50)
private String status;
This lists my information correctly, but on POST method, JPA saves correctly but returns the message:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.spin.spincare.model.Paciente["statusPaciente"])]
What should I do?
It's an issue with your getter:
public String getStatusPaciente() {
return status.getStatus();
}
In your POST call status is null, so when Jackson uses this getter to generate the JSON it gets a null pointer exception. Update it to something like:
public String getStatusPaciente() {
if (status == null) {
return null;
}
return status.getStatus();
}
Use #MapsId. This will make ids of entities match. Read more here.
#Entity
#Table(name="cad_status_paciente")
public class StatusPaciente {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#MapsId
#OneToOne
private Paciente paciente;
#Column(name="ds_status")
#Size(max=50)
private String status;
}
I have the following #Entity:
#Entity
public class Person {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Date birthDate;
private String status;
private String city;
...
// many more attributes
}
I'm using the spring data rest as follow:
#RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepositorio extends PagingAndSortingRepository<Person, Long>{
}
When I send the a post to the /api/person/ with a JSON containing all attributes of Person, only status is not set. Can someone help me?
Well, I think you're missing the get and set for status. Did you check it?
When I return a Page<Entity> from a method inside my #RestController class, all fields of Entity both referenced via #OneToXXX and #ManyToXXX take place in the returned JSON object. But when I switched the return type to PagedResource (to be able to add links to the response), #ManyToXXX fields are not included at all.
Here is the method in question:
#GetMapping("/fetch")
public PagedResources getResults(Pageable pageable, PagedResourcesAssembler assembler) {
Page<ParentClass> page = myRepository.findAll(pageable);
PagedResources pagedResources = assembler.toResource(page, myResourceAssembler);
return pagedResources;
}
Here is the resource assembler: it's #Autowired in the MyController's body.
MyResourceAssembler
#Component
public class MyResourceAssembler extends ResourceAssemblerSupport<ParentClass, Resource> {
public MyResourceAssembler() { super(MyController.class, Resource.class); }
#Override
public Resource toResource(ParentClass obj) {
return new Resource<>(obj,
linkTo(methodOn(MyController.class).getResults(obj.getId())).withRel("edit"),
}
}
Here are the basic class definitions:
ParentClass
#Entity
#Table(name = "parent_table", catalog = "myDB")
public class ParentClass implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#ManyToOne(optional = false)
#JoinColumn(name = "other_class", referencedColumnName = "id")
private OtherClass otherClass;
#OneToOne(mappedBy = "parent")
private SampleField1 sampleField1;
#OneToMany(mappedBy = "parent")
private List<SampleField2> sampleField2;
}
SampleField1 OneToXXX
#Entity
#Table(name = "sample_table_1", catalog = "myDB")
public class SampleField1 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name="some_field")
String someField;
#OneToOne
#JoinColumn(name = "sample_field_1", referencedColumnName = "id")
#JsonBackReference //to avoid infinite recursion
private ParentClass parent;
}
OtherClass ManyToOne
#Entity
#Table(name = "other_table", catalog = "myDB")
public class OtherClass implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name="some_other_field")
String someOtherField;
// I don't need any reference to ParentClass here.
}
To add further detail to the issue here is the logging output of changeProperties() method inside PersistentEntityJackson2Module class:
s.d.r.w.j.PersistentEntityJackson2Module : Assigning nested entity serializer for #javax.persistence.OneToOne(..) com.project.SampleField1 com.project.model.ParentClass.sampleField1
s.d.r.w.j.PersistentEntityJackson2Module : Assigning nested entity serializer for #javax.persistence.OneToMany(..) com.project.SampleField2 com.project.model.ParentClass.sampleField2
// .... omitted other lines for brevity
the resulting JSON is :
{
"_embedded":{
"parentClasses":[
{
"id":1,
// <-- There is no field for otherClass !
"sampleField1":{
"id":1,
"sampleField":"blabla"
},
"sampleField2":[ ]
}
]
},
"links":[
]
}
As it can be seen above, OneToXXX fields are being taken to be serialized but no output for the ManyToOne fields like
Assigning nested entity serializer for #javax.persistence.ManyToOne ... com.my.OtherClass ... and therefore those aren't existed in the response JSON.
According to this SO answer, #ManyToXXX referenced entities are appended as links to the JSON response. But that's not an acceptable solution for me since I have a different planning of consumption in my mind for the rest client.
Bottomline, I'd like to have my ManyToOne referenced entities in my JSON Response returned from getResults() method.
Anything I can provide just ask in the comments.
Return Entity in responses is not the best way, because usually clients dont need whole set of data. Also, if Entities has links for each other, it will cause StackoverflowException on serialization tries. Use DTO for responses. At least it will help you to determine where is the problem - serialization, or fetching from database. Anyway it is more proper way for serving data to clients.
By the way, check getter and setter for otherClass in your ParentClass :) If threre is no getter and setter, thats will be reason of your issue.
Also, take a look into OtherClass for default empty constructor. If it hasn't present in there, you should add it.
I have a simple proof-of-concept demo using Spring Data REST / RestRepository architecture. I started with two entities:
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="Address")
public class Address implements Serializable {
public Address() {}
#Column(name="ID", nullable=false, unique=true)
#Id
#GeneratedValue(generator="CUSTOMER_ADDRESSES_ADDRESS_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_ADDRESS_ID_GENERATOR", strategy="native")
private int ID;
//#RestResource(exported = false)
#ManyToOne(targetEntity=domain.location.CityStateZip.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.PERSIST})
#JoinColumns({ #JoinColumn(name="CityStateZipID", referencedColumnName="ID", nullable=false) })
private domain.location.CityStateZip cityStateZip;
#Column(name="StreetNo", nullable=true)
private int streetNo;
#Column(name="StreetName", nullable=false, length=40)
private String streetName;
<setters and getters ommitted>
}
and for CityStateZip:
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="CityStateZip")
public class CityStateZip {
public CityStateZip() {}
#Column(name="ID", nullable=false, unique=true)
#Id
#GeneratedValue(generator="CUSTOMER_ADDRESSES_CITYSTATEZIP_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_CITYSTATEZIP_ID_GENERATOR", strategy="native")
private int ID;
#Column(name="ZipCode", nullable=false, length=10)
private String zipCode;
#Column(name="City", nullable=false, length=24)
private String city;
#Column(name="StateProv", nullable=false, length=2)
private String stateProv;
}
with repositories:
#RepositoryRestResource(collectionResourceRel = "addr", path = "addr")
public interface AddressRepository extends JpaRepository<Address, Integer> {
List<Address> findByStreetNoAndStreetNameStartingWithIgnoreCase(#Param("stNumber") Integer streetNo, #Param("street") String streetName);
List<Address> findByStreetNameStartingWithIgnoreCase(#Param("street") String streetName);
List<Address> findByStreetNo(#Param("streetNo") Integer strNo);
}
and:
// #RepositoryRestResource(collectionResourceRel = "zip", path = "zip", exported = false)
#RepositoryRestResource(collectionResourceRel = "zip", path = "zip")
public interface CityStateZipRepository extends JpaRepository<CityStateZip, Integer> {
List<CityStateZip> findByZipCode(#Param("zipCode") String zipCode);
List<CityStateZip> findByStateProv(#Param("stateProv") String stateProv);
List<CityStateZip> findByCityAndStateProv(#Param("city") String city, #Param("state") String state);
}
and main() code of
#Configuration
#EnableJpaRepositories
#Import(RepositoryRestMvcConfiguration.class)
#EnableAutoConfiguration
// #EnableTransactionManagement
#PropertySource(value = { "file:/etc/domain.location/application.properties" })
#ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
with this code, things work fine. I can save a CityStateZip and reference it while saving an Address, and can GET the resulting address. Step two was to add another class: An Address can have 0-to-many associated Remarks, while a Remark is tied to a single Address. That entity is:
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="Remark")
#SuppressWarnings({ "all", "unchecked" })
public class Remark implements Serializable {
public Remark() {
}
#Column(name="ID", nullable=false)
#Id
#GeneratedValue(generator="CUSTOMER_ADDRESSES_REMARK_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_REMARK_ID_GENERATOR", strategy="native")
private int ID;
#ManyToOne(targetEntity=domain.location.Address.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="LocationID", referencedColumnName="ID", nullable=false) })
private domain.location.Address address_ix;
#Column(name="Comment", nullable=false, length=255)
private String comment;
<getters, setters removed>
}
and a repository:
#RepositoryRestResource(collectionResourceRel = "remarks", path = "remarks")
public interface RemarkRepository extends JpaRepository<Remark, Integer> {
}
and I add this to Address:
#OneToMany(mappedBy="address_ix", targetEntity=domain.location.Remark.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.Set remark = new java.util.HashSet();
I can retrieve the exposed API by doing GET .../addr if I do not have any Address instances. I can still save an Address instance to the database. But once I have a saved instance, doing a GET .../addr, or trying to GET the specific instance, I get an exception:
"timestamp": 1418423263313,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.http.converter.HttpMessageNotWritableException",
"message": "Could not write JSON: (was java.lang.NullPointerException) (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"addr\"]->java.util.ArrayList[0]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"addr\"]->java.util.ArrayList[0])",
"path": "/addr"
I assume I am doing something wrong with the #OneToMany association in Address, or the #ManyToOne in Remark, or a combination of both. I have added a #ManyToOne, one-directional association to Address with no problems. What do I need to do to be able to retrieve Address data through GETs? What is JSON complaining about? (At this point, there was no Remark instance in the database. I even added a #JsonIgnore annotation in front of the #OneToMany, but I still got the error.
It turns out that the problem was due to auto-generated code. The declaration of the Set of remarks is missing the parameter calling out the type of Set (or HashSet). If I change the code to
#OneToMany(mappedBy="address_ix", targetEntity=domain.location.Remark.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.Set<Remark> remark = new java.util.HashSet<Remark>();
the code now works as required. Without the parameter, the (Spring or JSON) code did not know how to process this member.
I want to save Tesis class using entityManager.persist() method but I get following error.
Caused by: javax.validation.UnexpectedTypeException: HV000030: No validator could be found for type: thymeleafexamples.layouts.acenta.Acenta.
#Entity
public class Tesis {
public Tesis(){
}
public Tesis(String adi, Acenta acenta) {
this.adi = adi;
this.acenta = acenta;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotEmpty
private String adi;
#NotEmpty
#ManyToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name="acenta_id")
private Acenta acenta;
//GETTERS AND SETTERS
}
/////////////////////////////////////////////////////////////////////
#SuppressWarnings("serial")
#Entity
public class Acenta implements java.io.Serializable {
public Acenta(String adi) {
this.adi = adi;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotEmpty
private String adi;
#OneToMany(mappedBy="acenta")
private Set<Tesis> tesiss;
}
According to Hibernate Validator's API org.hibernate.validator.constraints.NotEmpty annotation
"asserts that the annotated string, collection, map or array is not
null or empty"
Based on the above definition Acenta type is not a valid type to check. You may consider using javax.validation.constraints.NotNull annotation instead as it's valid for all types and moreover not vendor specific.