Mappage d'une table d'association plusieurs à plusieurs avec des colonnes supplémentaires

131

Ma base de données contient 3 tables: les entités utilisateur et service ont une relation plusieurs-à-plusieurs et sont jointes à la table SERVICE_USER comme suit:

UTILISATEURS - SERVICE_USER - SERVICES

La table SERVICE_USER contient une colonne BLOCKED supplémentaire.

Quelle est la meilleure façon d'effectuer une telle cartographie? Ce sont mes classes d'entité

@Entity
@Table(name = "USERS")
public class User implements java.io.Serializable {

private String userid;
private String email;

@Id
@Column(name = "USERID", unique = true, nullable = false,)
public String getUserid() {
return this.userid;
}

.... some get/set methods
}

@Entity
@Table(name = "SERVICES")
public class CmsService implements java.io.Serializable {
private String serviceCode;

@Id
@Column(name = "SERVICE_CODE", unique = true, nullable = false, length = 100)
public String getServiceCode() {
return this.serviceCode;
}
.... some additional fields and get/set methods
}

J'ai suivi cet exemple http://giannigar.wordpress.com/2009/09/04/m ... using-jpa / Voici un code de test:

User user = new User();
user.setEmail("e2");
user.setUserid("ui2");
user.setPassword("p2");

CmsService service= new CmsService("cd2","name2");

List<UserService> userServiceList = new ArrayList<UserService>();

UserService userService = new UserService();
userService.setService(service);
userService.setUser(user);
userService.setBlocked(true);
service.getUserServices().add(userService);

userDAO.save(user);

Le problème est que la mise en veille prolongée persiste l'objet User et UserService one. Aucun succès avec l'objet CmsService

J'ai essayé d'utiliser la récupération EAGER - aucun progrès

Est-il possible d'obtenir le comportement que j'attends avec le mappage fourni ci-dessus?

Peut-être existe-t-il un moyen plus élégant de mapper plusieurs à plusieurs tables de jointure avec une colonne supplémentaire?

archie_by
la source

Réponses:

192

Étant donné que la table SERVICE_USER n'est pas une table de jointure pure, mais qu'elle a des champs fonctionnels supplémentaires (bloqués), vous devez la mapper en tant qu'entité et décomposer l'association plusieurs à plusieurs entre l'utilisateur et le service en deux associations OneToMany: un utilisateur a de nombreux services utilisateur, et un Service a de nombreux UserServices.

Vous ne nous avez pas montré la partie la plus importante: la cartographie et l'initialisation des relations entre vos entités (c'est-à-dire la partie avec laquelle vous avez des problèmes). Je vais donc vous montrer à quoi cela devrait ressembler.

Si vous rendez les relations bidirectionnelles, vous devriez donc avoir

class User {
    @OneToMany(mappedBy = "user")
    private Set<UserService> userServices = new HashSet<UserService>();
}

class UserService {
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne
    @JoinColumn(name = "service_code")
    private Service service;

    @Column(name = "blocked")
    private boolean blocked;
}

class Service {
    @OneToMany(mappedBy = "service")
    private Set<UserService> userServices = new HashSet<UserService>();
}

Si vous ne mettez aucune cascade sur vos relations, vous devez conserver / enregistrer toutes les entités. Bien que seul le côté propriétaire de la relation (ici, le côté UserService) doit être initialisé, il est également recommandé de s'assurer que les deux côtés sont cohérents.

User user = new User();
Service service = new Service();
UserService userService = new UserService();

user.addUserService(userService);
userService.setUser(user);

service.addUserService(userService);
userService.setService(service);

session.save(user);
session.save(service);
session.save(userService);
JB Nizet
la source
2
Juste pour ajouter .. Bien que ce soit à mon avis le meilleur moyen (je préfère toujours mapper la chose possédant le FK en tant qu'entité pour des raisons de performances), ce n'est en fait pas le seul moyen. Vous pouvez également mapper les valeurs de la table SERVICE_USER en tant que composant (ce que JPA appelle un intégrable) et utiliser une @ElementCollectiondes entités Utilisateur et Service (ou les deux).
Steve Ebersole
6
Qu'en est-il de la clé primaire de la table UserService? Il doit s'agir de la combinaison de clés étrangères utilisateur et de service. Est-ce cartographié?
Jonas Gröger
24
Je ne le ferais pas. Les clés composites sont douloureuses, inefficaces et Hibernate recommande de ne pas utiliser de clés composites. Utilisez simplement un identifiant généré automatiquement comme pour toute autre entité, et la vie sera beaucoup plus simple. Pour garantir l'unicité de [userFK, serviceFK], utilisez une contrainte unique.
JB Nizet
1
@GaryKephart: posez votre propre question, avec votre propre code et votre propre mapping.
JB Nizet
1
@gstackoverflow: Hibernate 4 ne change rien à cet égard. Je ne vois vraiment pas en quoi c'est inélégant.
JB Nizet le
5

Je recherche un moyen de mapper une table d'association plusieurs-à-plusieurs avec des colonnes supplémentaires avec mise en veille prolongée dans la configuration des fichiers xml.

En supposant qu'il existe deux tables 'a' et 'c' avec une association plusieurs à plusieurs avec une colonne nommée 'extra'. Parce que je n'ai trouvé aucun exemple complet, voici mon code. J'espère que cela aidera :).

Voici d'abord les objets Java.

public class A implements Serializable{  

    protected int id;
    // put some others fields if needed ...   
    private Set<AC> ac = new HashSet<AC>();

    public A(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Set<AC> getAC() {
        return ac;
    }

    public void setAC(Set<AC> ac) {
        this.ac = ac;
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        final int prime = 97;
        int result = 1;
        result = prime * result + id;
        return result;
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof A))
            return false;
        final A other = (A) obj;
        if (id != other.getId())
            return false;
        return true;
    }

}

public class C implements Serializable{

    protected int id;
    // put some others fields if needed ...    

    public C(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        final int prime = 98;
        int result = 1;
        result = prime * result + id;
        return result;
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof C))
            return false;
        final C other = (C) obj;
        if (id != other.getId())
            return false;
        return true;
    }

}

Maintenant, nous devons créer la table d'association. La première étape consiste à créer un objet représentant une clé primaire complexe (a.id, c.id).

public class ACId implements Serializable{

    private A a;
    private C c;

    public ACId() {
        super();
    }

    public A getA() {
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }
    public C getC() {
        return c;
    }
    public void setC(C c) {
        this.c = c;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((a == null) ? 0 : a.hashCode());
        result = prime * result
                + ((c == null) ? 0 : c.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ACId other = (ACId) obj;
        if (a == null) {
            if (other.a != null)
                return false;
        } else if (!a.equals(other.a))
            return false;
        if (c == null) {
            if (other.c != null)
                return false;
        } else if (!c.equals(other.c))
            return false;
        return true;
    }
}

Créons maintenant l'objet d'association lui-même.

public class AC implements java.io.Serializable{

    private ACId id = new ACId();
    private String extra;

    public AC(){

    }

    public ACId getId() {
        return id;
    }

    public void setId(ACId id) {
        this.id = id;
    }

    public A getA(){
        return getId().getA();
    }

    public C getC(){
        return getId().getC();
    }

    public void setC(C C){
        getId().setC(C);
    }

    public void setA(A A){
        getId().setA(A);
    }

    public String getExtra() {
        return extra;
    }

    public void setExtra(String extra) {
        this.extra = extra;
    }

    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        AC that = (AC) o;

        if (getId() != null ? !getId().equals(that.getId())
                : that.getId() != null)
            return false;

        return true;
    }

    public int hashCode() {
        return (getId() != null ? getId().hashCode() : 0);
    }
}

À ce stade, il est temps de mapper toutes nos classes avec la configuration hibernate xml.

A.hbm.xml et C.hxml.xml (la même chose).

<class name="A" table="a">
        <id name="id" column="id_a" unsaved-value="0">
            <generator class="identity">
                <param name="sequence">a_id_seq</param>
            </generator>
        </id>
<!-- here you should map all others table columns -->
<!-- <property name="otherprop" column="otherprop" type="string" access="field" /> -->
    <set name="ac" table="a_c" lazy="true" access="field" fetch="select" cascade="all">
        <key>
            <column name="id_a" not-null="true" />
        </key>
        <one-to-many class="AC" />
    </set>
</class>

<class name="C" table="c">
        <id name="id" column="id_c" unsaved-value="0">
            <generator class="identity">
                <param name="sequence">c_id_seq</param>
            </generator>
        </id>
</class>

Et puis le fichier de mappage d'association, a_c.hbm.xml.

<class name="AC" table="a_c">
    <composite-id name="id" class="ACId">
        <key-many-to-one name="a" class="A" column="id_a" />
        <key-many-to-one name="c" class="C" column="id_c" />
    </composite-id>
    <property name="extra" type="string" column="extra" />
</class>

Voici l'exemple de code à tester.

A = ADao.get(1);
C = CDao.get(1);

if(A != null && C != null){
    boolean exists = false;
            // just check if it's updated or not
    for(AC a : a.getAC()){
        if(a.getC().equals(c)){
            // update field
            a.setExtra("extra updated");
            exists = true;
            break;
        }
    }

    // add 
    if(!exists){
        ACId idAC = new ACId();
        idAC.setA(a);
        idAC.setC(c);

        AC AC = new AC();
        AC.setId(idAC);
        AC.setExtra("extra added"); 
        a.getAC().add(AC);
    }

    ADao.save(A);
}
Zhar
la source
1

Comme indiqué précédemment, avec JPA, pour avoir la possibilité d'avoir des colonnes supplémentaires, vous devez utiliser deux associations OneToMany, au lieu d'une seule relation ManyToMany. Vous pouvez également ajouter une colonne avec des valeurs générées automatiquement; de cette façon, il peut fonctionner comme clé primaire de la table, si cela est utile.

Par exemple, le code d'implémentation de la classe supplémentaire devrait ressembler à ceci:

@Entity
@Table(name = "USER_SERVICES")
public class UserService{

    // example of auto-generated ID
    @Id
    @Column(name = "USER_SERVICES_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long userServiceID;



    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "USER_ID")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "SERVICE_ID")
    private Service service;



    // example of extra column
    @Column(name="VISIBILITY")    
    private boolean visibility;



    public long getUserServiceID() {
        return userServiceID;
    }


    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Service getService() {
        return service;
    }

    public void setService(Service service) {
        this.service = service;
    }

    public boolean getVisibility() {
        return visibility;
    }

    public void setVisibility(boolean visibility) {
        this.visibility = visibility;
    }

}
ingitalie
la source