20 Temmuz 2018 Cuma

SpringData JPA @EntityGraph Anotasyonu - N+1 Select Problem İçindir

Giriş
Şu satırı dahil ederiz
import org.springframework.data.jpa.repository.EntityGraph;
Açıklaması şöyle
There are two types of entityGraphs, Fetch and Load, which defines if the entities not specified by attributeNodes of entityGraphs should be fetched lazily or eagerly. Attributes specified by attributeNodes of entityGraph are always fetched eagerly.

FETCH TYPE: Attributes that are specified by attributeNodes of entityGraph are treated as FetchType.EAGER and rest of the attributes are treated as FetchType.Lazy.

LOAD TYPE: Attributes that are specified by attributeNodes of entityGraph are treated as FetchType.EAGER and rest of the attributes are treated according to their specified or default fetchTypes.
Bu bu anotasyon
1. @NamedEntityGraph ile ilişkilendirilebilir
@NamedEntityGraph.attributeNodes ile belirtilen alanlar FETCH veya LOAD olarak şekilde yüklenir.  @EntityGraph.value alanı kullanılacak @NamedEntityGraph anotasyonunu belirtir.


2. Tek başına kullanılabilir - Ad-hoc
Bu durumda yüklenecek alanlar belirtmek gerekir. @EntityGraph.attributePaths  şeklinde belirtilebilir.

3. @NamedEntityGraph İle Birlikte Kullanım

3.1 FETCH Kullanımı
Varsayılan type FETCH. 

Yani  @NamedEntityGraph.attributeNodes anotasyonu ile belirtilenler EAGER yüklenir. Geri kalan her şey LAZY yüklenir.


3.2 LOAD Kullanımı
Açıklaması şöyle
... attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated according to their specified or default FetchType.
Örnek - @NamedEntityGraph.attributeNodes 
Şöyle yaparız. @NamedEntityGraph.attributeNodes ile belirtilenler EAGER yüklenir. Geri kalan her şey kodda belirtildiği şekilde yüklenir.
@Entity
@Table(name = "books", schema = Constants.BENCHMARKS)
@NamedEntityGraph(name = "BookEntity.all",attributeNodes = @NamedAttributeNode("aliases"))
public class BookEntity {
  @Id
  private String isbn = UUID.randomUUID().toString();

  @Column(name = "title")
  private String title;

  @Column(name = "description")
  private String description;

  @CollectionTable(schema = Constants.BENCHMARKS, name = "book_tags",
    joinColumns = @JoinColumn(name = "book_isbn"))
  @ElementCollection(fetch = FetchType.EAGER)
  private List<String> aliases;
}
Kullanmak için @EntityGraph gerekir. Şöyle yaparız
@EntityGraph(value = "BookEntity.all", type = EntityGraphType.LOAD)
@Query("SELECT x FROM BookEntity x " + 
        "WHERE x IN (" + 
        "    SELECT y FROM BookEntity y" + 
        "    INNER JOIN y.aliases yt" + 
        "    WHERE yt IN (" + 
        "        :aliases" + 
        "    )" + 
        "    GROUP BY y" + 
        "    HAVING COUNT( DISTINCT yt) = (" + 
        "        :aliasesSize)" + 
        "    )")
Iterable<BookEntity> findAllByAliasesContainsAll(@Param("aliases") Collection<String>
aliases, @Param("aliasesSize") long aliasesSize);
Örnek
Elimizde şöyle bir kod olsun.
@Entity
@Table(name = "books", schema = Constants.BENCHMARKS)
@NamedEntityGraph(name = "BookEntity.all",attributeNodes = @NamedAttributeNode("aliases"))
public class BookEntity {
  @Id
  private String isbn = UUID.randomUUID().toString();

  @Column(name = "title")
  private String title;

  @Column(name = "description")
  private String description;

  @CollectionTable(schema = Constants.BENCHMARKS, name = "book_tags",
    joinColumns = @JoinColumn(name = "book_isbn"))
  @ElementCollection(fetch = FetchType.EAGER)
  private List<String> aliases;
}
Repository sınıfında @NamedEntity anotasyonunu kullanmak için şöyle yaparız.
@EntityGraph(value = "BookEntity.all", type = EntityGraphType.LOAD)
@Query("select b from BookEntity b where ...")
Iterable<BookEntity> findAllByAliasesContainsAll(@Param("aliases")
        List<String> aliases);
4. Tek Başına Kullanım - Ad-hoc

Örnek
Eğer @NamedEntityGraph kullanmak istemiyorsak şöyle yaparız. Burada  @EntityGraph.attributePaths kullanılıyor
public interface PublicationRepository extends JpaRepository<Publication,String> { @EntityGraph(type = EntityGraph.EntityGraphType.FETCH, attributePaths = "articles") List<Publication> findByCategory(String category); }
Çıkan SQL şöyledir
SELECT * FROM publication LEFT OUTER JOIN article ON publication.publication_id = article.publication_id WHERE publication.category = 'technology'
Örnek
Şöyle yaparız. Burada aslında hem tek başına kullanım hem de @NamedEntityGraph gösteriliyor.
public interface PostRepository extends JpaRepository<Post,Long> {

  // referencing a named entity graph
  @EntityGraph(value = "post-entity-graph", type = EntityGraph.EntityGraphType.LOAD)
  Optional<Post> findById(Long id);

  // ad-hoc entity graph
  @EntityGraph(attributePaths = { "subject","user" })
  List<Post> getAllByUserId(Long id);

}
Örnek
Şöyle yaparız
@Repository
public interface UserDetailsRepository extends JpaRepository<UserDetails, String> {

  // ad-hoc entity graph
  @EntityGraph(type = EntityGraph.EntityGraphType.FETCH, attributePaths = "addresses")
  List<UserDetails> findByContainingName(String text);

}
Üretilen SQL şöyle
SELECT userdetail0_.id as id1_1_0_, addresses1_.id as id1_0_1_, ... 
  FROM user_details userdetail0_
  LEFT OUTER JOIN address addresses1_ ON 
  userdetail0_.id=addresses1_.user_id WHERE userdetail0_.name LIKE ? ESCAPE ?
Aynı şeyi @NamedEntityGraph ile şöyle yapardık
@Entity
@Table(name = "user_details")
@Getter
@Setter
@NamedEntityGraph(name="user_details_entity_graph",
attributes= @NamedAttributedNode({"addresses"})
public class UserDetails {
  ...

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "userDetails")
  private List<Address> addresses;

}

@Repository
public interface UserDetailsRepository extends JpaRepository<UserDetails, String> {

  @EntityGraph(type = EntityGraph.EntityGraphType.FETCH,
value="user_details_entity_graph")
  List<UserDetails> findByContainingName(String text);

}

Hiç yorum yok:

Yorum Gönder