Mapowanie tabeli asocjacji wiele do wielu z dodatkowymi kolumnami

131

Moja baza danych zawiera 3 tabele: Encje użytkowników i usług mają relację wiele do wielu i są połączone z tabelą SERVICE_USER w następujący sposób:

USERS - SERVICE_USER - SERVICES

Tabela SERVICE_USER zawiera dodatkową kolumnę BLOCKED.

Jaki jest najlepszy sposób wykonania takiego mapowania? To są moje klasy Entity

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

Postępowałem zgodnie z tym przykładem http://giannigar.wordpress.com/2009/09/04/m ... using-jpa / Oto kod testowy:

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

Problem polega na tym, że hibernacja utrzymuje się w obiekcie User i UserService. Brak powodzenia z obiektem CmsService

Próbowałem użyć pobierania EAGER - brak postępu

Czy możliwe jest osiągnięcie oczekiwanego zachowania przy użyciu powyższego mapowania?

Może istnieje bardziej elegancki sposób mapowania wielu do wielu tabeli złączeń z dodatkową kolumną?

archie_by
źródło

Odpowiedzi:

192

Ponieważ tabela SERVICE_USER nie jest czystą tabelą łączenia, ale ma dodatkowe pola funkcjonalne (zablokowane), należy zamapować ją jako jednostkę i rozłożyć wiele powiązań między użytkownikiem a usługą na dwa powiązania OneToMany: jeden użytkownik ma wiele usług użytkownika, a jedna usługa ma wiele usług użytkownika.

Nie pokazałeś nam najważniejszej części: mapowania i inicjalizacji relacji między twoimi encjami (tj. Częścią, z którą masz problemy). Więc pokażę ci, jak to powinno wyglądać.

Jeśli sprawisz, że relacje będą dwukierunkowe, powinieneś mieć

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

Jeśli nie umieścisz kaskady w swoich relacjach, musisz zachować / ocalić wszystkie byty. Chociaż tylko strona będąca właścicielem relacji (w tym przypadku strona UserService) musi być zainicjowana, dobrą praktyką jest również upewnienie się, że obie strony są spójne.

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
źródło
2
Dodam tylko ... Chociaż jest to moim zdaniem najlepszy sposób (ze względów wydajnościowych zawsze wolę mapować rzecz będącą właścicielem FK jako podmiot), to nie jest tak naprawdę jedyny sposób. Możesz również odwzorować wartości z tabeli SERVICE_USER jako komponent (co JPA nazywa osadzalnym) i użyć jednej @ElementCollectionz (lub obu) jednostek User i Service.
Steve Ebersole
6
A co z kluczem podstawowym tabeli UserService? Powinna to być kombinacja kluczy obcych użytkownika i usługi. Czy to jest zmapowane?
Jonas Gröger
24
Nie zrobiłbym tego. Klucze złożone są bolesne, nieefektywne, a firma Hibernate odradza używanie kluczy złożonych. Po prostu użyj automatycznie wygenerowanego identyfikatora, jak w przypadku każdego innego podmiotu, a życie będzie znacznie prostsze. Aby zapewnić [userFK, serviceFK]niepowtarzalność, użyj unikalnego ograniczenia.
JB Nizet,
1
@GaryKephart: zadaj własne pytanie, używając własnego kodu i własnego mapowania.
JB Nizet
1
@gstackoverflow: Hibernate 4 nic nie zmienia w tym względzie. Naprawdę nie rozumiem, jak to jest nieeleganckie.
JB Nizet
5

Szukam sposobu na zmapowanie tabeli skojarzeń wiele do wielu z dodatkowymi kolumnami z hibernacją w konfiguracji plików xml.

Zakładając, że mamy dwie tabele „a” i „c” z powiązaniami wiele do wielu z kolumną o nazwie „extra”. Ponieważ nie znalazłem żadnego pełnego przykładu, oto mój kod. Mam nadzieję, że to pomoże :).

Najpierw są obiekty 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;
    }

}

Teraz musimy utworzyć tabelę asocjacji. Pierwszym krokiem jest utworzenie obiektu reprezentującego złożony klucz podstawowy (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;
    }
}

Teraz stwórzmy sam obiekt skojarzenia.

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

W tym momencie nadszedł czas, aby zmapować wszystkie nasze klasy z konfiguracją hibernacji xml.

A.hbm.xml i C.hxml.xml (wycisz to samo).

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

Następnie plik mapowania skojarzeń 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>

Oto przykładowy kod do przetestowania.

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
źródło
1

Jak wspomniano wcześniej, w przypadku JPA, aby mieć szansę na dodatkowe kolumny, należy użyć dwóch skojarzeń OneToMany zamiast jednej relacji ManyToMany. Możesz także dodać kolumnę z wartościami generowanymi automatycznie; w ten sposób może działać jako klucz podstawowy tabeli, jeśli jest przydatny.

Na przykład kod implementacji dodatkowej klasy powinien wyglądać następująco:

@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
źródło