开发者

Cyclic serialisation with Many to Many relationship with Hibernate

开发者 https://www.devze.com 2022-12-19 06:01 出处:网络
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 inv

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 p开发者_如何学Carent 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();
}


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.


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.


How about using annotations? http://thinkinginsoftware.blogspot.com/2010/08/json-and-cyclical-references.html


From lombok library. Or override equals and hashcode. Use inside hashcode only unique fields (e.g. id).

@EqualsAndHashCode(callSuper = false, of = {"id"})


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号