JPA'da ManyToMany ilişkisine sahip varlık (ve karşılık gelen birleştirme tablosu satırları) nasıl kaldırılır?


91

Diyelim ki iki varlığım var: Grup ve Kullanıcı. Her kullanıcı birçok gruba üye olabilir ve her grup birçok kullanıcıya sahip olabilir.

@Entity
public class User {
    @ManyToMany
    Set<Group> groups;
    //...
}

@Entity
public class Group {
    @ManyToMany(mappedBy="groups")
    Set<User> users;
    //...
}

Şimdi bir grubu kaldırmak istiyorum (diyelim ki birçok üyesi var).

Sorun şu ki, bazı Gruplarda EntityManager.remove () 'i çağırdığımda, JPA sağlayıcısı (benim durumumda Hibernate) satırları birleştirme tablosundan kaldırmıyor ve yabancı anahtar kısıtlamaları nedeniyle silme işlemi başarısız oluyor. Kullanıcıda remove () çağrısı iyi çalışıyor (sanırım bunun ilişkinin sahiplenme tarafıyla bir ilgisi var).

Peki bu durumda bir grubu nasıl silebilirim?

Bulabilmemin tek yolu, gruptaki tüm kullanıcıları yüklemek, ardından her kullanıcı için mevcut grubu gruplarından kaldırmak ve kullanıcıyı güncellemektir. Ancak, sırf bu grubu silebilmek için gruptaki her kullanıcı için update () 'i çağırmak bana saçma görünüyor.

Yanıtlar:


86
  • İlişkinin sahipliği, "mappedBy" özelliğini ek açıklamaya yerleştirdiğiniz yere göre belirlenir. 'MappedBy' koyduğunuz varlık, sahibi OLMAYAN varlıktır. Her iki tarafın da sahip olma şansı yok. Bir 'kullanıcıyı sil' kullanım durumunuz yoksa Group, şu anda sahip olduğu için sahipliği varlığa taşıyabilirsiniz User.
  • Öte yandan, onu sormuyorsunuz, ama bilmeye değer bir şey var. groupsVe usersbirbiriyle birleştirilmez. Demek istediğim, Group1.users'tan User1 örneğini sildikten sonra User1.groups koleksiyonları otomatik olarak değişmiyor (bu benim için oldukça şaşırtıcı),
  • Sonuç olarak, sahibinin kim olduğuna karar vermenizi öneririm. UserSahibi olduğunu varsayalım . Daha sonra bir kullanıcıyı silerken ilişki kullanıcı grubu otomatik olarak güncellenecektir. Ancak bir grubu silerken, ilişkiyi şu şekilde silmeye özen göstermelisiniz:

entityManager.remove(group)
for (User user : group.users) {
     user.groups.remove(group);
}
...
// then merge() and flush()

Thnx! Ben de aynı sorunu yaşadım ve çözümünüz onu çözdü. Ama bu sorunu çözmenin başka bir yolu olup olmadığını bilmeliyim. Korkunç bir kod üretir. Neden em.remove (varlık) olamıyor ve hepsi bu?
Royi Freifeld

2
Bu perde arkasında optimize edilmiş mi? çünkü tüm veri kümesini sorgulamak istemiyorum.
Ced

1
Bu gerçekten kötü bir yaklaşım, ya bu grupta birkaç bin kullanıcınız varsa?
Sergiy Sokolenko

2
Bu ilişki tablosunu güncellemez, ilişki tablosunda bu ilişki ile ilgili satırlar kalır. Test etmedim ama bu potansiyel olarak sorunlara neden olabilir.
Saygın Doğu

@SergiySokolenko for döngüsünde hiçbir veritabanı sorgusu çalıştırılmaz. İşlem işlevi bırakıldıktan sonra tümü toplu olarak işlenir.
Kilves

42

Aşağıdakiler benim için çalışıyor. İlişkinin sahibi olmayan varlığa aşağıdaki yöntemi ekleyin (Grup)

@PreRemove
private void removeGroupsFromUsers() {
    for (User u : users) {
        u.getGroups().remove(this);
    }
}

Bunun çalışması için, Grubun güncellenmiş bir Kullanıcı listesine sahip olması gerektiğini unutmayın (bu otomatik olarak yapılmaz). bu nedenle, Kullanıcı varlığında grup listesine her Grup eklediğinizde, Grup varlığındaki kullanıcı listesine bir Kullanıcı da eklemelisiniz.


1
cascade = CascadeType.ALL, bu çözümü kullanmak istediğinizde ayarlanmamalıdır. Aksi takdirde mükemmel çalışır!
ltsstar

@damian Merhaba, çözümünüzü kullandım, ancak concurrentModficationException bir hatam var sanırım nedeni, Grubun güncellenmiş bir Kullanıcı listesine sahip olması olabilir. Çünkü kullanıcıya birden fazla grup
eklersem

@Damian Bunun çalışması için Grubun güncellenmiş bir Kullanıcı listesine sahip olması gerektiğini unutmayın (bu otomatik olarak yapılmaz). bu nedenle, Kullanıcı varlığındaki grup listesine her Grup eklediğinizde, Grup varlığındaki kullanıcı listesine bir Kullanıcı da eklemelisiniz. Lütfen bunu detaylandırır mısınız, bana bir örnek verin, teşekkürler
Adnan Abdul Khaliq

27

Olası bir çözüm buldum ama ... Bunun iyi bir çözüm olup olmadığını bilmiyorum.

@Entity
public class Role extends Identifiable {

    @ManyToMany(cascade ={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            joinColumns=@JoinColumn(name="Role_id"),
            inverseJoinColumns=@JoinColumn(name="Permission_id")
        )
    public List<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

@Entity
public class Permission extends Identifiable {

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            joinColumns=@JoinColumn(name="Permission_id"),
            inverseJoinColumns=@JoinColumn(name="Role_id")
        )
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

Bunu denedim ve işe yarıyor. Rolü sildiğinizde ilişkiler de silinir (ancak İzin varlıkları değil) ve İzni sildiğinizde, Rol ile ilişkiler de silinir (ancak Rol örneği değil). Ancak iki kez tek yönlü bir ilişkiyi haritalandırıyoruz ve her iki varlık da ilişkinin sahibidir. Bu, bazı sorunların Hazırda Bekletme'ye geçmesine neden olabilir mi? Ne tür sorunlar?

Teşekkürler!

Yukarıdaki kod, ilgili başka bir gönderiye aittir.


koleksiyon yenileme sorunları?
jelies

12
Bu doğru değil. Çift Yönlü ManyToMany, ilişkinin bir ve yalnızca bir sahip tarafına sahip olmalıdır. Bu çözüm, her iki tarafı da sahip yapıyor ve sonunda yinelenen kayıtlarla sonuçlanacak. Yinelenen kayıtlardan kaçınmak için Listeler yerine Setler kullanılmalıdır. Ancak Set kullanmak, tavsiye edilmeyen bir şey yapmak için sadece geçici bir çözümdür.
L. Holanda

4
o zaman bu kadar yaygın bir senaryo için en iyi uygulama nedir?
SalutonMondo

8

JPA / Hibernate çözümlerine alternatif olarak: Birleştirme tablonuzdaki foregin anahtarınızın veritabanı tanımında bir CASCADE DELETE cümlesi kullanabilirsiniz, örneğin (Oracle sözdizimi):

CONSTRAINT fk_to_group
     FOREIGN KEY (group_id)
     REFERENCES group (id)
     ON DELETE CASCADE

Bu şekilde, DBMS'nin kendisi grubu sildiğinizde grubu işaret eden satırı otomatik olarak siler. Ayrıca, silme işlemi Hibernate / JPA, JDBC'den, DB'de manuel olarak veya başka bir şekilde yapılsa da çalışır.

kademeli silme özelliği tüm ana DBMS (Oracle, MySQL, SQL Server, PostgreSQL) tarafından desteklenmektedir.


3

Değeri ne olursa olsun EclipseLink 2.3.2.v20111125-r10461 kullanıyorum ve @ManyToMany tek yönlü bir ilişkim varsa anlattığınız sorunu gözlemliyorum. Ancak, bunu iki yönlü bir @ManyToMany ilişkisi olarak değiştirirsem, bir varlığı sahip olmayan taraftan silebilirim ve JOIN tablosu uygun şekilde güncellenir. Bunların tümü, herhangi bir kademeli nitelik kullanılmadan yapılır.


2

Bu benim için çalışıyor:

@Transactional
public void remove(Integer groupId) {
    Group group = groupRepository.findOne(groupId);
    group.getUsers().removeAll(group.getUsers());

    // Other business logic

    groupRepository.delete(group);
}

Ayrıca, @Transactional (org.springframework.transaction.annotation.Transactional) yöntemini işaretleyin, bu tüm süreci tek bir oturumda gerçekleştirecek, biraz zaman kazandıracaktır.


1

Bu iyi bir çözüm. En iyi bölüm SQL tarafındadır - her seviyeye ince ayar yapmak kolaydır.

MySql ve MySql Workbench'i, Gerekli Yabancı ANAHTAR için silme üzerine Basamaklama yapmak için kullandım.

ALTER TABLE schema.joined_table 
ADD CONSTRAINT UniqueKey
FOREIGN KEY (key2)
REFERENCES schema.table1 (id)
ON DELETE CASCADE;

SO'ya hoş geldiniz. Senin biçimlendirme ve prova okuma geliştirmek için bu işe bir an ve göz atın: stackoverflow.com/help/how-to-ask
petezurich

1

Sonunda yaptığım şey bu. Umarım birisi onu faydalı bulabilir.

@Transactional
public void deleteGroup(Long groupId) {
    Group group = groupRepository.findById(groupId).orElseThrow();
    group.getUsers().forEach(u -> u.getGroups().remove(group));
    userRepository.saveAll(group.getUsers());
    groupRepository.delete(group);
}

0

Benim durumum için mappedBy'yi sildim ve tabloları şu şekilde birleştirdim:

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "user_group", joinColumns = {
        @JoinColumn(name = "user", referencedColumnName = "user_id")
}, inverseJoinColumns = {
        @JoinColumn(name = "group", referencedColumnName = "group_id")
})
private List<User> users;

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JsonIgnore
private List<Group> groups;

3
CascadeType.all'ı çoktan çoğa kullanmayın. Silindiğinde, A kaydının tüm ilişkili B kayıtlarını silmesine neden olur. Ve B kayıtları, ilişkili tüm A kayıtlarını siler. Ve bunun gibi. Büyük hayır-hayır.
Josiah

0

Bu, referans nedeniyle kullanıcıyı silemediğim benzer bir sorunda benim için çalışıyor. teşekkür ederim

@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.