views:

239

answers:

4

I have a parent (Program) pojo with a many-to-many relationship with their children (Subscriber).

The problem is when it serialises a Program, it also serialises the Program's Subscribers, which involves serialising their Programs, which involves serialising their Subscribers, until it has serialised every single Program & Subscriber in the database.

The ERD looks like: Program <-> Subscriber

This means what was a tiny 17KB block of data (json) being returned has become a 6.9MB return. Thus in turn blows out the time to serialise the data and then return it.

Why is my parent returning children returning parents returning children? How can I stop this so I only get the Subscribers for each Program? I'm assuming I've done something wrong with my annotations, perhaps? I would like to maintain a many-to-many relationship but without this deeply nested data retrieval.

(Note: I have prior tried adding as many Lazy annotations I can find just to see if that helps. It doesn't. Perhaps I'm doing that wrong too?)

Program.java

@Entity
@Table(name="programs")
public class Program extends Core implements Serializable, Cloneable {
   ...
   @ManyToMany()
   @JoinTable(name="program_subscribers",
         joinColumns={@JoinColumn(name="program_uid")},
         inverseJoinColumns={@JoinColumn(name="subscriber_uid")})
   public Set<Subscriber> getSubscribers() { return subscribers; }
   public void setSubscribers(Set<Subscriber> subscribers) { this.subscribers = subscribers; }

Subscriber.java

@Entity
@Table(name="subscribers")
public class Subscriber extends Core implements Serializable {
   ...
   @ManyToMany(mappedBy="subscribers")
   public Set<Program> getPrograms() { return programs; }
   public void setPrograms(Set<Program> programs) { this.programs = programs; 

}

Implementation

public Collection<Program> list() {
  return new Programs.findAll();
}
+2  A: 

You didn't mention the framework you are using for JSON serialization, so I'll assume JAXB. Anyway, the idea is to make the Subscriber.getPrograms(..) transient in some way, so that it's not serialized. Hibernate takes care of these 'loops', but others don't. So:

@XmlTransient
@ManyToMany(..)
public Set<Program> getPrograms()...

If you use another framework, it may have a different annotation/configuration for specifying transient fields. Like the transient keyword.

The other way is to customize your mapper to handle the cycle manually, but this is tedious.

Bozho
It's a hand me down project, I'm not 100% what's used for JSON serialisation. I see references to com.sdicons.json.mapper.JSONMapper.SimpleMapperHelper. And we have a method public JSONValue toJSON(Object pojo) throws MapperException {}. (guess it's roll your own serialisation)
Ben Clark-Robinson
Also if I annotate getPrograms() @XmlTransient this would mean I could never have subscriber programs returned?
Ben Clark-Robinson
yes. The other was is to customize your mapper to take care of the cycle manually.
Bozho
+1  A: 

Hello,

1) How does "your" serialization work. I mean is it JAXB or custom serialization or smth else. 2) Almost all frameworks let you set the depth of serialization. I mean you can set for example depth in 2. 3) I advice you not to serialize object with children, mark them(childre) transient, and serialize separately.

ponkin
A: 

Both Bozho and ponkin are on the right track. I needed to stop serialising the data down the wire but the big problem is I am unable to change the pojo -> toJSON class/method where the serialisation takes place. I was also worried about investing time on the toJSON() method considering I was taking such a performance hit at the point of serialisation I wanted a fix that would occur before I had the data rather than afterwards.

Also due to the nature of the Many-to-Many Bidirectional design I had listed I was always going to have this cyclic programs/subscribers/programs/... problem.

Resolution: (for now atleast) I have removed the Subscriber.getProgram() method and created a finder method on the ProgramDAO which returns the Programs by Subscriber.

public List<Program> findBySubscriber(Subscriber subscriber) {
  String hql = "select p " +
     "from Program p " +
     " join p.subscribers s " +
     "where s = :sub"
     ;    

  Query q = getSession().createQuery(hql);
  q.setEntity("sub", subscriber);

  List<Program> l = q.list();
  return l;
}

For any CRUD work I think I'm just going to have to loop over Programs.getSubscribers, or write more hql helper methods.

Ben Clark-Robinson