Memetakan tabel asosiasi banyak ke banyak dengan kolom ekstra

130

Basis data saya berisi 3 tabel: Entitas Pengguna dan Layanan memiliki hubungan banyak-ke-banyak dan digabungkan dengan tabel SERVICE_USER sebagai berikut:

PENGGUNA - SERVICE_USER - SERVICES

Tabel SERVICE_USER berisi kolom BLOKED tambahan.

Apa cara terbaik untuk melakukan pemetaan seperti itu? Ini adalah kelas Entitas saya

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

Saya mengikuti contoh ini http://giannigar.wordpress.com/2009/09/04/m ... using-jpa / Berikut adalah beberapa kode tes:

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

Masalahnya adalah hibernasi tetap ada objek Pengguna dan UserService satu. Tidak berhasil dengan objek CmsService

Saya mencoba menggunakan EAGER fetch - tidak ada kemajuan

Apakah mungkin untuk mencapai perilaku yang saya harapkan dengan pemetaan yang disediakan di atas?

Mungkin ada beberapa cara yang lebih elegan untuk memetakan banyak ke banyak tabel bergabung dengan kolom tambahan?

archie_by
sumber

Jawaban:

192

Karena tabel SERVICE_USER bukan tabel bergabung murni, tetapi memiliki bidang fungsional tambahan (diblokir), Anda harus memetakannya sebagai entitas, dan menguraikan banyak hingga banyak asosiasi antara Pengguna dan Layanan menjadi dua asosiasi OneToMany: Satu Pengguna memiliki banyak Layanan Layanan, dan satu Layanan memiliki banyak Layanan Pengguna.

Anda belum menunjukkan kepada kami bagian yang paling penting: pemetaan dan inisialisasi hubungan antara entitas Anda (yaitu bagian yang bermasalah dengan Anda). Jadi saya akan menunjukkan bagaimana seharusnya.

Jika Anda membuat hubungan dua arah, Anda harus memilikinya

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

Jika Anda tidak menaruh kaskade pada hubungan Anda, maka Anda harus bertahan / menyimpan semua entitas. Meskipun hanya sisi yang memiliki hubungan (di sini, sisi UserService) harus diinisialisasi, itu juga merupakan praktik yang baik untuk memastikan kedua belah pihak dalam koherensi.

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
sumber
2
Sekadar menambahkan .. Meskipun menurut saya ini cara terbaik (saya selalu lebih suka memetakan benda yang memiliki FK sebagai entitas untuk alasan kinerja), sebenarnya bukan satu-satunya cara. Anda juga dapat memetakan nilai-nilai dari tabel SERVICE_USER sebagai komponen (apa yang JPA sebut sebagai embeddable) dan menggunakan nilai @ElementCollectiondari salah satu (atau keduanya) Pengguna dan entitas Layanan.
Steve Ebersole
6
Bagaimana dengan kunci utama tabel UserService? Itu harus kombinasi pengguna dan kunci layanan asing. Apakah itu dipetakan?
Jonas Gröger
24
Saya tidak akan melakukannya. Kunci komposit menyakitkan, tidak efisien, dan Hibernate merekomendasikan untuk tidak menggunakan kunci komposit. Cukup gunakan ID yang dibuat secara otomatis seperti untuk entitas lain, dan hidup akan jauh lebih sederhana. Untuk memastikan keutuhan [userFK, serviceFK], gunakan kendala unik.
JB Nizet
1
@GaryKephart: ajukan pertanyaan Anda sendiri, dengan kode Anda sendiri dan pemetaan Anda sendiri.
JB Nizet
1
@ gstackoverflow: Hibernate 4 tidak mengubah apa pun dalam hal itu. Saya benar-benar tidak melihat bagaimana hal itu salah.
JB Nizet
5

Saya mencari cara untuk memetakan tabel asosiasi banyak-ke-banyak dengan kolom ekstra dengan hibernasi dalam konfigurasi file xml.

Diasumsikan memiliki dua tabel 'a' & 'c' dengan asosiasi banyak ke banyak dengan kolom bernama 'ekstra'. Karena saya tidak menemukan contoh lengkap, ini kode saya. Semoga ini bisa membantu :).

Pertama di sini adalah objek 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;
    }

}

Sekarang, kita harus membuat tabel asosiasi. Langkah pertama adalah membuat objek yang mewakili kunci primer yang kompleks (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;
    }
}

Sekarang mari kita buat objek asosiasi itu sendiri.

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

Pada titik ini, saatnya memetakan semua kelas kami dengan konfigurasi hibernate xml.

A.hbm.xml dan C.hxml.xml (quiete sama).

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

Dan kemudian file pemetaan asosiasi, 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>

Ini adalah contoh kode untuk diuji.

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

Seperti yang dikatakan sebelumnya, dengan JPA, untuk memiliki kesempatan untuk memiliki kolom tambahan, Anda perlu menggunakan dua asosiasi OneToMany, alih-alih hubungan ManyToMany tunggal. Anda juga dapat menambahkan kolom dengan nilai yang dibuat secara otomatis; dengan cara ini, ini bisa berfungsi sebagai kunci utama dari tabel, jika berguna.

Sebagai contoh, kode implementasi kelas tambahan akan terlihat seperti itu:

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