I am trying out some very basic webservice. I get this exception everytime I try to return the Prtnr object.
Uncaught exception thrown in one of the service methods of the servlet: spitter. Exception thrown :
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
(through reference chain: org.hibernate.collection.PersistentSet[0]->org.abc.dvo.PrtnrGeoInfo["id"]->org.abc.dvo.PrtnrGeoInfoId["partner"]->
org.abc.dvo.Prtnr["prtnrGeoInfos"]->org.hibernate.collection.PersistentSet[0]->org.abc.dvo.PrtnrGeoInfo["id"]->org.abc.dvo.PrtnrGeoInfoId["partner"]->
org.abc.dvo.Prtnr["prtnrGeoInfos"]->org.hibernate.collection.PersistentSet[0]->org.abc.dvo.PrtnrGeoInfo["id"]->org.abc.dvo.PrtnrGeoInfoId["partner"]->
org.abc.dvo.Prtnr["prtnrGeoInfos"]->org.hibernate.collection.PersistentSet[0]->org.abc.dvo.PrtnrGeoInfo["id"]->org.abc.dvo.PrtnrGeoInfoId["partner"]->
...
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:164)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:446)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:150)
...
The Prtnr class is :
public class Prtnr implements Cloneable, java.io.Serializable {
private static final long serialVersionUID = 201207021420600052L;
private Integer prtnrId;
private String creatUserId;
private Date creatTs;
private String updtUserId;
private Date updtTs;
private String prtnrNm;
private Integer cncilNum;
private Character prtnrTypCd;
private Set<PrtnrGeoInfo> prtnrGeoInfos = new HashSet<PrtnrGeoInfo>(0);
private Set<PrtnrDtl> prtnrDtls = new HashSet<PrtnrDtl>(0);
private Set<SuplyDtl> suplyDtls = new HashSet<SuplyDtl>(0);
private Set<TrnsprtDtl> trnsprtDtls = new HashSet<TrnsprtDtl>(0);
private Set<PrtnrFacil> prtnrFacils = new HashSet<PrtnrFacil>(0);
private Set<PrtnrHumanResrc> prtnrHumanResrcs = new HashSet<PrtnrHumanResrc>(0);
.....
.....
Getters and setters for these properties
...
}
The PrtnrGeoInfo class is :
public class PrtnrGeoInfo implements java.io.Serializable {
private PrtnrGeoInfoId id = new PrtnrGeoInfoId();
private String creatUserId;
private Date creatTs;
private String updtUserId;
private Date updtTs;
Getters and setters for these properties
}
The PrtnrGeoInfoId class is :
public class PrtnrGeoInfoId implements java.io.Serializable {
private Prtnr partner;
private GeoSegment geoSegment;
private static final long serialVersionUID = 201207060857580050L;
Getters and setters for these properties
}
I believe it is because of the classes refrencing each other. But how can this problem be resolved. Within the app which is Struts 2 and Spring, this object get passed just fine.
The controller class is as follows:
#Controller
#RequestMapping("/partners")
public class PartnerController {
#RequestMapping(value="/{id}", method=RequestMethod.GET, headers ={"Accept=text/xml,application/json"})
#ResponseBody
public Prtnr getPartner(#PathVariable("id") String id) throws Exception{
Prtnr partner = null;
try{
partner = partnerService.getPartnerById(Integer.valueOf(id));
System.out.println("******* Test message " );
}catch(Exception ex){
System.out.println("******* Exception thrown ... " + ex.getMessage());
}
return partner;
}
}
The calling class is
public class TestTemplate
{
private static final long serialVersionUID = 1130201273334264152L;
public static void main(String[] args){
Prtnr partner = (Prtnr)new RestTemplate().getForObject("http://localhost:9080/respondersApp/testWs/partners/{id}", Prtnr.class, "1");
System.out.println("partner name is : " + partner.getPrtnrNm());
}
}
In this link you can find how to solve this.
However below I'll paste the solution in practice.
It's very simple. Assuming that your database query already works without JSON, all you have to do is this:
Add the #JsonManagedReference In the forward part of the relationship (i.e. User.java class):
#Entity
public class User implements java.io.Serializable{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#Column(name="name")
private String name;
#ManyToMany
#JoinTable(name="users_roles",joinColumns=#JoinColumn(name = "user_fk"),
inverseJoinColumns=#JoinColumn(name = "role_fk"))
#JsonManagedReference
private Set<Role> roles = new HashSet<Role>();
...
Add the #JsonBackReference In the back part of the relationship (i.e. Role.java class):
#Entity
public class Role implements java.io.Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#ManyToMany(mappedBy="roles")
#JsonBackReference
private Set<User> users = new HashSet<User>();
...
The work is done. If you take a look at your firebug logs, you'll notice that the infinite recursive loop has disappeared.
This is quite a common scenario for me when you are trying to convert entity classes into JSON format. The simplest solution is just to use #JsonIgnore on the reverse mapping to break the cycle.
You can annotate the second reference of Prtnr in PrtnrGeoInfoId with #JsonBackReference
The infinite recursion is due to the following:
Class Prtnr contains Set<PrtnrGeoInfo> prtnrGeoInfos and each PrtnrGeoInfo contains PrtnrGeoInfoId id which in turn contains Prtnr partner.
Thus, Prtnr -> PrtnrGeoInfo ->PrtnrGeoInfoId ->Prtnr, is causing a cyclic dependency which is a problem for Jackson when it is trying to do the POJO Mapping.
You need to remove this cyclic dependency to fix this exception.
Related
I'm having a lazy initialization exception using spring. I know this is a common error and I've been through several stack questions, but none of the answers really did it for me. Here's my scenario: I have two classes that relate like such:
public class Foo implements Serializable {
#Id
#EqualsAndHashCode.Include
#Column(name = "uuid")
private UUID uuid;
#Column(name = “attribute”)
private String attribute;
#OneToMany(fetch = FetchType.LAZY, mappedBy = “foo”)
private Set<Bar> bar;
}
public class Bar implements Serializable {
#Id
#Column(name = "uuid")
private UUID uuid;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = “foo_uuid")
private Foo foo;
}
I have a rest endpoint that lists all Bar objects. In that specific endpoint, I need to return attribute, which is a Foo attribute. Since that is not required inside the application business logic, it seemed unnecessary to add attribute to Bar as well. So I have a BarWrapper class, which is a custom format of Bar, to be returned by the endpoint:
#Getter
#Setter
#NoArgsConstructor
public class BarWrapper {
…
private String attribute;
public BarWrapper(final Bar bar) {
//The next line throws lazy initialization exception.
this.attribute = bar.getFoo().getAttribute()
}
I have tried #Transactional on all classes, and didn't work. I tried to add cascade = CascadeType.ALL, which did work, but is not a good practice. I have also tried creating a custom function just for this, but didn't help either:
#Transactional
private String extractAttribute(final Bar bar){
final Foo foo = bar.getFoo();
return foo.getAttribute();
}
How can I overcome this Lazy initialization exception?
EDIT:
This is how I'm calling the BarWrapper constructor:
#AllArgsConstructor
#Service
#Slf4j
public class BarApplicationServices {
private final FooService fooService;
private final BarService barService;
public BarWrapper createBar(final CreateBarRequestBody requestBody) {
final Foo foo = fooService.findFooToBeSettled(requestBody.getFooUuid());
final Bar createdBar = barService
.createBar(new Bar(foo));
return new BarWrapper(createdBar);
}
}
Your #Transactional method is private which does not work. Make it public.
If this does not work, try the following link
I have these Objects:
#Data
#Entity
#Table
#EqualsAndHashCode(callSuper = true)
public class User extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -55089179131569489L;
private String username;
private String email;
private boolean admin;
private String name;
private String surname;
#OneToMany(mappedBy = "owner")
private List<Ad> ads;
}
and
#Entity
#Table
#Data
#EqualsAndHashCode(callSuper = true)
public class Ad extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -4590938091334150254L;
private String name;
private String description;
private double price;
#Enumerated(EnumType.STRING)
private Category category;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "OWNER_ID")
private User owner;
}
When I try to execute a POST with an object of type Ad.class with inside an existing object of type User.class (already in the Database) the service saves only the Ad object and the join column "OWNER_ID" remains empty.
I think that the mapping is correct. Could you help me to figure out the problem?
This is my Repository:
#Repository
#Transactional(readOnly = true)
public interface AdRepository extends PagingAndSortingRepository<Ad, String>
{}
and this is my RestRepository
#RepositoryRestResource(collectionResourceRel = "ad", path = "ad")
public interface AdRestRepository extends PagingAndSortingRepository<Ad, String> {}
If I step back a little and generalize your problem,
You are trying to POST a sub resource and expect both actions of
making a new resource (Ad)
making association with the owner (User)
to be happened with a single call.
But unfortunately spring-data-rest does not support such a behavior. You need 2 calls to do this.
One to make the resource (Ad) => POST to /ads with actual payload
Second to make the association => POST to users/{ownerId} with the hateoas link of the resource created by the first call.
Take a look at this section of official documentation.
I have tried several things I found while searching but nothing helped or I did not implement it correctly.
Error I'm getting
Direct self-reference leading to cycle (through reference chain: io.test.entity.bone.Special["appInstance"]->io.test.entity.platform.ApplicationInstance["appInstance"])
Both these extend the base entity and in the base (super class) it has an appInstance as well.
Base entity looks similar to this
#MappedSuperclass
public abstract class BaseEntity implements Comparable, Serializable {
#ManyToOne
protected ApplicationInstance appInstance;
//getter & setter
}
Application entity looks like this
public class ApplicationInstance extends BaseEntity implements Serializable {
private List<User> users;
// some other properties (would all have the same base and application instance . User entity will look similar to the Special.)
}
Special entity
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "objectType")
#JsonIgnoreProperties({"createdBy", "appInstance", "lastUpdatedBy"})
public class Special extends BaseEntity implements Serializable {
#NotNull
#Column(nullable = false)
private String name;
#Column(length = Short.MAX_VALUE)
private String description;
#NotNull
#Column(nullable = false)
private Double price;
#OneToOne
private Attachment image;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = SpecialTag.class)
#CollectionTable(name = "special_tags")
#Column(name = "specialtag")
private List<SpecialTag> specialTags;
#Temporal(TemporalType.TIME)
private Date specialStartTime;
#Temporal(TemporalType.TIME)
private Date specialEndTime;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = WeekDay.class)
#CollectionTable(name = "available_week_days")
#Column(name = "weekday")
private List<WeekDay> availableWeekDays;
#OneToMany(mappedBy = "special", cascade = CascadeType.REFRESH)
private List<SpecialStatus> statuses;
#OneToMany(mappedBy = "special", cascade = CascadeType.REFRESH)
private List<SpecialReview> specialReviews;
#Transient
private Integer viewed;
private Boolean launched;
#OneToMany(mappedBy = "special")
private List<CampaignSpecial> specialCampaigns;
#Override
#JsonIgnore
public ApplicationInstance getAppInstance() {
return super.getAppInstance();
}
}
All entities in Special inherits from BaseEntity which contains AppInstance
then i have a method to get the special
#GET
#Path("{ref}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(value = MediaType.TEXT_PLAIN)
public Special findByGuestRef(#PathParam("ref") String pRefeference) {
// find the special and return it
return special;
}
On the special entity I tried the following
Added jsonIgnoreProperties
Added an override for appInstance to annotate with #JsonIgnore
#JsonIdentityInfo
links for the above
https://stackoverflow.com/a/29632358/4712391
Jackson serialization: how to ignore superclass properties
jackson self reference leading to cycle
none of those solutions works. Am I doing something wrong?
Note: Would it also just be possible to edit special, since the other entities are in a different package and would not like to edit them.
Usually excluding attributes in a response is as easy as adding a #JsonIgnore annotation to their getters, but if you don't want to add this annotation to a parent class, you could override the getter and then add the annotation on it:
public class Special extends BaseEntity implements Serializable {
...
#JsonIgnore
public ApplicationInstance getAppInstance() {
return this.appInstance;
}
...
}
NOTE: As there are several frameworks, make sure that you are using the correct #JsonIgnore annotation or it will be ignored, see this answer for instance.
Another option, more "manual", is just creating a bean for the response which would be a subset of the Special instance:
#GET
#Path("{ref}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(value = MediaType.TEXT_PLAIN)
public SpecialDTO findByGuestRef(#PathParam("ref") String pRefeference) {
// find the special and return it
return new SpecialDTO(special);
}
public class SpecialDTO {
//declare here only the attributes that you want in your response
public SpecialDTO(Special sp) {
this.attr=sp.attr; // populate the needed attributes
}
}
To me, problem seems to be in the Special object and the fields being initialized in it.
I guess that there is a circular reference detected when serialisation happens.
Something similar to:
class A {
public A child;
public A parent;
}
A object = new A();
A root = new A();
root.child = object;
object.parent = root;
In the above code, whenever you will try to seralize either of these objects, you will face the same problem.
Note that public fields are not recommended.
I'll suggest to peek into your Special object and the references set in it.
I have a write converter as
public class CarConverter implements Converter<Car, DBObject> {
#Override
public final DBObject convert(final Car car) {
DBObject dbo = new BasicDBObject();
dbo.put("_id", car.getId());
// below line produces error
dbo.put("wheels", car.getWheels());
return dbo;
}
}
and my Car.java as
#Document(collection = "car")
public class Car implements Serializable {
private static final long serialVersionUID = 6121244806685144430L;
#Id private String id;
private List<Wheel> wheels;
// getters and setters
}
and my Wheel.java as (It is not document , just a bean)
public class Wheel implements Serializable {
private static final long serialVersionUID = 6121244806685144430L;
private String wheelId;
private String name;
//getters and setters
}
I got the below error when I try to save my Car object to mongodb.
SLF4J: Failed toString() invocation on an object of type [com.mongodb.BasicDBObject]
java.lang.RuntimeException: json can't serialize type : class com.mypackage.Wheel
at com.mongodb.util.ClassMapBasedObjectSerializer.serialize(ClassMapBasedObjectSerializer.java:77)
at com.mongodb.util.JSONSerializers$IterableSerializer.serialize(JSONSerializers.java:290)
at com.mongodb.util.ClassMapBasedObjectSerializer.serialize(ClassMapBasedObjectSerializer.java:79)
at com.mongodb.util.JSONSerializers$MapSerializer.serialize(JSONSerializers.java:317)
at com.mongodb.util.ClassMapBasedObjectSerializer.serialize(ClassMapBasedObjectSerializer.java:79)
at com.mongodb.util.JSON.serialize(JSON.java:55)
at com.mongodb.util.JSON.serialize(JSON.java:40)
at com.mongodb.BasicDBObject.toString(BasicDBObject.java:83)
at org.slf4j.helpers.MessageFormatter.safeObjectAppend(MessageFormatter.java:276)
at org.slf4j.helpers.MessageFormatter.deeplyAppendParameter(MessageFormatter.java:248)
at org.slf4j.helpers.MessageFormatter.arrayFormat(MessageFormatter.java:206)
at org.slf4j.helpers.MessageFormatter.format(MessageFormatter.java:148)
at org.slf4j.impl.Log4jLoggerAdapter.info(Log4jLoggerAdapter.java:341)
INFO : org.springframework.data.mongodb.core.mapping.event.LoggingEventListener - onBeforeSave:
com.mypackage.Car#1e4ec58, [FAILED toString()]
java.lang.IllegalArgumentException: can't serialize class com.mypackage.Wheel
at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:284)
at org.bson.BasicBSONEncoder.putIterable(BasicBSONEncoder.java:309)
at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:248)
at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:185)
at org.bson.BasicBSONEncoder.putObject(BasicBSONEncoder.java:131)
at com.mongodb.DefaultDBEncoder.writeObject(DefaultDBEncoder.java:33)
at com.mongodb.OutMessage.putObject(OutMessage.java:289)
at com.mongodb.OutMessage.writeUpdate(OutMessage.java:180)
at com.mongodb.OutMessage.update(OutMessage.java:60)
at com.mongodb.DBCollectionImpl.update(DBCollectionImpl.java:275)
at com.mongodb.DBCollection.update(DBCollection.java:191)
at com.mongodb.DBCollection.save(DBCollection.java:975)
at com.mongodb.DBCollection.save(DBCollection.java:934)
Please somebody help me , what is the problem ? How can I fix it ?
I ran into this same error. My Document looked like this:
#Document
public class AnalyticsDailyData implements Serializable {
private static final long serialVersionUID = -5409790992784008124L;
#Id
#NotNull
#Valid
private AnalyticsMetaData metaData;
...
Adding an actual id field of type ObjectId made the error go away for me. I then set the metaData field to be indexed and unique.
#Document
public class AnalyticsDailyData implements Serializable {
private static final long serialVersionUID = -5409790992784008124L;
private ObjectId id;
#Indexed(unique = true)
#NotNull
#Valid
private AnalyticsMetaData metaData;
...
I am still able to query by metaData after adding a method to my Repo:
public interface AnalyticsDailyDataRepo extends PagingAndSortingRepository<AnalyticsDailyData, ObjectId> {
AnalyticsDailyData findOneByMetaData(AnalyticsMetaData meta);
}
I am trying to use ORMLite to represent comments in a conversation, like this:
#DatabaseTable(tableName = "comments")
public class Comment implements Parcelable{
#DatabaseField(id = true)
private Long id;
#DatabaseField
private Long conversation_id;
#DatabaseField
private String text;
...
public static class List extends ArrayList<Comment>{
}
}
...and...
#DatabaseTable(tableName = "conversations")
public class Conversation implements Parcelable{
#DatabaseField(id = true)
private Long id;
...
#ForeignCollectionField
private Comment.List comments;
#DatabaseField
private Date created_at;
...
}
And I am getting this error:
Field class for 'comments' must be of class ForeignCollection or
Collection
I am also using GSON so these models are populated automatically from json. For example:
{
"created_at":"2013-08-12T20:38:11Z",
"id":31,
"comments":[
{
"conversation_id":31,
"id":46,
"text":"IE sucks",
},
{
"conversation_id":31,
"id":47,
"text":"Yes it does",
}
]
}
Is there a way to achieve this just by changing descriptors?
Is it necessary to rework the Conversation class to use ForeignCollection as a type for comments or change Comment.List class to extend ForeignCollection? I'd like to avoid doing any of those because I am afraid it would break GSON implementation which currently works fine.
In Comment class:
...
#DatabaseField(
foreign = true
)
private Conversation conversation_id;
...
The conversation_id will actually only store id of the Conversation object, not the object itself.
There is a really good (if somehow unformatted) documentation here: http://ormlite.com/docs/foreign-object