Jackson JSON infinite recursion without omiting anything from the serialization - json

I want to serialize and eventually deserialize an object to perform an export/import operation. I use Jackson library because of the extend annotation provided. I do break the infinite recursion by using the latest tags #JsonManagedReference, #JsonBackReference. But the problem here #JsonBackReference does omit the annotated part from the json file so I am not able to set the relationship while importing.
The relationship btwn entities can be shown:
public class A{
#Id
#Column(name = "ID", unique = true, precision = 20)
#SequenceGenerator(name = "a_generator", sequenceName =
"SEQ_A", initialValue = 1, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"a_generator")
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "metricDefinition",
fetch = FetchType.LAZY, orphanRemoval = true)
#Fetch(FetchMode.SELECT)
#NotAudited
#JsonManagedReference
private Set<B> bSet= new HashSet<B>();
}
public class B{
#Id
#Column(name = "id", unique = true, precision = 20)
#SequenceGenerator(name = "b_generator", sequenceName = "seq_b", initialValue = 1, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "b_generator")
private Long id;
#ManyToOne(cascade = {CascadeType.REFRESH})
#JoinColumn(name = "a_id")
#Fetch(FetchMode.SELECT)
#JsonBackReference(value = "a-b")
private A a;
#ManyToOne(cascade = {CascadeType.REFRESH})
#JoinColumn(name = "ref_a_id")
#Fetch(FetchMode.SELECT)
#JsonBackReference(value = "a-ref")
private A refA;
#Column(name = "is_optional")
#Fetch(FetchMode.SELECT)
private boolean isOptional;
#Column(name = "name")
#Fetch(FetchMode.SELECT)
private String name;
When I serialize any A object, it does serialize the B's included but the referenced A and refA are omitted. So, when I import A object of course the B's are also imported but I do want to the relationship between the objects to be set.
Is there any idea how can I break the infinite recursion without omitting the one side of the reference?
Thanks in advance

I did also try to use statement below according to answers given similar questions but it did not work so I asked the question above.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "#id")
it does not sufficient to break the circle. You should also add below annotation to the id property of your class.
#JsonProperty("id")

We can try to break the loop either at the Parent end or at the Child end by following 3 ways
Use #JsonManagedReference and #JsonBackReference
Use #JsonIdentityInfo
Use #JsonIgnore
Use #JsonIdentityInfo
#Entity
#Table(name = "nodes")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Node {
...
}
#Entity
#Table(name = "relations")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Relation {
...
}
Refer more in detail here with the working demo at the end.

Related

Getting parent_id for nested children

I'm new to JPA and I'm stumped on how to capture the parent id param for nested children. I have two entities, Data and Property. Right now I successfully managed to create a one to many mapping from Data to Property. The thing is that Property can have its own children of itself. The "first layer" properties are able to get and save the data id value, but properties nested in another property are unable to do so. I was wondering if there is a method to do something like this since I could not find any information on it online.
Data (parent class)
#Entity
#Table(name = "data")
public class Data implements Serializable {
// omitted non relevant code
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#OneToMany(targetEntity = EventProperty.class, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "data_id", referencedColumnName = "id")
private List<EventProperty> properties;
}
Property (child class)
#Entity
#Table(name = "property")
public class Property implements Serializable {
private static final long serialVersionUID = -1100374255977700675L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Integer id;
#ManyToOne
#JoinColumn(name = "data_id", referencedColumnName = "id")
private EventData eventData;
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private EventProperty parent;
#OneToMany(targetEntity = EventProperty.class, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private List<EventProperty> children;
}
I'm wondering if storing the properties as a List in Data is the right way to go, or if there's any way to set data_id of a child Property to be the same as the parent Property if it is null, or if there's some other correct way that I am not aware of. Any help would be greatly appreciated!

Can I use #Where annotation along with #ManytoOne association?

EER Diagram
I am not an expert in Spring, JPA, Hibernate or MySql.
However I am using all for a web service supporting RESTful calls.
I am building a store management app backend with Spring.
My entities at this point of time are StoreModel, StoreUserModel, StoreUserRoleModel and StoreUserAuthModel.
I have setup bidirectional relationships(OneToMany and ManyToOne) between
StoreModel - StoreUserAuthModel,
StoreUserMode - StoreUserAuthModel and
StoreUserRoleMode - StoreUserAuthModel.
I dont want the foreign key constraint though there are foreign key fields storeid, roleid and userid in StoreUserAuthModel.
Now All the four tables have isdeleted column to implement soft delete.
I am lazy fetching the associations. However I dont want the softdeleted values whenever i query the associations.
I would like to know if I can use #Where annotation along with the #ManyToOne annotation in the StoreUserAuthModel entity?
The issue is different from How to use #Where in Hibernate because my problem is with ManyToOne annotation whereas I have used the where annotation with OneToMany
#Entity
#Table(name = "store")
public class StoreModel {
#NotBlank
private String name;
#NotBlank
private String address;
#NotBlank
private String city;
#NotBlank
private String phone;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "storeid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ))
#Where(clause="isdeleted = 0")
private List<StoreUserAuthModel> authList = new ArrayList<StoreUserAuthModel>();
...
}
#Entity
#Table(name = "storerole")
public class StoreRoleModel {
#NotBlank
private String name;
#NotBlank
private Integer rolehierarchy;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "roleid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ))
#Where(clause="isdeleted = 0")
private List<StoreUserAuthModel> authList = new ArrayList<StoreUserAuthModel>();
...
}
#Entity
#Table(name = "storeuser")
public class StoreUserModel{
#NotBlank
#Column(unique = true)
private String username;
#Email
#Column(unique = true)
private String useremail;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "userid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ))
#Where(clause="isdeleted = 0")
List<StoreUserAuthModel> userAuthList = new ArrayList<StoreUserAuthModel>();
...
}
#Entity
#Table(name = "storeuserauth",
uniqueConstraints = #UniqueConstraint(columnNames = {"storeid", "roleid", "userid"}))
public class StoreUserAuthModel {
#NotNull
Long storeid;
#NotNull
Long roleid;
#NotNull
Long userid;
// Using #where to filter out the soft deleted storeuser
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="userid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ),insertable = false, updatable = false )
#Where(clause="isdeleted = 0")
private StoreUserModel storeuser;
// Using #where to filter out the soft deleted store
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="storeid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ),insertable = false, updatable = false )
#Where(clause="isdeleted = 0")
private StoreModel store;
// Using #where to filter out the soft deleted role
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="roleid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ),insertable = false, updatable = false )
#Where(clause="isdeleted = 0")
private StoreRoleModel role;
...
}
// In the controller, Following code shows how I plan to use
Optional<StoreUserModel> aUser = storeUserRepository.findByUseremailAndIsdeleted(zUserMail), 0);
if(aUser.isPresent()) {
// The user was found!!!
// Testing...
// Getting the User Auth List (that will filter out the soft deleted auths)
List<StoreUserAuthModel> authList = aUser.get().getUserAuthList();
for(StoreUserAuthModel auth :authList) {
StoreModel store = auth.getStore();
// here both soft deleted store as well as normal stores are shown.
// ie where clause on store relation is not working!!
logger.debug("Store is "+store.getName());
}
}
...
Now all the store rows matching the id are in the list.
The expected result should apply where clause too
I turned on logging for hibernate 5.3.9
There is no where clause when it fires the select query
The #Where annotation has no effect on ToOne relationships. But instead of adding #Where to the reference you can use #Where on the Entity:
#Where(clause="isdeleted = 0")
#Entity
#Table(name = "storerole")
public class StoreRoleModel {
That way no deleted entities of StoreRoleModel will be loaded by Hibernate.

JsonView returning empty json objects

I am trying to implement a JsonView to selectively serialize fields from an entity but the json that is serialized has empty objects with no fields. Below is my code:
ViewClass:
public class AuditReportView {
public interface Summary {}
}
Entity:
#Entity
#SequenceGenerator(name = "AUDIT_REPORT_SEQUENCE_GENERATOR", sequenceName = "EJB_AUDIT_REPORT_SEQ", initialValue = 1, allocationSize = 1)
#Table(name = "DEVICE_AUDIT_REPORT")
#Data
public class AuditReport implements Serializable {
private static final long serialVersionUID = 1246376778314918671L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "AUDIT_REPORT_SEQUENCE_GENERATOR")
#Column(name = "ID", nullable = false)
#JsonView(AuditReportView.Summary.class)
private Long id;
#Column(name = "DEVICE_ID", nullable = false)
#JsonView(AuditReportView.Summary.class)
private String deviceId;
#Column(name = "REPORT_TIMESTAMP", nullable = false)
#JsonView(AuditReportView.Summary.class)
private Calendar reportTimestamp;
#Column(name = "USER_ID", nullable = false)
#JsonView(AuditReportView.Summary.class)
private long userId;
#Column(name = "USERNAME", nullable = false)
#JsonView(AuditReportView.Summary.class)
private String username;
#Column(name = "START_DATE", nullable = false)
#JsonView(AuditReportView.Summary.class)
private Calendar startDate;
#Column(name = "END_DATE", nullable = false)
#JsonView(AuditReportView.Summary.class)
private Calendar endDate;
#OneToMany(mappedBy = "auditReport", fetch = FetchType.LAZY, orphanRemoval = true, cascade={CascadeType.ALL})
private Set<AuditEntry> auditEntries = new HashSet<AuditEntry>();
}
Controller:
#JsonView(AuditReportView.Summary.class)
#RequestMapping(method = RequestMethod.GET, value = "auditReportSummary")
public #ResponseBody ResponseEntity<?> getAuditReportSummary()
{
final List<AuditReport> auditReports = auditDAO.getAuditReportSummary();
return new ResponseEntity<>(auditReports, HttpStatus.OK);
}
Json from Postman:
[
{},
{},
{}
]
The database only has 3 results and when I debug it is definately pulling them out, it is just that no members are being serialized. I'm using Spring 4.3.7 and Jackson 2.8.7. Any ideas of what could be wrong or where to start debugging the issue?
Thanks
You must create getters and setters methods for attributes. I did it and it worked.
I guess the issue is due to the #ResponceBody ResponseEntity<?>
Please try with the following code :
#JsonView(AuditReportView.Summary.class)
#RequestMapping(method = RequestMethod.GET, value = "auditReportSummary" produces = MediaType.APPLICATION_JSON_VALUE)
public List<AuditReport getAuditReportSummary()
{
final List<AuditReport> auditReports = auditDAO.getAuditReportSummary();
return auditReports;
}
I am not much sure about it, but you can try if it works..
Try adding a default constructor - ex:
public AuditReport() {}
The default constructor is generated by the java compiler if no custom constructor is specified in the code. However if a custom constructor is specified, the default constructor is no longer automatically added which can break serialization libraries / spring, etc..
BUT - you haven't specified a constructor - how could this be?
One thing I noticed is that you're using Lombok - due to the Data annotation. Lombok can generate constructors for classes. So its possible one of the annotations or libraries you're using is adding a constructor, making the compiler skip generation of a default constructor, which may be breaking your serialization.
So, I hope adding a default constructor works out for you.

Could not write content: failed to lazily initialize a collection of role

I have One-To-Many relationship, here is my code
#Entity
#Table(name = "catalog")
public class Catalog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "catalog_id")
private int catalog_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy="mycatalogorder")
private List<Order> orders;
#OneToMany(mappedBy="mycatalog")
private List<CatalogItem> items;
// setters and getters
}
#Entity
#Table(name = "catalogitem")
public class CatalogItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "catalogitem_id")
private int catalogitem_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#NotEmpty
#Column(name = "price", nullable = false)
private Double price;
#OneToOne(mappedBy="ordercatalogitem", cascade=CascadeType.ALL)
private OrderItem morderitem;
#ManyToOne
#JoinColumn(name="catalog_id", nullable=false)
private Catalog mycatalog;
// setters and getters
}
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private int order_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#NotEmpty
#Size(min = 3, max = 1024)
#Column(name = "note", nullable = false)
private String note;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "ddmmYYYY HH:mm:ss")
#Column(name = "created", nullable = false)
private Date created;
#OneToMany(mappedBy="myorder")
private Set<OrderItem> orderItems;
#ManyToOne
#JoinColumn(name="catalog_id", nullable=false)
private Catalog mycatalogorder;
#PrePersist
protected void onCreate() {
created = new Date();
}
// setters and getters
}
#Entity
#Table(name = "orderitem")
public class OrderItem {
#Id
#Column(name="catalogitem_id", unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="catalogitem"))
private int catalogitem_id;
#Column(name = "quantity")
private int quantity;
#OneToOne
#PrimaryKeyJoinColumn
private CatalogItem ordercatalogitem;
#ManyToOne
#JoinColumn(name="order_id", nullable=false)
private Order myorder;
// setters and getters
}
And I am getting the exception:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write content: failed to lazily initialize a collection of
role: com.example.helios.model.Catalog.items, could not initialize
proxy - no Session; nested exception is
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily
initialize a collection of role:
com.example.helios.model.Catalog.items, could not initialize proxy -
no Session
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:271)
org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:100)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:222)
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:183)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:80)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
My versions is:
SpringFramework 4.2.4.RELEASE
Hibernate 4.3.11.Final
Jackson 2.7.4
Jacksontype 2.7.1
This is the normal Hibernate behaviour
In one to many relations, hibernate loads the father entity (Catalog in your case) but it will load the children entities List (List items and List orders in your case) in a LAZY mode
This means you can't access to these objects because they are just proxies and not real objects
This is usefull in order to avoid to load the full DB when you execute a query
You have 2 solution:
Load children entities in EAGER mode (I strongly suggest to you to not do it because you can load the full DB.... but it is something related to your scenario
You don't serialize in your JSON the children entities by using the com.fasterxml.jackson.annotation.JsonIgnore property
Angelo
A third option which can be useful if you don't want to use EAGER mode and load up everything is to use Hibernate::initialize and only load what you need.
Session session = sessionFactory.openSession();
Catalog catalog = (Catalog) session.load(Catalog.class, catalogId);
Hibernate.initialize(shelf);
More information
I had the same problem but a fixed by:
#OneToMany
#JoinColumn(name = "assigned_ingredient", referencedColumnName = "ingredient_id")
#Fetch(FetchMode.JOIN) // Changing the fetch profile you can solve the problem
#Where(clause = "active_ind = 'Y'")
#OrderBy(clause = "meal_id ASC")
private List<Well> ingredients;
you can have more information here: https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/
It's caused by an infinite loop when parsing datas to JSON.
You can solve this by using #JsonManagedReference and #JsonBackReference annotations.
Definitions from API :
JsonManagedReference (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonManagedReference.html) :
Annotation used to indicate that annotated property is part of two-way
linkage between fields; and that its role is "parent" (or "forward")
link. Value type (class) of property must have a single compatible
property annotated with JsonBackReference. Linkage is handled such
that the property annotated with this annotation is handled normally
(serialized normally, no special handling for deserialization); it is
the matching back reference that requires special handling
JsonBackReference: (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonBackReference.html):
Annotation used to indicate that associated property is part of
two-way linkage between fields; and that its role is "child" (or
"back") link. Value type of the property must be a bean: it can not be
a Collection, Map, Array or enumeration. Linkage is handled such that
the property annotated with this annotation is not serialized; and
during deserialization, its value is set to instance that has the
"managed" (forward) link.
Example:
Owner.java:
#JsonManagedReference
#OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
Set<Car> cars;
Car.java:
#JsonBackReference
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "owner_id")
private Owner owner;
Another solution is to use #JsonIgnore which will just set null to the field.
Here is my solution for this task with Hibernate. I marked hibernate releation with #JsonIgnore and use custom field for jackson, in which I check if the field is loaded. If you need serialize collection to json then you should manualy call collection getter during hibernate transaciton.
#JsonIgnore
#OneToMany(mappedBy = "myorder")
private List<OrderItem> orderItems = new ArrayList<>();
#JsonProperty(value = "order_items", access = JsonProperty.Access.READ_ONLY)
private List<OrderItem> getOrderItemsList() {
if(Hibernate.isInitialized(this.relatedDictionary)){
return this.relatedDictionary;
} else{
return new ArrayList<>();
}
}
#JsonProperty(value = "order_items", access = JsonProperty.Access.WRITE_ONLY)
private void setOrderItemsList(List<OrderItem> orderItems) {
this.orderItems = orderItems;
}
I know this is an old post but this might still help someone facing a similar issue. To solve the problem, iterate through the list of items and set the lazy-loadable collection to null. Then set your mapper to include NON-NULL
for (Catalog c : allCatalogs) {
c.setItems(null);
}
objectMapper.setSerializationInclusion(Include.NON_NULL)
Using FetchType.LAZY , if still getting the error "Could not write content: failed to lazily initialize a collection of role" , that may be probably caused by somewhere in the logic (perhaps in a controller) , Catalog is being tried to be deserialized that contains list of catalog items which is a proxy but the transaction has already ended to get that.
So create a new model ('CatalogResource' similar to catalog but without the list of items).
Then create a catalogResource object out of the Catalog (which is returned from the query)
public class CatalogResource {
private int catalog_id;
private String name;
private List<Order> orders;
}
I think the best solution to your problem (which also is the simplest) is to set your FetchType to LAZY and simply annotate the oneToMany collection fields using #transient.
Setting FetchType to EAGER isn't a good idea most times.
Best of luck.
"You don't serialize in your JSON the children entities by using the com.fasterxml.jackson.annotation.JsonIgnore property"
Add #JsonIgnore for hibernate lazy loading properties eg. #ManyToOne. That should work

JPQL #ManyToMany querying

I need to write two different yet similar JPQL queries and currently lack the expertise. I really hope one can help.
Return all items associated with one or more tags, by querying the current Tag class and returning the collection of items?
Return all items that holds a reference to the current Tag class, by querying over every item and comparing with the collection of tags?
Additional questions:
When i add a tag to the collection of an item, do the item automatically get added to the collection in the tag?
Any ideas on how i can sort the returned items depending on the number of tags they match? Can i include this in the JPQL query?
Would it be better for my Tag class to have the String keyword as #id?
My code:
#Entity
#NamedQueries({
#NamedQuery(name = Item.FIND_ALL, query = "select i from Item i")
})
#TableGenerator(name = "Item_ID_Generator", table = "ITEM_ID_GEN", pkColumnName = "PRIMARY_KEY_NAME",
pkColumnValue = "Item.id", valueColumnName = "NEXT_ID_VALUE")
public class Item implements Serializable {
public static final String FIND_ALL = "Item.findAll";
// private static final long serialVersionUID = 1L;
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.TABLE, generator = "Item_ID_Generator")
private Integer id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
#JoinTable(name = "jnd_item_tag",
joinColumns = #JoinColumn(name = "item_fk"),
inverseJoinColumns = #JoinColumn(name = "tag_fk"))
private List<Tag> tags = new ArrayList();
}
#Entity
public class Tag implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(unique = true)
private String keyword;
#ManyToMany(mappedBy = "tags")
private List<Item> referencedByItem;
}
Execute the following query, which will return the matched tags sets, and the matched tags:
SELECT i, t FROM Item i JOIN i.tags t WHERE t.keyword IN :listOfTags
I this case can be this way:
#NamedQuery(name = "selectByTags", query = "SELECT i, t FROM Item i JOIN i.tags t WHERE t.keyword IN :listOfTags")
//and here you need set your list
List<Tag> tags = daoTags.getListTags();
query.setParameter("listOfTags", listOfTags);
I think that adapting this query solves both requests for your question.