4

I'm using Spring(xml+annotations), Hibernate(annotations) in this web service project. The database relationship diagram, models, expected and actual output are given below,

Database Table relationship

Customer.java

@Entity
@Table(name="customer")
public class Customer implements Serializable{
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="customer_id", unique=true, nullable =false)
    long customerId;
    @Column(name="name")
    String name;
    @Column(name="secondary_name")
    String secondaryName;
    @Column(name="date")
    Date date;
    @Column(name="address")
    String address;
    @Column(name="post")
    String post;
    @Column(name="pin")
    String pin;
    @Column(name="phone")
    String phone;
    @OneToMany(fetch=FetchType.LAZY, mappedBy="customer", cascade=CascadeType.ALL)
    @JsonManagedReference
    Set<Loan> loans = new HashSet<Loan>();
    //constructors, getters and setters
}

Loan.java

public class Loan implements Serializable{
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="loan_id", nullable=false, unique=true)
    long loanId;
    @ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    @JoinColumn(name="customer_id", nullable = false)
    @JsonBackReference
    Customer customer;
    @Column(name="date", nullable=false)
    Date date;
    @Column(name="amount", nullable=false)
    double amount;
    @OneToMany(fetch=FetchType.LAZY, mappedBy="loan", cascade=CascadeType.ALL)
    @JsonManagedReference
    List<Item> items = new ArrayList<Item>();
    //constructors, getters, setters
}

Item.java

public class Item implements Serializable{
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="item_id", nullable=false, unique=true)
    long itemId;
    @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    @JoinColumn(name="loan_id", nullable = false)
    @JsonBackReference
    Loan loan;
    @Column(name="name", nullable=false)
    String name;
    @Column(name="weight", nullable=false)
    double weight;
    //constructors, setters, getters
}

Actual output:Here, customer details are not shown

{  
   "loanId":4,
   "date":1484937000000,
   "amount":10000.0,
   "items":[  
      {  
         "itemId":3,
         "name":"Item1",
         "weight":10.0
      },
      {  
         "itemId":4,
         "name":"Item2",
         "weight":20.0
      }
   ]
}

Expected output: need to display customer details also when looking for a loan

{  
   "loanId":4,
   "customer":{  
      "customerId":2,
      "name":"Prem",
      "address":"Street,State"
   },
   "date":1484937000000,
   "amount":10000.0,
   "items":[  
      {  
         "itemId":3,
         "name":"Item1",
         "weight":10.0
      },
      {  
         "itemId":4,
         "name":"Item2",
         "weight":20.0
      }
   ]
}

I can able to fetch the customer details from the database and fail to load it using Jackson Json. If I remove @JsonManagedReference, I end up with circular loop. If I remove @JsonBackReference, no effects in the output. Complete code at: https://github.com/liwevire/TM_Service Thanks in advance.

liwevire
  • 782
  • 2
  • 9
  • 21

3 Answers3

16

Because you are using the @JsonBackReference on the Customer property in the Loan entity, the Customer object will not included in the serialization. Use the @JsonManagedReference for the Customer in the Loan object and use @JsonBackReference on the Loan property in the Customer entity.

This will serialize the Customer property of your Loan entity. But the Customer object serialization will not contains the Loan property. You need to pick one side of the relationship to serialize.

To allow both side, use @JsonIdentityInfo annotation in your entity and remove the @JsonBackReference and @JsonManagedReference. You entities will be something like:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "customerId")
public class Customer implements Serializable {
    ...
}

The property of the @JsonIdentityInfo refer to your entity id property, for Customer this will be customerId. Do this for Loan and Item also.

W-S
  • 516
  • 5
  • 14
  • 1
    Can't I have both? Coz, sometimes I want to know the loans that customer have. Sometimes I want who is tagged for a loan. Anyways I can manage the situation at the cost of bit increased traffic between applications. – liwevire Jan 23 '17 at 07:23
  • You can use the `@JsonIdentityInfo` annotation. Remove all `@JsonBackReference` and `@JsonManagedReference` from the entities. And add `@JsonIdentityInfo` for `Customer`, `Loan` and `Item`. – W-S Jan 23 '17 at 07:46
  • 1
    Output below `{"loanId":2,"customer":{"customerId":1,"name":"Premkumar","secondaryName":"T","date":1484663297000,"address":"Sethu Road","post":"PVI","pin":"614804","phone":"9876543210","loans":[2,{"loanId":3,"customer":1,"date":1484591400000,"amount":20.0,"items":[{"itemId":2,"loan":3,"name":"L2","weight":1.0}]},{"loanId":4,"customer":1,"date":1484937000000,"amount":10000.0,"items":[{"itemId":3,"loan":4,"name":"Item1","weight":10.0},{"itemId":4,"loan":4,"name":"Item2","weight":20.0}]}]},"date":1484591400000,"amount":40.0,"items":[{"itemId":1,"loan":2,"name":"PT1","weight":1.0}]}` – liwevire Jan 24 '17 at 16:39
  • This makes the output little bulky. It pulls the maximum details it can from the db. Thinking of querying the details when needed. Anyways thanks @W-S. – liwevire Jan 24 '17 at 16:45
  • @liwevire I have one query. Ex: I want to get list of Organizations with enterprise. I used JsonIdentityInfo, and getting like this: { "organizations": [ { "organizationId": 1, "organizationName": "org1", "enterprise": { "enterpriseId": 1, "enterpriseName": "ent1", "organizations": null } }, { "organizationId": 2, "organizationName": "org2", "enterprise": 1 } ] } – Krish Nov 03 '17 at 14:56
  • 1
    @W-S For first occurence its building whole object and for next occurences its referring that ID. But I want whole Object. How to do that ? – Krish Nov 03 '17 at 14:57
  • @Krishna: In my case there are 3 identities involved; customer, loan and item. Customer(1)->Loan(n), Loan(1)->Item(n). Have applied JsonIdentityInfo on every entity classes. Getting Loan details will have Items obviously, but also the customer details. I would recommend JsonBackReference and Json ManagedReference. Also in your case, there are only two entities. – liwevire Nov 03 '17 at 15:14
  • 1
    @liwevire you mistaken me. What I was asking is, for the particular Enterprise we can get the list of organizations (unique records). But If I want to traverse from Child its not working properly i.e., list of organizations with enterprise. For first occurrence of enterprise its building whole enterprise object and for next occurrences (org1 and org2 are associated with Ent1) its referring that ID. But I want whole Object. How to do achieve that? – Krish Nov 03 '17 at 16:21
  • @Krishna In case your app have js front-end, this might help you. https://stackoverflow.com/questions/34568490/jackson-jsonidentityinfo-object-instead-of-id – liwevire Nov 03 '17 at 17:27
  • 1
    @liwevire No, our job is to implement the APIs. and distribute them to multiple clients. I can't say them to use these id refs. Is there any way to avoid these IDs and get entire object in both directions (Parent->Child, Child->Parent) ? it is very helpful to me. – Krish Nov 03 '17 at 17:48
  • 2
    After a lot of tries, @JsonIgnoreProperties solved my problem – Krish Nov 06 '17 at 12:10
  • I know it's an old thread, but I am getting the same issue, as I am not getting the parent information, i.e. Customer object within loan object, for above case, no matter what I do. I tried with JsonIgnoreProperties, switching JsonManagedReference and Backreference but nothing helped. Surprisingly, the Spring server-side internally contains Customer entity within Loan, as I try to print out the toString of each Loan, but when I hit the API through browser or from JS front-end, I am not getting. Any help would be appreciated. – tanmayghosh2507 Dec 09 '18 at 04:35
  • @tanmayghosh2507 - did you get a solution to your problem? I have the same thing and it seems a very reasonable thing to want. – John Jan 28 '20 at 12:03
  • @John - I think I had to restructure my data schema. Don't remember any direct solution to this problem. It's been quite long. Will check and let you know if I find the solution from previous code. – tanmayghosh2507 Jan 28 '20 at 20:10
  • Life saving answer. – Philippe Gioseffi Aug 06 '20 at 00:12
1

This seems pretty old but let me put my coins here as well; I would seperate the entity and model. Means;

> Client <-> Application : Models
> 
> Application <-> Database : Entities

And your service layer or whatever layer you process data should make the conversion between entities and models.

  1. You get rid of recursion by returning data as your wish.
  2. You split the definitions between two different communication channels. This way you can decide what to show to your client and how to show your client as well. This will save your DB schema be exposed directly too.
  3. You can extend model as per your wish without touching to the DB backend.
Olgun Kaya
  • 2,519
  • 4
  • 32
  • 46
1

Here is a bit lengthy approach. But needs some design modifications in your app. I had a similar problem and I created separate pojos for every entity classes.

In service layer I use these pojos instead of the entity objects as parameters and I use appropriate getters/setters to set/get the properties to/from entity classes. In this way you can get/set the properties you want to and avoid unwanted ones. However I implemented additional methods in DAO layer to get the related entities. This is very lengthy approach but solved the problem for me.

abhijit12
  • 11
  • 2