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

13 Kasım 2023 Pazartesi

JPA JPQL LEFT JOIN FETCH ve MultipleBagFetchException

Giriş
LEFT JOIN FETCH özellikle parent nesnede iki tane ilişki varsa problem olabiliyor. 

1. Set Döndürmek
Hibernate tarafından beklenen şey Set döndürmek. Set kullanırsak bile 
1. Full Cartesian Product problemi ortaya çıkar. 
2. Sayfalama çalışmıyor

Örnek-  Sayfalama Hatası
Elimizde şöyle bir kod olsun
@Entity
@Data
@NoArgsConstructor
@Table(name = "teams")
public class Team {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String name;

  @OneToMany(mappedBy = "team", 
    cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
  private Set<Member> members;

  @OneToMany(mappedBy = "team", 
    cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
  private Set<Milestone> milestones;

  // constructors, helper functions, etc ..
}

public interface TeamRepository extends JpaRepository<Team, Long> {

  @EntityGraph(attributePaths = {
            "members", "milestones"
  })
  Page<Team> findAll(Pageable pageable);
}
Açıklaması şöyle
When lookin at the response, it might lead you to believe that pagination is automatically handled when using the entity graph by passing in a Pageable to your query. However, it will give you a warning indicating that pagination is being performed in memory.
Çıktı şöyle
WARN 88609 --- [nio-8080-exec-3] org.hibernate.orm.query : HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
2. List Döndürmek
Bazen kodda ilişkiyi temsil etmek için Set yerine List kullanılıyor. Bu durumda 
org.hibernate.loader.MultipleBagFetchException diye bir exception alırız.
Açıklaması şöyle
The reason why a MultipleBagFetchException is thrown by Hibernate is that duplicates can occur, and the unordered List, which is called a bag in Hibernate terminology, is not supposed to remove duplicates.
Çözüm olarak 2 tane JPQL kullanılır. Açıklaması şöyle
This can be solved if you use Set instead of List. If you really want to stick with List, then you would have to divide into two queries eagerly loading each child and merge in the application code. Therefore, it may require you to change some code if you need to load multiple child entities and the decision will depend on the feature acceptance criteria.

Örnek
Elimizde Post sınıfı için şöyle bir kod olsun. Burada PostComment ve Tag ilişkileri için List kullanılıyor.
@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
 
@ManyToMany(
    cascade = {
        CascadeType.PERSIST,
        CascadeType.MERGE
    }
)
@JoinTable(
    name = "post_tag",
    joinColumns = @JoinColumn(name = "post_id"),
    inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private List<Tag> tags = new ArrayList<>();
Post nesnelerini çekmek için şöyle bir kod çalıştıralım. MultipleBagFetchException alırız
List<Post> posts = entityManager.createQuery("""
    select p
    from Post p
    left join fetch p.comments
    left join fetch p.tags
    where p.id between :minId and :maxId
    """, Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();
Çözüm olarak 2 tane JPQL kullanılır. Şöyle yaparız
List<Post> posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.comments
    where p.id between :minId and :maxId""", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
 
posts = entityManager.createQuery("""
    select distinct p
    from Post p
    left join fetch p.tags t
    where p in :posts""", Post.class)
.setParameter("posts", posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
Örnek
Elimizde şöyle bir kod olsun
public class Lesson {
  @Id
  private Long id;
    // ...other properties
  @OneToMany(mappedBy = "lesson", cascade = CascadeType.ALL)
  private List<Student> students;
  @OneToMany(mappedBy = "lesson", , cascade = CascadeType.ALL)
  private List<Guest> guests;
    // ...constructors, getters and setters
}
Yine 2 tane JPQL kullanılır. Şöyle yaparız
@Repository
public class LessonCriteriaRepositoryImpl implements LessonCriteriaRepository {

  @PersistenceContext
  private EntityManager entityManager;

  public List<Lesson> findAll() {
    //build first query for fetching students
    CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Lesson> criteriaQuery = builder.createQuery(Lesson.class);
    Root<Lesson> lesson = criteriaQuery.from(Lesson.class);
    lesson.fetch("students", JoinType.LEFT);
    criteriaQuery.select(lesson).distinct(true);
    TypedQuery<Lesson> query1 = entityManager.createQuery(criteriaQuery);
    List<Lesson> lessons = query1.getResultList();

    //build second query for fetching guests
    builder = entityManager.getCriteriaBuilder();
    criteriaQuery = builder.createQuery(Lesson.class);
    lesson = criteriaQuery.from(Lesson.class);
    lesson.fetch("guests", JoinType.LEFT);
    criteriaQuery.select(lesson).distinct(true).where(lesson.in(lessons));
    TypedQuery<Lesson> query2 = entityManager.createQuery(criteriaQuery);
    return query2.getResultList();
  }
}



12 Mart 2021 Cuma

JPA JPQL JOIN

Giriş
Eğer ilişki LAZY ise child nesnenin tamamını yüklemez.

Örnek
Elimizde şöyle bir JPQL olsun
FROM Employee emp
JOIN emp.department dep
SQL olarak şunu elde ederiz. Burada child nesnenin sütunlarının çekilmediği zaten görülebilir. Ancak bir proxy nesne yaratılır.
SELECT emp.*
FROM employee emp
JOIN department dep ON emp.department_id = dep.id
Örnek
JPQL JOIN kodla da elde edilebilir. Şöyle yaparız
@Entity
public class Author {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  ...  
  @OneToMany(fetch=FetchType.LAZY, mappedBy="author")
  private List<Book> books;
}

@Entity
public class Book {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  ...
  @ManyToOne
  private Author author;
}

public interface BookDataService extends JpaRepository<Book, Long>{
}

public interface AuthorDataService extends JpaRepository<Book, Long>{
}

List<Author> authors = authorDataService.findAll();
for (Author author : authors) {
    System.out.printf("Author: %s %s has %d books.%n",
        author.getFirstName(), author.getLastName(),
        author.getBooks().size());
}
N+1 Select Problemi
JOIN N+1 Select Problemine sebep olur. Bu duruma karşı
1. JOIN FETCH kullanılabilir
2. Hibernate FetchMode.SUBSELECT kullanılabilir

JPA JPQL JOIN FETCH - Inner Join Gibi Sadece Randevusu Olan Doktorları Yükler

Giriş
JOIN FETCH sadece randevusu olan doktorları yükler

Örnek - ManyToOne
Elimizde şöyle bir kod olsun
@Entity(name = "Post")
@Table(name = "post")
public class Post {
  @Id
  private Long id;
  private String title;
  //Getters and setters omitted for brevity
}

@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {
  @Id
  private Long id;
  @ManyToOne
  private Post post;
  private String review;
  //Getters and setters omitted for brevity
}
Şöyle yaparız
List<PostComment> comments = entityManager.createQuery("""
    select pc
    from PostComment pc
    join fetch pc.post p
    """, PostComment.class)
.getResultList();
 
for(PostComment comment : comments) {
  ...
}
Select cümlesi şöyle
SELECT
...
FROM
post_comment pc
INNER JOIN
post p ON pc.post_id = p.id
Örnek - ManyToMany
Şöyle yaparız
public class User implements UserDetails {
  ...
  @ManyToMany
  private Set<Role> roles;

}

public interface UserRepository extends JpaRepository<User,Long> {

  @Query("select u from User u join FETCH u.roles where u.email=:p_email")
  Optional<User> findByEmailWithRoles(@Param("p_email") String email);
}
Not
Örneklerde JPQL JOIN FETCH SQL'e çevrilince ortaya INNER JOIN çıkıyor. Aslında INNER JOIN tek SQL cümlesine sebep oluyor ancak tek yöntem değil. Benzer sonucu şöyle de elde edebilirdik. Burada iki tane SQL cümlesi çalıştırılıyor
SELECT *
FROM authors;
-- pretend this returns 3 authors

SELECT *
FROM books
WHERE author_id in (1, 2, 3); -- an array of the author's ids
Örnek
Elimizde şöyle bir kod olsun
public interface AuthorDataService extends JpaRepository<Author, Long>{
  @Query("select a from Author a join fetch a.books")
  List<Author> findAll();
}
SQL olarak şunu elde ederiz. Burada child nesnenin sütunlarının çekildiği görülebilir. 
select author0_.*,books1_.*, from author author0_ 
  inner join book books1_ on author0_.id=books1_.author_id
Eğer duplicate Author geliyorsa şöyle yaparız
public interface AuthorDataService extends JpaRepository<Author, Long>{
  @Query("select distinct a from Author a join fetch a.books")
  List<Author> findAll();
}
SQL olarak şunu elde ederiz. Burada child nesnenin sütunlarının çekildiği görülebilir. 
select distinct author0_.*,books1_.*, from author author0_ 
  inner join book books1_ on author0_.id=books1_.author_id
Örnek
Şöyle yaparız
SELECT d FROM Doctor d
JOIN FETCH d.appointments
Örnek
Elimizde şöyle bir JPQL olsun
FROM Employee emp
JOIN FETCH emp.department dep
SQL olarak şunu elde ederiz. Burada child nesnenin sütunlarının çekildiği görülebilir. 
SELECT emp.*, dept.*
FROM employee emp
JOIN department dep ON emp.department_id = dep.id
Query DSL Kütüphanesi
QueryDSL ve JOOQ kütüphaneleri ile join fetch yapabilmek mümkün.




15 Aralık 2020 Salı

JPA JPQL Join Çeşitleri

Giriş
- JOIN - INNER JOIN ile aynı. Kesişimi verir
- LEFT JOIN - LEFT OUTER JOIN ile aynı
- RIGHT JOIN
- CROSS JOIN
- FETCH - JPA'ya mahsus


1. JOIN
JPQL JOIN yazısına taşıdım. Sadece randevusu olan doktorları yükler. Eğer ilişki LAZY ise child nesnenin tamamını yüklemez.  N+1 Select Problemine sebep olur

2. LEFT JOIN
Randevusu olan veya olmayan tüm doktorları yükler.  Eğer ilişki LAZY ise child nesnenin tamamını yüklemez.  N+1 Select Problemine sebep olur

3. RIGHT JOIN
Örnek ver

4. CROSS JOIN
Bir örnek burada

5. FETCH
FETCH hem JOIN hem de LEFT JOIN ile birlikte kullanılabilir. 

Yani 
- JOIN FETCH veya
- LEFT JOIN FETCH 
şeklinde kullanabiliriz.

- Eğer ilişki LAZY ise child nesnenin tamamıyla yüklenmesini sağlar. Yani ilişkiyi EAGER haline getirir. İlişkinin EAGER hale gelmesi kendi içinde de "Cartesian Product Problem" ine sebep oluyor. Onu da aşağıda yazdım.

Yani ilişkinin şöyle olması gerekir. Burada LAZY sadece daha rahat anlaşılsın diye yazılı. Yoksa @OneToMany'nin varsayılan davranışı zaten böyle
@OneToMany(fetch = FetchType.LAZY)
private Set<Department> department;

5.1 JOIN FETCH - Inner Join Gibi
JOIN FETCH yazısına taşıdım

5.2 LEFT JOIN FETCH - Left Outer Join Gibi
LEFT JOIN FETCH yazısına taşıdım

16 Mart 2020 Pazartesi

JPA JPQL - Java Persistence Query Language

Giriş
JPQL'de veritabanındaki sütun isimleri değil, nesnemideki field isimler kullanılır. Örneğin order by yapmak isteyelim. Nesnemizin alan ismi creationTime olsun. Veritabanındaki sütun ismi ise CREATION_TIME olsun.
" ... order by c.creationTime"
şeklinde kullanırız.

floor
Açıklaması şöyle
There are several improvements and new capabilities in the querying capabilities. Jakarta Persistence QL (JPQL) has new numeric functions such as CEILINGFLOORROUND, and new functions to handle date and time (along with compatible methods via criteria API). For dates, for example, the SQL types LOCAL DATELOCAL TIME and LOCAL DATETIME can be retrieved by functions as java.time.LocalDatejava.time.LocalTime and java.time.LocalDateTime, respectively.

like
Şöyle yaparız.
public List<Employee> getFirstNamesLike(String firstName) {

  Query query = entityManager.createNativeQuery("SELECT em.* FROM
    spring_data_jpa_example.employee as em " +
    "WHERE em.firstname LIKE ?", Employee.class);
  query.setParameter(1, firstName + "%");
  return query.getResultList();
}
like
Şöyle yaparız.
SELECT e from MyEntity e WHERE LOWER(e.myAttribute) ...
select
DTO için şöyle yaparız.
@Query("select n from Client n where n.nom like :x")
Hibernate HQL ile arasındaki en büyük fark select n kelimesinin kullanılması. HQL'deki şu cümle
from Client n where n.nom=:x order by description asc"
şu hale geliyor
select n from Client n where n.nom=:x order by description asc"
select ve join
Şöyle yaparız.
@Query("select a from A a join a.bs b where b.prop1 = :prop1 and ...")
select ve left join
JPA JPQL Join yazısına taşıdım

select ve JPA Tuple
JPA Tuple için şöyle yaparız.
"select p.id as id, p.title as title from Post p where p.createdOn > :fromTimestamp"
select ve DTO Projection
1. DTO projection ne zaman gerekir ?
- Örneğin Eager OneToMany ilişki varsa ve bu ilişkiyi göndermek istemiyorsak
- Nesnenin tüm alanları yerine belli bir kısmını göndermek istiyorsak

2. JPQL Nasıl Olmalı
Önce bir DTO sınıfı tanımlanır. DTO sınıfı tüm alanları dahil eden bir constructor sağlamalıdır. Yani AllArgsConstructor gerekir çünkü JPQL içinde bu constructor kullanılır.

Daha sonra JPQL içinde DTO'nun tüm paket ismini kullanmak gerekir çünkü JPA sağlayıcısı bu paketi tanımamaktadır.

Örnek
DTO Projection için şöyle yaparız.
"select new com.foo.PostDTO (p.id as id, p.title) from Post p where
  p.createdOn > :fromTimestamp"
Örnek
Elimizde şöyle bir kod olsun.
public class ProgramDTO {

  private Long id;
  private String programTitle;
  private String description;
  private String programType;
  private String price;
}
EntityManager ile şöyle yaparız. Burada kısa olsun diye JPQL'de DTO'nun tüm paket ismini yazmadım.
entityManager
  .createQuery("select new ProgramDTO(p.id, p.programTitle, p.description,
 p.programType, p.price from Program p")
  .getResultList();