Comrades, I write a web service on Java, the client part in C #, I also use the hibernate framework. Model class example:

@Entity @Table(name = "garages") public class Garage implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private int id; @Column(name = "description") private String decription; @ManyToOne(cascade = CascadeType.REFRESH) @JoinColumn(name = "owner_id") private Owner owner; @OneToMany(cascade = CascadeType.ALL, mappedBy = "garage") private List<Car> cars; public Garage() { } public Garage(int id, String decription, Owner owner, List<Car> cars) { this.decription = decription; this.owner = owner; this.cars = cars; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getDecription() { return decription; } public void setDecription(String decription) { this.decription = decription; } public Owner getOwner() { return owner; } public void setOwner(Owner owner) { this.owner = owner; } public List<Car> getCars() { return cars; } public void setCars(List<Car> cars) { this.cars = cars; } } @Entity @Table(name = "owners") public class Owner implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private int id; @Column(name = "name") private String name; @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner") private List<Car> ownerCars; @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner") private List<Garage> ownerGarages; public Owner() { } public Owner(int id, List<Car> ownerCars, List<Garage> ownerGarages) { this.id = id; this.ownerCars = ownerCars; this.ownerGarages = ownerGarages; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public List<Car> getOwnerCars() { return ownerCars; } public void setOwnerCars(List<Car> ownerCars) { this.ownerCars = ownerCars; } public List<Garage> getOwnerGarages() { return ownerGarages; } public void setOwnerGarages(List<Garage> ownerGarages) { this.ownerGarages = ownerGarages; } } 

DAO implementation:

 public class OwnerDaoImpl implements OwnerDao { private Session session = null; @Override public void addOwner(Owner owner) { openSessionAndBeginTransaction(); session.save(owner); closeSessionAndCommit(); } @Override public ArrayList<Owner> getAllOwners() { openSessionAndBeginTransaction(); List<Owner> owners = session.createQuery("from Owner").list(); closeSessionAndCommit(); return (ArrayList)owners; } @Override public Owner getOwnerForGarageId(int idGarage) { openSessionAndBeginTransaction(); Query query = session.createQuery("from Garage where id = :param"); query.setParameter("param", idGarage); Garage garage = (Garage) query.uniqueResult(); closeSessionAndCommit(); return garage.getOwner(); } @Override public Owner getOwnerForCarId(int idCar) { openSessionAndBeginTransaction(); Query query = session.createQuery("from Car where id = :param"); query.setParameter("param", idCar); Car car = (Car) query.uniqueResult(); closeSessionAndCommit(); return car.getOwner(); } private void openSessionAndBeginTransaction() { session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); } private void closeSessionAndCommit() { session.getTransaction().commit(); session.close(); } @Override public ArrayList<Garage> getListGaragesForOwnerId(int ownerId) { openSessionAndBeginTransaction(); Query query = session.createQuery("from Car where owner_id = :param"); query.setParameter("param", ownerId); List<Garage> result = query.list(); closeSessionAndCommit(); return (ArrayList) result; } @Override public Owner getOwnerForName(String name) { openSessionAndBeginTransaction(); Query query = session.createQuery("from Owner where name = :param"); query.setParameter("param", name); Owner result = (Owner) query.uniqueResult(); return result; } } 

And the service itself:

 @WebService(serviceName = "GaragesServices") public class GarageServices implements CarServiceInterface, GarageServiceInterface, OwnerServiceInterface { CarDao carDao = new CarDaoImpl(); GarageDao garageDao = new GarageDaoImpl(); OwnerDao ownerDao = new OwnerDaoImpl(); Sender postSender = new Sender(); /** * * @param garage */ @Override @WebMethod public void addGarage(@WebParam(name = "garage") Garage garage) { garageDao.addGarage(garage); } /** * * @return */ @Override @WebMethod public ArrayList<Garage> getAllGarages() { return garageDao.getAllGarages(); } /** * * @param ownerId * @return */ @Override @WebMethod public ArrayList<Garage> getListGaragesForOwnerId(@WebParam(name = "ownerId") int ownerId) { return garageDao.getListGaragesForOwnerId(ownerId); } /** * * @param idCar * @return */ @Override @WebMethod public Garage getGarageForCarId(@WebParam(name = "idCar") int idCar) { return garageDao.getGarageForCarId(idCar); } /** * * @param owner */ @Override @WebMethod public void addOwner(@WebParam(name = "owner") Owner owner) { ownerDao.addOwner(owner); } /** * * @return */ @Override @WebMethod public ArrayList<Owner> getAllOwners() { return ownerDao.getAllOwners(); } /** * * @param idGarage * @return */ @Override @WebMethod public Owner getOwnerForGarageId(@WebParam(name = "idGarage") int idGarage) { return ownerDao.getOwnerForGarageId(idGarage); } /** * * @param idCar * @return */ @Override @WebMethod public Owner getOwnerForCarId(@WebParam(name = "idCar") int idCar) { return ownerDao.getOwnerForCarId(idCar); } /** * * @param car */ @Override @WebMethod public void addCar(@WebParam(name = "car") Car car) { carDao.addCar(car); } /** * * @return */ @Override @WebMethod public ArrayList<Car> getCars() { return carDao.getCars(); } /** * * @param ownerId * @return */ @Override @WebMethod public ArrayList<Car> getListCarsForOwnerId(@WebParam(name = "ownerId") int ownerId) { return carDao.getListCarsForOwnerId(ownerId); } /** * * @param name * @return */ @Override @WebMethod public ArrayList<Car> getListCarsForOwnerName(@WebParam(name = "name") String name) { return carDao.getListCarsForOwnerName(name); } /** * * @param idGarage * @return */ @Override @WebMethod public ArrayList<Car> getListCarsForGarageId(@WebParam(name = "idGarage") int idGarage) { return carDao.getListCarsForGarageId(idGarage); } /** * Операция веб-службы * @param owner */ @WebMethod(operationName = "sendMailToAdministrator") public void sendMailToAdministrator(@WebParam(name = "owner") Owner owner) { postSender.send(owner); } /** * Операция веб-службы */ @Override @WebMethod(operationName = "getAllGaragesForOwnerName") public ArrayList<Garage> getAllGaragesForOwnerName(@WebParam(name = "name") String name) { return garageDao.getListGaragesForOwnerName(name); } @Override @WebMethod public Owner getOwnerByName(@WebParam(name = "name")String name) { return ownerDao.getOwnerForName(name); } 

}

Client side code:

 public partial class Form1 : Form { private ClientServiceManagement client = new ClientServicesGarageManagement(); private garage[] garagesClient = null; private car[] carsInGarage = null; private String header = "Пользователь не найден!"; private String emptyField = "Пустое поле!"; private String emptyMessage = "Пожалуйста, введите данные для поиска!"; private garage garageChoosen = null; private String error = "Заполните поле марки машины или выберите гараж для добавления"; private owner user = null; private MessageBoxButtons buttons; private DialogResult result; public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { comboBox1.Items.Clear(); listView1.Items.Clear(); if (validateSearchBox(textBox1.Text.Length)) { user = client.getOwnerByName(textBox1.Text); if(user == null) { String message = "Владельца гаража с именем " + textBox1.Text + " не найден. Измените параметры поиска"; buttons = MessageBoxButtons.OK; result = MessageBox.Show(message, header, buttons); if(result == System.Windows.Forms.DialogResult.OK) { textBox1.Text = ""; } } try { label5.Text = user.name; initGaragesCount(user); } catch (NullReferenceException e1) { e1.ToString(); } } else { buttons = MessageBoxButtons.OK; result = MessageBox.Show(emptyMessage, emptyField, buttons); } } private void button2_Click(object sender, EventArgs e) { car car = new car(); if (textBox3.Text != "" && garageChoosen != null) { car.markAndModel = textBox3.Text; car.owner = user; car.garage = garageChoosen; client.addCar(car); initializeGarage(garageChoosen.id); } else { buttons = MessageBoxButtons.OK; result = MessageBox.Show(error, emptyField, buttons); } } private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { int indexChoose = comboBox1.SelectedIndex; garageChoosen = garagesClient[indexChoose]; initializeGarage(garageChoosen.id); } private bool validateSearchBox(int length) { if (length != 0) { return true; } return false; } private void clearInformation() { comboBox1.Items.Clear(); listView1.Items.Clear(); } private void initGaragesCount(owner user) { garagesClient = client.getListGaragesByOwnerId(user.id); foreach (garage element in garagesClient) { comboBox1.Items.Add(element.id + " " + element.decription); } } private void initializeGarage(int idGarage) { carsInGarage = client.getCarsByGarageId(idGarage); foreach(car oneCar in carsInGarage) { listView1.Items.Add(oneCar.id + " " + oneCar.markAndModel); } } } 

}

The essence of the problem is that when calling the search owner by name on the side of the service catches an exception

 Warning: StandardWrapperValve[GarageServices]: Servlet.service() for servlet GarageServices threw exception com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: com.ua.model.Owner@795dade6 -> com.ua.model.Garage@2c848766 -> com.ua.model.Owner@795dade6 

And on the client - System.ServiceModel.ProtocolException

  System.Xml.XmlExceptionHelper.ThrowXmlException(XmlDictionaryReader reader, String res, String arg1, String arg2, String arg3) в System.Xml.XmlExceptionHelper.ThrowUnexpectedEndOfFile(XmlDictionaryReader reader) в System.Xml.XmlBaseReader.MoveToEndOfFile() в System.Xml.XmlUTF8TextReader.Read() в System.ServiceModel.Channels.Message.ReadStartBody(XmlDictionaryReader reader, EnvelopeVersion envelopeVersion, Boolean& isFault, Boolean& isEmpty) в System.ServiceModel.Channels.ReceivedMessage.ReadStartBody(XmlDictionaryReader reader) в System.ServiceModel.Channels.BufferedMessage..ctor(IBufferedMessageData messageData, RecycledMessageState recycledMessageState, Boolean[] understoodHeaders, Boolean understoodHeadersModified) в System.ServiceModel.Channels.TextMessageEncoderFactory.TextMessageEncoder.ReadMessage(ArraySegment`1 buffer, BufferManager bufferManager, String contentType) в System.ServiceModel.Channels.MessageEncoder.ReadMessage(Stream stream, BufferManager bufferManager, Int32 maxBufferSize, String contentType) в System.ServiceModel.Channels.HttpInput.ReadChunkedBufferedMessage(Stream inputStream) 

BUT!!! If the entity Owner that I request is not connected with anyone, then there is no exception. As I read the problem is that the collections are heavy. Found solutions, but they did not fit. Maybe something is not right. Help in solving the problem

    1 answer 1

    What's happening?

    You Garage has a link to Owner , and Owner owns a collection of objects of type Garage . While you are working with them on your server, everything is fine, Hibernate handles cyclic object graphs normally.

    When you start giving objects to the client, the web service serializes them to XML, starting from the root, recursively bypassing all the fields. It turns out something like:

     <Owner> <id>1</id> <name>Foo</name> <garages> <Garage> <id>1</id> <description>barbaz</description> <Owner> <id>1</id> <name>Foo</name> <garages> <Garage> <id>1</id> <description>barbaz</description> <Owner> <id>1</id> <name>Foo</name> <garages> <Garage> <id>1</id> <description>barbaz</description> ... бесконечная рекурсия 

    The web service, however, is able to notice such situations, so it simply throws an exception:

     com.sun.istack.SAXException2: A cycle is detected in the object graph 

    And it sends an empty response to the client, to which the client also responds with an exception.


    OK, what to do?

    Option 1: Data Transfer Object

    In general, giving objects to the client directly from the database is not a good idea. As a rule, there is no need to always and immediately all the fields from all the tables.

    For example, to display the list of owners is sufficient code and name, there is no need to drag all their household at once. Then, let's say, by clicking on a specific owner in the list, you can open the form only with its garages and cars.

    Some fields should never reach the client: imagine that you give all users with all passwords.

    To resolve this situation, use the Data Transfer Object (DTO) pattern. For each class of database model or domain model, twins are created with the required set of fields, without JPA / Hibernate annotations. In your case it can be such classes:

     public class GarageListDto { // для списков private int id; private String decription; private String ownerName; private int carsCount; // геттеры и сеттеры } public class GarageDto { // для формы private int id; private String decription; private OwnerListDto owner; private List<CarDto> cars; // геттеры и сеттеры } public class OwnerListDto { private int id; private String name; // геттеры и сеттеры } public class OwnerDto { private int id; private String name; private List<CarDto> ownerCars; private List<GarageDto> ownerGarages; // геттеры и сеттеры } 

    In general, your task is to cut the cycles in the object graph, focusing on the logic of the client. In addition, you will need to overtake the models in the DTO and back on the server. It is possible for each DTO to make a static factory method that accepts the model and returns the DTO. And the opposite method. You can use ready-made solutions for mapping objects into objects, such as Dozer .

    Option 2: Configure XML Serialization

    If for some reason you want to unload the entire object graph to the client (and with this approach you can download the entire database with one awkward query), use the tools to resolve the cycles of the XML serializer.

    In Java, you probably use JAXB. There are three options:

    • Mark one of the @XmlTransient annotated @XmlTransient . Then the recursion on it will not go deep.
    • Add an @XmlID annotation on the key field of the object to which you want to refer and @XmlIDREF on the referenced field. In this case, JAXB will not nest the XML representations of objects to each other, but make a flat list and link them by identifiers.
    • Implement the CycleRecoverable interface in the model classes to programmatically set the behavior when loops are detected.

    See examples here: https://jaxb.java.net/guide/Mapping_cyclic_references_to_XML.html

    • Good answer, but did not help to completely solve the problem. The serialization option does not work. The first option is not fully, the second in general in any way. As a result, I get an org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.ua.model.Garage.cars, could not initialize proxy - no Session in the console org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.ua.model.Garage.cars, could not initialize proxy - no Session and `service invocation threw an exception with message: null; Exceptions details: java.lang.reflect.InvocationTargetException `in the browser. Options exhausted. I do not know what to take next. - Dmitriy Smirnov
    • one
      Obviously you are executing code after closing a transaction, so lazy initialization is of course impossible. If you went the dark way of siphoning the entire database into XML outside the session, you need to either forget about laziness and all links @ManyToOne and @OneToMany expose fetch = FetchType.EAGER , or JAXB to serialize explicitly in the @Transactional method, and send from the controller out already String. - Nofate
    • Why didn't the first option help? There are no other options: no, you either somehow give away a graph of objects with cyclic links, or somehow you terminate these links. - Nofate
    • @Notafe it is likely that the problem was related to laziness, which did not work. Now I'm trying on a new one. By results I will unsubscribe. Everything turned out) I changed the initialization and everything went like clockwork. Thank you) - Dmitriy Smirnov