[JEE 5] JPA OneToMany + EJB3 Stateless + WS = Casse tête
Bonjour à tous,
Je souhaiterais avoir votre avis sur le problème suivant :
A modéliser : tâches ayant plusieurs sous tâches et une tâche parente.
JPA :
Une première classe sert uniquement pour l'id.
Code:
1 2 3
| public class BaseEntity {
private Integer id;
} |
Je ne mets pas volontairement les getters et les setters pour la lisibilité.
Une tâche :
Code:
1 2 3 4 5 6
| public class Task extends BaseEntity {
private Date time;
private String name;
private BaseEntity parent;
private Set<BaseEntity> enfants;
} |
La config :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| [...]
<entity class="BaseEntity">
<attributes>
<id name="id">
<generated-value strategy="IDENTITY" />
</id>
<transient name="new" />
</attributes>
</entity>
<entity class="Task">
<attributes>
<basic name="time">
<temporal>TIME</temporal>
</basic>
<basic name="name"></basic>
<many-to-one name="parent" target-entity="BaseEntity"
fetch="LAZY" />
<one-to-many name="enfants" target-entity="BaseEntity">
<cascade>
<cascade-all/>
</cascade>
</one-to-many>
</attributes>
</entity>
[...] |
EJB/WS :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@WebService(name="ProjectTest", serviceName="ServiceProject")
@SOAPBinding(style=Style.RPC)
public class ServiceProject {
@EJB
private IDaoProject dao;
@WebMethod
public Task[] getAll() {
List<Task> tasks = dao.getAll();
return tasks.toArray(new Task[0]);
}
} |
Petite subtilité : getAll renvoie un Task[] pour pouvoir être une WebMethod.
Une fois que tout est déployé, j'obtiens le wsdl suivant (via l'adresse http://localhost:8080/[dossier de déploiement]/ServiceProject?wsdl) :
(à peu prêt, j'ai taillé dans mon wsdl)
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <definitions name="ServiceProject" targetNamespace="http://service.test.org/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://service.test.org/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<types>
<xs:schema targetNamespace="http://service.test.org/" version="1.0" xmlns:tns="http://service.test.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="task">
<xs:complexContent>
<xs:extension base="tns:baseEntity">
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="enfants" nillable="true" type="tns:baseEntity"/>
<xs:element minOccurs="0" name="name" type="xs:string"/>
<xs:element minOccurs="0" name="parent" type="tns:baseEntity"/>
<xs:element minOccurs="0" name="time" type="xs:dateTime"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="baseEntity">
<xs:sequence>
<xs:element minOccurs="0" name="id" type="xs:int"/>
</xs:sequence>
</xs:complexType>
<xs:complexType final="#all" name="taskArray">
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="item" nillable="true" type="tns:task"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</types>
<message name="ProjectTest_getAll"/>
<portType name="ProjectTest">
<operation name="getAll">
<input message="tns:ProjectTest_getAll"/>
<output message="tns:ProjectTest_getAllResponse"/>
</operation>
</portType>
<binding name="ProjectTestBinding" type="tns:ProjectTest">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="getAll">
<soap:operation soapAction=""/>
<input>
<soap:body namespace="http://service.test.org/" use="literal"/>
</input>
<output>
<soap:body namespace="http://service.test.org/" use="literal"/>
</output>
</operation>
</binding>
<service name="ServiceProject">
<port binding="tns:ProjectTestBinding" name="ProjectTestPort">
<soap:address location="http://127.0.0.1:8080/simexplorer-si-template-ejb/ServiceProject"/>
</port>
</service>
</definitions> |
Et maintennant, si je remplis ma base avec une arborescence de tâches, je peux très bien appeler mon service depuis mon appli web, pour retrouver la liste des tâches. Les enfants de ceux ci ne sont pas initialisés, mais j'ai des BaseEntity avec des id.
Mais si j'appelle getAll en WS, l'appel à mon service va bien se passer, mais lorsque la librairie construit le résultat pour le client, elle va chercher à instancier toute l'arborescence. Et puisqu'on est sorti de la méthode du service (où mon contexte transactionnel me permet le chargement JPA), on a une LazyException.
Le deuxième problème qui est apparu, c'est que lorsque le parent d'un enfant est remonté pour la réponse, il retrouve la même instance. Il constate la récursivité que cela impliquerait et s'arrête.
Pour palier à ce problème, j'ai essayé de passer le parent et les enfants en BaseEntity, pour renvoyer uniquement les ids. Mais l'instance de parent est bien une Task, et le WS l'"introspecte" quand même...
Si vous voyez une issue, je suis preneur ;)
Merci