Zuordnung einer Viele-zu-Viele-Zuordnungstabelle mit zusätzlichen Spalten

130

Meine Datenbank enthält 3 Tabellen: Benutzer- und Service-Entitäten haben eine Viele-zu-Viele-Beziehung und werden wie folgt mit der Tabelle SERVICE_USER verknüpft:

USERS - SERVICE_USER - SERVICES

Die Tabelle SERVICE_USER enthält eine zusätzliche Spalte BLOCKED.

Was ist der beste Weg, um ein solches Mapping durchzuführen? Dies sind meine Entitätsklassen

@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
}

Ich bin diesem Beispiel gefolgt http://giannigar.wordpress.com/2009/09/04/m ... using-jpa / Hier ist ein Testcode:

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);

Das Problem ist, dass der Ruhezustand das Benutzerobjekt und das UserService-Objekt beibehält. Kein Erfolg mit dem CmsService-Objekt

Ich habe versucht, EAGER Fetch zu verwenden - kein Fortschritt

Ist es möglich, das erwartete Verhalten mit der oben angegebenen Zuordnung zu erreichen?

Vielleicht gibt es eine elegantere Möglichkeit, viele Join-Tabellen mit zusätzlichen Spalten zuzuordnen?

archie_by
quelle

Antworten:

192

Da die Tabelle SERVICE_USER keine reine Verknüpfungstabelle ist, sondern zusätzliche Funktionsfelder (blockiert) enthält, müssen Sie sie als Entität zuordnen und die Zuordnung von vielen zu vielen zwischen Benutzer und Dienst in zwei OneToMany-Zuordnungen zerlegen: Ein Benutzer hat viele UserServices, und ein Dienst hat viele UserServices.

Sie haben uns nicht den wichtigsten Teil gezeigt: die Zuordnung und Initialisierung der Beziehungen zwischen Ihren Entitäten (dh den Teil, mit dem Sie Probleme haben). Also zeige ich dir, wie es aussehen soll.

Wenn Sie die Beziehungen bidirektional gestalten, sollten Sie dies also tun

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>();
}

Wenn Sie Ihren Beziehungen keine Kaskade hinzufügen, müssen Sie alle Entitäten beibehalten / speichern. Obwohl nur die besitzende Seite der Beziehung (hier die UserService-Seite) initialisiert werden muss, empfiehlt es sich auch, sicherzustellen, dass beide Seiten kohärent sind.

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
quelle
2
Nur um es hinzuzufügen. Obwohl dies meiner Meinung nach der beste Weg ist (ich ziehe es aus Leistungsgründen immer vor, die Sache, der die FK gehört, als Einheit zuzuordnen), ist dies nicht der einzige Weg. Sie können die Werte aus der Tabelle SERVICE_USER auch als Komponente zuordnen (was JPA als einbettbare Datei bezeichnet) und eine @ElementCollectionvon einer (oder beiden) der Benutzer- und Service-Entitäten verwenden.
Steve Ebersole
6
Was ist mit dem Primärschlüssel der UserService-Tabelle? Es sollte die Kombination von Benutzer- und Dienstfremdschlüsseln sein. Ist das abgebildet?
Jonas Gröger
24
Das würde ich nicht. Zusammengesetzte Schlüssel sind schmerzhaft, ineffizient, und Hibernate empfiehlt, keine zusammengesetzten Schlüssel zu verwenden. Verwenden Sie einfach eine automatisch generierte ID wie für jede andere Entität, und das Leben wird viel einfacher. [userFK, serviceFK]Verwenden Sie eine eindeutige Einschränkung, um die Einheitlichkeit von sicherzustellen .
JB Nizet
1
@ GaryKephart: Stellen Sie Ihre eigene Frage mit Ihrem eigenen Code und Ihrer eigenen Zuordnung.
JB Nizet
1
@gstackoverflow: Hibernate 4 ändert diesbezüglich nichts. Ich sehe wirklich nicht, wie unelegant das ist.
JB Nizet
5

Ich suche nach einer Möglichkeit, eine Viele-zu-Viele-Zuordnungstabelle mit zusätzlichen Spalten mit Ruhezustand in der XML-Dateikonfiguration zuzuordnen.

Angenommen, Sie haben zwei Tabellen 'a' & 'c' mit einer Zuordnung von vielen zu vielen mit einer Spalte mit dem Namen 'extra'. Da ich kein vollständiges Beispiel gefunden habe, ist hier mein Code. Hoffe es wird helfen :).

Hier sind zuerst die Java-Objekte.

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;
    }

}

Jetzt müssen wir die Zuordnungstabelle erstellen. Der erste Schritt besteht darin, ein Objekt zu erstellen, das einen komplexen Primärschlüssel darstellt (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;
    }
}

Nun erstellen wir das Assoziationsobjekt selbst.

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);
    }
}

Zu diesem Zeitpunkt ist es an der Zeit, alle unsere Klassen mit der XML-Konfiguration im Ruhezustand abzubilden.

A.hbm.xml und C.hxml.xml (leise das gleiche).

<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>

Und dann die Zuordnungszuordnungsdatei 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>

Hier ist das zu testende Codebeispiel.

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
quelle
2

Wie bereits erwähnt, müssen Sie bei JPA zwei OneToMany-Zuordnungen anstelle einer einzelnen ManyToMany-Beziehung verwenden, um zusätzliche Spalten zu erhalten. Sie können auch eine Spalte mit automatisch generierten Werten hinzufügen. Auf diese Weise kann es bei Bedarf als Primärschlüssel der Tabelle verwendet werden.

Zum Beispiel sollte der Implementierungscode der zusätzlichen Klasse folgendermaßen aussehen:

@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;
    }

}
ingitaly
quelle