Bonsoir,


J'ai un problème avec JBoss/Hibernate concernant les relations "manyToMany"...

J'ai 2 beans entités:
- user
- role

En toute logique, un "user" peut posséder 1 ou plusieurs "role" ... Pareil pour un "role"; je dirais meme plus: cela peut varier de 0 à plusieurs ...

Donc j'ai créé mes beans entité de la sorte:

User.java:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
53
54
55
56
57
58
59
60
61
62
63
64
//by loopx
//08/01/08
 
package be.loopx.pixmania.ejb.entity;
 
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
 
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
 
@Entity
@Table(name="user")
public class User implements Serializable {
	private static final long serialVersionUID = 1L;
 
	private int id;
	private String login;
	private String password;
	private List<Role> roles=new ArrayList<Role>();
 
	public void setLogin(String login) {
		this.login = login;
	}
	@Column(length=20, nullable=false, unique=true)
	public String getLogin() {
		return login;
	}
 
	public void setPassword(String password) {
		this.password = password;
	}
	@Column(length=20, nullable=false)
	public String getPassword() {
		return password;
	}
 
	public void setId(int id) {
		this.id = id;
	}
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	public int getId() {
		return id;
	}
 
	public void setRoles(List<Role> roles) {
		this.roles=roles;
	}
 
	@ManyToMany(mappedBy="users", cascade={CascadeType.ALL})
	public List<Role> getRoles() {
		return this.roles;
	}
}
Role.java:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
package be.loopx.pixmania.ejb.entity;
 
import java.io.Serializable;
 
@Entity
@Table(name="role")
public class Role implements Serializable {
	private static final long serialVersionUID = 1L;
 
	private int id;
	private String name;
	private List<User> users=new ArrayList<User>();
 
	public void setName(String n) {
		this.name = n;
	}
	@Column(length=50, nullable=false, unique=true)
	public String getName() {
		return name;
	}
 
	public void setId(int id) {
		this.id = id;
	}
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	public int getId() {
		return id;
	}
 
	public void setUsers(List<User> users) {
		this.users = users;
	}
	@ManyToMany
	public List<User> getUsers() {
		return users;
	}
}
J'ai ajouté (dans les 2 entités) des List contenant les beans entité qui lui sont lié.

J'ai eu une erreur pendant un moment, je viens de comprendre ce que c'était. Voici l'erreur:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
 
01:56:30,346 ERROR [LazyInitializationException] failed to lazily initialize a collection of role: be.loopx.pixmania.ejb.entity.User.roles, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: be.loopx.pixmania.ejb.entity.User.roles, no session or session was closed
        at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:358)
        at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:350)
        at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:97)
        at org.hibernate.collection.PersistentBag.size(PersistentBag.java:225)
        at be.loopx.pixmania.ejb.facade.UserFacadeBean.addRoleToUser(UserFacadeBean.java:63)
En fait, un module web (.war) est client des EJB (plus précisément, une servlet). Celle-ci communique via ce schéma:

Servlet => privateEJB(session stateful) => userEJB_facade(session stateless) => entity => base de donnée

Voici la méthode "addRoleToUser" de la facade "UserFacadeBean":
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
public void addRoleToUser(Role role, User user) {		
		role.getUsers().add(user);	//add 1 user to the role
		user.getRoles().add(role);	//add 1 role to the user
 
                em.merge(user);
	}
Cette méthode est appelée par le bean stateful "privateEJB" dont voici un extrait:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
 
//bind administrator to admin
		usersList=userEJB_facade.listFiltered("where o.login='admin'");
		rolesList=roleEJB_facade.listFiltered("where o.name='administrator'");
		System.out.print(this.getClass().getName()+"> USER="+usersList.get(0).getLogin()+" / ROLE="+rolesList.get(0).getName());	
 
		userEJB_facade.addRoleToUser(rolesList.get(0), usersList.get(0));
Ce code est situé dans le constructeur du bean session stateful. Quand un client se connecte au module Web (via le navigateur), la servlet est instanciée. Dans chaque session HTTP, le bean privateEJB est injecté.

Pour les tests (actuellement), lors de l'instanciation du bean privateEJB, je crée le user "admin" et le role "administrator": aucun problème (dans la bd, j'ai tout ce qu'il faut, y compris une table "role_user" contenant (enfin, ne contenant encore rien) les liens entre user et role.

La méthode "addRoleToUser" est la pour pour lié ces 2 entités. Seulement voilà, j'ai l'erreur de "lazy" cité plus haut. J'ai trouvé pourquoi: dans la facade utilisateur (dans la méthode "addRoleToUser"), je récupère 2 beans entités. Ceux-ci sont considéré comme "non attaché" ce qui pose donc le problème de "lazy". Pour tester, j'ai ajouter cette ligne dans la méthode d'ajout d'un role:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
user=findById(user.getId());	//re-attach user
AVANT le:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
user.getRoles().add(role);	//add 1 role to the user
de manière à re-attacher le bean entité ... L'erreur est passé ... enfin non, puisque j'ai le meme souci avec le bean entité "role" passé à la méthode:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
02:07:30,991 WARN  [arjLoggerI18N] [com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator_2] TwoPhaseCoordinator.beforeCompletion - failed for com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple@1a46497
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: be.loopx.pixmania.ejb.entity.Role


Alors voilà, je me rend compte de ceci: je ne peux pas faire un "findById" sur chaque entité dans la facade utilisateur (userEJB_facade), logique (regardez le schéma ci-dessous

privateEJB => *EJB_facade => Entity => base de donnée
Il m'est impensable de faire l'injection de la facade "role" dans la facade "user"! Donc, le "truc" de l'ajout de la ligne permettant de ratacher le bean entité "user" dans la facade utilisateur est donc à annuler. Il faut que cela se passe AVANT l'appel à la facade!


J'essaie donc de modifier le bean privateEJB (son constructeur) de manière à ce qu'il soit ainsi:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
//bind administrator to admin
		usersList=userEJB_facade.listFiltered("where o.login='admin'");
		rolesList=roleEJB_facade.listFiltered("where o.name='administrator'");
		System.out.print(this.getClass().getName()+"> USER="+usersList.get(0).getLogin()+" / ROLE="+rolesList.get(0).getName());		
 
		User user=userEJB_facade.findById(usersList.get(0).getId());
		//userEJB_facade.addRoleToUser(rolesList.get(0), usersList.get(0));
		userEJB_facade.addRoleToUser(rolesList.get(0), user);
en conclusion, avant l'appel à "addRoleToUser", j'essaie de re-attacher le user. J'ai plusieurs question à ce sujet:
- pourquoi est-ce que la méthode de recherche (avec du code EJBQL) n'a t'elle pas attaché le bean entité USER et ROLE ??
- pourquoi est-ce que un "findById" juste avant la facade ne fonctionne pas ?

Parce que le problème est bien la: impossible d'attacher le bean USER (trouvé avec la requette login=admin) AVANT l'appel à la méthode "addRoleToUser". Je bloque, pour tout vous dire, ca fait 1 semaine que je me casse la tête, et rien n'avance. Je pense que j'ai un problème de logique dans le développement ou alors, un problème de "relation" entre bean.

Je précise que l'erreur (après avoir ajouté le findbyid du user dans le constructeur du bean privateEJB) est exactement la meme que au tout début (donc, le findbyid placé dans privateEJB ne fonctionne pas, alors que si je l'appel quand je suis dans la facade, cela fonctionne).


Voilà, je suis perdu, je pense que je n'y arriverais jamais sans une petite aide de votre part. Donc, je vous remercie d'avance de me lire.

EDIT: j'ai d'autre question:
- qu'elle est la méthode "correct" pour attaché un bean ? (findById ?)
- est-ce que la méthode utilisée pour "lier" 2 beans entité est correct ?
- un ptit tuto peut etre, avec exemple parce que, je bloque...

EDIT2: voici une mini partie des logs, pour ceux que ca intéresse ...
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
02:49:12,031 INFO  [[/web_base]] be.loopx.pixmania.servlet.ControlServlet> All context have been initialized
02:49:12,062 INFO  [STDOUT] be.loopx.pixmania.ejb.session.PrivateBean> All context have been initialized
02:49:12,246 INFO  [STDOUT] be.loopx.pixmania.ejb.session.PrivateBean> USER=admin / ROLE=administrator
actuellement, après ces logs, j'ai les erreurs cité plus haut