@ManyToMany etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
@ManyToMany etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

11 Şubat 2020 Salı

JPA ManyToMany İlişki - Ara Tablo Kullanan

Giriş
Açıklaması şöyle
Always use @JoinColum on unidirectional one-2-many relationships, otherwise JPA will create a linking table instead of storing foreign keys in many side of the relationship.
Şeklen şöyle


Örnek
Şöyle yaparız
@Entity
public class Person {
 ...
  @ManyToMany
  private List<Address> addresses;
}

@Entity
public class Address {
  ...
}
Tablo yapısı şöyledir
Person -> Ara Tablo -> Address.
Sadece Person sınıfında @ManyToMany anotasyonu kullanılır. Address sınıfında kullanılmaz. 

Silme
- Eğer Person'dan Address silinirse sadece ara tablo kaydı silinir. Address silinmez.
- Eğer Person silinirse hem Person hem de ara tablo kaydı silinir. Address silinmez.

Owning Side
Owning side için açıklama şöyle
When implementing many-to-many bi-directional associations in JPA, we must define an “owning side.” To mark an entity as the “owning side,” we should mark its @ManyToMany collection with the @JoinTable annotation. It means that Hibernate will track changes in this collection and update the junction table in the database accordingly.

Before selecting an entity to “own” the association, we should consider data usage. For our application, we can assume that users will search for posts and create and update them more often than tags. Therefore, setting the Post entity as the “owning side” makes more sense. So, if we look at the Post entity code above, we’ll see that the tags collection is annotated with @JoinTable. This is precisely what we need.
Synchronized Methods
Açıklama şöyle
One more thing that we need to remember is that we need to keep the bidirectional association synchronized. E.g., when we add a Tag instance to a tags set in a PostWe should not forget to add this Post to the corresponding posts set in the Tag entity. The same logic applies to the Tag removal process. To implement this approach, we add synchronization methods to both entities.
Bu örnek için tam açıklama şöyle
  1. In the synchronization methods, consider usage in a non-transactional context. Use Hibernate.isInitialized to check if a collection on the other side is initialized.
  2. Prefer Set collection type to define a many-to-many association – Hibernate generates better SQL for data updates. Remember that synchronization methods cause collections initialization for both sides in the association.
  3. Consider using List instead of Set collection type in the many-to-many association on the “non-owning” association side to save an extra select query.
  4. Remember that using List as a collection type on the “owning” side in many-to-many associations causes complete links recreation in the junction table.
  5. Synchronization methods for the “owning” side entity are optional, but we’d recommend implementing them for the “non-owning” side with all precautions listed above.

Şöyle yaparız
@Entity @Table(name = "post") public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; @Column(name = "title") private String title; @ManyToMany @JoinTable(name = "post_tag", joinColumns = @JoinColumn(name = "post_id"), inverseJoinColumns = @JoinColumn(name = "tag_id")) private Set<Tag> tags = new LinkedHashSet<>(); public void addTag(Tag tag) { tags.add(tag); if (Hibernate.isInitialized(tag.getPosts())) { tag.getPosts().add(this); } } public void removeTag(Tag tag) { tags.remove(tag); if (Hibernate.isInitialized(tag.getPosts())) { tag.getPosts().remove(this); } } }
Diğer taraf için şöyle yaparız
@Entity @Table(name = "tag") public class Tag { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; @Column(name = "name") private String name; @ManyToMany(mappedBy = "tags") private List<Post> posts = new ArrayList<>(); public void addPost(Post post) { posts.add(post); if (Hibernate.isInitialized(post.getTags())) { post.getTags().add(this); } } public void removePost(Post post) { posts.remove(post); if (Hibernate.isInitialized(post.getTags())) { post.getTags().remove(this); } } }
Spring kullanıyorsak şöyle yaparız
public interface PostRepository extends JpaRepository<Post, Long> { @EntityGraph(attributePaths="tags") Optional<Post> findPostWithTagsById(Long id); } public interface TagRepository extends JpaRepository<Tag, Long> { }




19 Şubat 2019 Salı

JPA @ManyToMany İlişki - Ara Tablo Kullanmayan

Giriş
ManyToMany bidirectional ilişkide bir sınıfı sadece @ManyToMany (mappedBy) şeklide diğer sınıf ise @ManyToMany ve @JoinTable olarak tanımlanır.

Bu ilişki EAGER. Açıklaması şöyle
OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER
Set vs List
Açıklaması şöyle
Use Set instead of List while defining many-2-many relations. Because if you delete or insert an entity object in this List then instead of one query to delete or insert, spring data JPA will drop all elements of List and then reinsert the concerning objects again.

Veritabanı Açısından Owner Taraf
@JoinTable anotasyonu tanımlayan sınıf veri tabanı açısından owner taraftır.  Önce bu sınıf kaydedilir. Hani tarafın owner olacağının seçimi bir muamma. Her iki tarafı owner yapmak mümkün. Açıklaması şöyle.
In the case of ManytoMany relationships in bidirectional scenario the Owner of the relationship can be selected arbitrarily, but having in mind the purpose you should select the entity that makes more sense to retrieve first or the one that is more used according to your purpose. You only need to remember that Many always need to be the owning side in ManyToOne scenarios.
Örnek
Şöyle yaparız. Veritabanı açısından owner taraf Event tablosudur.
@Entity
public class Event {
  @ManyToMany(fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST,CascadeType.MERGE})
  @JoinTable(
        name = "event_registrations",
        joinColumns = @JoinColumn(name="event_id", referencedColumnName = 
        "id"),
        inverseJoinColumns = @JoinColumn(name = "user_id", 
        referencedColumnName = "id"))
  private List<User> userList;
  ...
}
Diğer tarafta şöyle yaparız. Kod açısından ise owner tarafa mappedBy anotasyonunu kullanan User nesnesidir.
@Entity
public class User {
  @ManyToMany(fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST,CascadeType.MERGE},
        mappedBy = "userList")
  private List<Event> eventRegistrations;
  ...
}

cascade Alanı
Örnek
Owning side şöyle olsun.
@Entity
@Table(name = "product_details")
public class ProductDetails {

  private int productId;
  private String productName;

  @ManyToMany(cascade = { CascadeType.ALL })
  @JoinTable(
    name = "order_product", 
    joinColumns = { @JoinColumn(name = "productId") }, 
    inverseJoinColumns = { @JoinColumn(name = "orderId") }
  )
  private Collection<OrderDetails> orders = new ArrayList();
  ...
}
Diğer taraf şöyle olsun.
@Entity
@Table(name = "order_details")
public class OrderDetails {

  private int orderId;
  private String orderName;
  @ManyToMany(mappedBy = "orders")
  private Collection<ProductDetails> products = new ArrayList();
  ...
}
Örnek
Şöyle yaparız.
@ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.MERGE})
@JoinTable(
        name = "profile_skills",
        joinColumns = @JoinColumn(name = "profile_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(name = "skill_id", referencedColumnName = "id")
)
private Set<Skill> skills;
fetch Alanı
Örnek
Şöyle yaparız
public class Course {

    @Id
    @GeneratedValue
    private int id;

    @ManyToMany(mappedBy = "courses", fetch = FetchType.EAGER,cascade = 
    {CascadeType.ALL})
    private Set<Student> students;
}
Şöyle yaparız
@Entity
public class Student {

@Id
@GeneratedValue
private int id;


@ManyToMany(cascade = {CascadeType.ALL})
@JoinTable(name = "stud_course", joinColumns ={ @JoinColumn(name = "student_id",
  referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "course_id",
  referencedColumnName = "id")})
private Set<Course> courses;
targetEntity Alanı
Örnek
A sınıfı şöyle olsun
@Entity
public class A {

  @Column(name = "a_id")
  String id;
  
  @ManyToMany(targetEntity = B.class,
              cascade = { CascadeType.PERSIST, CascadeType.MERGE })
  @JoinTable(name = "at",
             joinColumns = @JoinColumn(name = "a_id"),
             inverseJoinColumns = @JoinColumn(name = "b_id"))
  Set<Report> bSet;
}
joinColumn A tablosundaki id alanıdır.
inverseJoinColumn ise B tablosundaki id alanıdır.

B sınıfı şöyle olsun. Ya da şöyle olsun
@Entity
public class B {
  @Column(name = "b_id")
  String id;

  @ManyToMany(mappedBy = "bSet")
  Set<A> aSet;

}
Set yerine list kullanılabilir.