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

8 Kasım 2023 Çarşamba

SpringData JPA ScrollAPI

Giriş
ScrollAPI aslında Spring Data Commons ile geliyor. SpringData JPA bu bağımlılığı getirdiği için bir şey yapmaya gerek yok

Offset-based scrolling
OffsetScrollPosition  veya WindowIterator sınıfı ile kullanılır. Açıklaması şöyle
Offset scrolling works like pagination, which returns expected results by skipping a certain number of records from a large result. While we only see a portion of the requested results, the server needs to build the full result, which causes additional load.

Örnek
Şöyle  yaparız. Ben sadece bazı açıklamalar ekledim. Window sınıfı offsetleri takip ediyor. Kullanım olarak Window sınıfı Iterator gibi. Window.hasNext() çağrısı yapmak lazım
public List<BookReview> getBooksUsingOffset(String rating) {
  // To keep track of the position in the result set.
  OffsetScrollPosition offset = ScrollPosition.offset();

  // Retrieves the first 5 books with the specified rating
  Window<BookReview> bookReviews = bookRepository.findFirst5ByBookRating(rating, offset);
  List<BookReview> bookReviewsResult = new ArrayList<>();
  do {
    // Adds each BookReview to result
    bookReviews.forEach(bookReviewsResult::add);
    // Retrieves the next batch of 5 books with the specified rating
    bookReviews = bookRepository
      .findFirst5ByBookRating(
        rating, 
        (OffsetScrollPosition) bookReviews.positionAt(bookReviews.size() - 1));
  } while (!bookReviews.isEmpty() && bookReviews.hasNext());

   return bookReviewsResult;
}
Örnek
Şöyle  yaparız.   WindowIterator + OffsetScrollPosition  kullanılır. Window.hasNext() çağrısı yapmaya gerek yok
public List<BookReview> getBooksUsingOffSetFilteringAndWindowIterator(String rating) {
  WindowIterator<BookReview> bookReviews = WindowIterator.of(position -> 
    bookRepository
      .findFirst5ByBookRating("3.5", (OffsetScrollPosition) position))
      .startingAt(ScrollPosition.offset());

  List<BookReview> bookReviewsResult = new ArrayList<>();
  bookReviews.forEachRemaining(bookReviewsResult::add);

  return bookReviewsResult;
}
Keyset-Filtering
Açıklaması şöyleWindowIterator + KeysetScrollPosition  kullanılır
Keyset filtering helps the retrieval of a subset of results using the built-in capabilities of the database aiming to reduce the computation and IO requirements for individual queries.
The database only needs to construct smaller results from the given keyset position without materializing a large full result
Örnek
Şöyle yaparız
public List<BookReview> getBooksUsingKeySetFiltering(String rating) {
  WindowIterator<BookReview> bookReviews = WindowIterator.of(position -> 
    bookRepository
      .findFirst5ByBookRating(rating, (KeysetScrollPosition) position))
      .startingAt(ScrollPosition.keyset());
    
  List<BookReview> bookReviewsResult = new ArrayList<>();
  bookReviews.forEachRemaining(bookReviewsResult::add);

  return bookReviewsResult;
}

5 Eylül 2023 Salı

SpringData JPA Hibernate Dirty Check

Örnek
Elimizde şöyle bir kod olsun
@Service
public class UserService {

  @Autowired
  private UserRepository userRepository;
  ...
}
Example 1
Şöyle yaparız
// 1. Regular update within a transaction (without explicit saving)
@Transactional
public void updateName(Long userId, String newName) {
  User user = userRepository.findById(userId)alo .orElseThrow(() -> new EntityNotFoundException("User not found"));
  user.setName(newName);
  // Thanks to dirty checking, changes will be saved automatically upon transaction // completion.
}
Example 2
Şöyle yaparız
// 2. Data retrieval without a transaction (changes won't be saved automatically)
public void nonTransactionalUpdateName(Long userId, String newName) { User user = userRepository.findById(userId) .orElseThrow(() -> new EntityNotFoundException("User not found")); user.setName(newName); // Changes will not be saved as the method is not within a transaction. }
Example 3
Şöyle yaparız
// 3. Explicitly saving changes within a transaction
@Transactional public void explicitSaveAfterUpdate(Long userId, String newName) { User user = userRepository.findById(userId) .orElseThrow(() -> new EntityNotFoundException("User not found")); user.setName(newName); // Explicitly saving changes, although it's not required in this context. userRepository.save(user); }
Example 3 içi açıklama şöyle
In this example, explicit saving is not required because Hibernate’s “dirty checking” considers changes in managed entities and automatically synchronizes them with the database at the end of the transaction.

If you still call userRepository.save(user); for a managed entity, in most cases, it won’t lead to any direct negative consequences, but there are a few things to note:

Performance: The save call can potentially trigger additional operations, such as merging entities, which may be less efficient than simply waiting for automatic saving of changes at the end of the transaction.

Code readability: Explicitly calling save for entities that are already in the Hibernate management context can confuse developers unfamiliar with the code context. They might wonder why explicit saving is happening here.

Save behavior: In practice, save in Spring Data JPA works as persist or merge depending on the state of the entity. If the entity is new, it will be saved as a new record, if the entity already exists (for example, it was fetched from the database), it will be merged. In most scenarios, this won’t cause problems, but knowing this behavior is essential for understanding some more complex cases.

Therefore, although explicitly saving managed entities is not an error, it is better to avoid it unless there is a specific need to simplify the code and improve its performance.
Example 4
Şöyle yaparız
// 4. Creating a new entity and saving it
@Transactional
public void createUser(String name, String email) {
  User user = new User();
  user.setName(name);
  user.setEmail(email);
  userRepository.save(user);  // Save is necessary here as the entity is new.
}
Example 5
Şöyle yaparız
// 5. Retrieving data in read-only mode
@Transactional(readOnly = true) public List<User> getAllUsers() { return userRepository.findAll(); // As the method is wrapped in @Transactional with readOnly=true, // any attempts to change entities within this method will not result in their saving // to the DB. }
Example 6
Şöyle yaparız
// 6. Explicitly detaching an entity from the persistence context and then saving it
@Transactional public void detachAndUpdate(Long userId, String newName) { User user = userRepository.findById(userId)alo .orElseThrow(() -> new EntityNotFoundException("User not found")); userRepository.detach(user); // Detaching the entity from the persistence context. user.setName(newName); // Now we need to explicitly save changes as the entity is detached. userRepository.save(user); }

20 Ağustos 2023 Pazar

SpringData JPA @EntityGraph.type FETCH + @NamedEntityGraph Kullanımı - N+1 Select Problem İçindir

Giriş
Açıklaması şöyle. Yani  @NamedEntityGraph.attributeNodes anotasyonu ile belirtilenler EAGER yüklenir. Geri kalan her şey LAZY yüklenir.
..., attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated as FetchType.LAZY

Örnek - @NamedEntityGraph.attributeNodes 
Şöyle yaparız. Burada Publication sınıfı ve ona ait Article sınıfları EAGER yükleniyor.
@Entity
@Table(name = "publication")
@NamedEntityGraph(name="publication-articles-graph",
  attributeNodes = @NamedAttributedNode(value ="articles"))
public class Publication {
  ...
  @OneToMany(cascade = CascadeType.ALL)
  @JoinColumn(name = "publicationId")
  private List<Article> articled;
}

public interface PublicationRepository extends JpaRepository<Publication,String> {
  @EntityGraph(type = EntityGraph.EntityGraphType.FETCH,
    value = "publication-articles-graph")
  List<Publication> findByCategory(String category);
}
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 Book ve ona ait Author nesneleri EAGER yüklenir
@Entity @NamedEntityGraph( name = "Book.author", attributeNodes = @NamedAttributeNode("author") ) public class Book { @ManyToOne private Author author; // ... } @Repository public interface BookRepository extends JpaRepository<Book, Long> { @EntityGraph("Book.author") List<Book> findAll(); }

10 Nisan 2023 Pazartesi

SpringData JpaRepository.saveAndFlush metodu - Kullanmayın

Giriş
Normalde JPA sağlayıcısı save() işlemini hemen veri tabanına göndermez. Optimizasyon amaçlı biraz bekletir. saveAndFlush () JPA sağlayıcısını işlemin hemen veri tabanında uygulanmasını sağlar.

İmzası şöyle.
PaymentMethod saveAndFlush(PaymentMethods entity);
Örnek
Tabloya yapılan işlemin hemen görülmesi için kullanılır. Şöyle yaparız.
@Transactional
public void saveAndGenerateResult(Data data) {
    saveDataInTableA(data.someAmountForA);
    saveDataInTableB(data.someAmountForB);
    callAnAggregatedFunction(data);
}

public void saveDataInTableA(DataA a) {
    tableARepository.saveAndFlush(a);
}

public void saveDataInTableA(DataB b) {
    tableBRepository.saveAndFlush(b);
}

public void callAnAggregatedFunction() {
  // Do something based on the data saved from the beginning in Table A and Table B
}

10 Ocak 2023 Salı

SpringData JpaRepository.findById - Anti-Pattern Kullanmayın

Giriş
@Id olarak işaretli alana göre arama yapar. 

Problem1 - findById() metodunun Döngü İçinde Kullanılması
Eğer birden fazla nesne almak istiyorsak findAllById() kullanılır
Örnek
Şöyle yaparız
@Service
public class UserService {

  @Autowired
  private UserRepository userRepository;

  public List<User> getUsersByIds(List<Long> ids) {
    return userRepository.findAllById(ids);
  }
}
Üretilen SQL şöyle
SELECT * FROM User user WHERE user.id IN :ids
Problem 2
Açıklaması şöyle. Yani findById() lazy değildir. OneToMany, ManyToOne gibi ilişkilerde diğer taraftaki nesneleri de getirir.
Another issue with findById is that it can lead to the creation of many unnecessary objects. Each time you call findById, Spring Data JPA creates a new entity object, even if the entity is already in the persistence context. This can lead to a significant increase in memory usage and garbage collection overhead.
findById() yerine getReferenceById() tercih edilmeli
getReferenceById() metodunun kodu şöyle. Yeni bir proxy dönüyor
public T getReferenceById(ID id) {
    Assert.notNull(id, "The given id must not be null!");
    return this.em.getReference(this.getDomainClass(), id);
}
getReferenceById metodunun hikayesi şöyle. Yani önce getOne() -> daha sonra getById() -> daha sonra getReferenceById() haline gelmiş.
Initially, Spring Data JPA offered a getOne method that we should call in order to get an entity Proxy. But we can all agree that getOne is not very intuitive.

So, in the 2.5 version, the getOne method got deprecated in favor of the getById method alternative, that’s just as unintuitive as its previous version.

Neither getOne nor getById is self-explanatory. Without reading the underlying Spring source code or the underlying Javadoc, would you know that these are the Spring Data JPA methods to call when you need to get an entity Proxy?

Therefore, in the 2.7 version, the getById method was also deprecated, and now we have the getReferenceById method instead,...
Örnek - OneToMany İlişki
Elimizde şöyle bir kod olsun. Department nesnesi Employee nesnesine OneToMany ile bağlı. Employee nesnesi de Department nesnesine ManyToOne ile bağlı
@Entity
@Table(name = "departments")
public class Department {

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

  private String name;

  @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
  private List<Employee> employees = new ArrayList<>();

  // getters and setters
}

@Entity
@Table(name = "employees")
public class Employee {

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

  private String name;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "department_id")
  private Department department;

  // getters and setters
}
Şöyle kullanalım
@Service
public class DepartmentService {

  @Autowired
  private DepartmentRepository departmentRepository;

  public Department getDepartmentById(Long id) {
    return departmentRepository.findById(id)
    .orElseThrow(() -> new EntityNotFoundException("Department not found with id " + id));
  }
}
Üretilen SQL şöyle
SELECT * FROM departments WHERE id = ?

SELECT * FROM employees WHERE department_id = ?
Düzeltmek için şöyle yaparız. Burada getOne() kullanılıyor ancak getReferenceById() de aynı şey zaten
@Service
public class DepartmentService {

  @Autowired
  private DepartmentRepository departmentRepository;

  public Department getDepartmentReferenceById(Long id) {
    return departmentRepository.getOne(id);
  }
}
Üretilen SQL şöyle
SELECT department FROM Department department WHERE department.id = ?
Açıklaması şöyle
Note that the actual SQL query to fetch the related Employee objects will only be executed when we access the employees property of the Department object, or call a method on one of its related employees that requires database access.
Örnek - ManyToOne İlişki
Elimizde şöyle bir kod olsun
@Entity
@Table(name = "post")
public class Post {
 
  @Id
  private Long id;
 
  private String title;
 
  @NaturalId
  private String slug;
}

@Entity
@Table(name = "post_comment")
public class PostComment {
 
  @Id
  @GeneratedValue
  private Long id;
 
  private String review;
 
  @ManyToOne(fetch = FetchType.LAZY)
  private Post post;
}
Şöyle kullanalım. Aslında yeni bir PostComment nesnesi ekliyoruz ve bunun için ilişkili olduğu Post nesnesine ihtiyaç var.
@Transactional(readOnly = true)
public class PostServiceImpl implements PostService {
 
  @Autowired
  private PostRepository postRepository;
 
  @Autowired
  private PostCommentRepository postCommentRepository;
 
  @Transactional
  public PostComment addNewPostComment(String review, Long postId) {           
    PostComment comment = new PostComment()
      .setReview(review)
      .setPost(postRepository.findById(postId)
        .orElseThrow(
                   ()-> new EntityNotFoundException(
                     String.format("Post with id [%d] was not found!", postId)
                    )
                )
            );
 
postCommentRepository.save(comment);
    return comment;
  }
}
Üretilen SQL şöyle
SELECT
  post0_.id AS id1_0_0_,
  post0_.slug AS slug2_0_0_,
  post0_.title AS title3_0_0_
FROM
  post post0_
WHERE
  post0_.id = 1
 
SELECT nextval ('hibernate_sequence')
 
INSERT INTO post_comment (
  post_id,
  review,
  id
)
VALUES (
  1,
  'Best book on JPA and Hibernate!',
  1
)
Kodu şöyle yapalım. Burada artık getReferenceById() kullanılıyor
@Transactional
public PostComment addNewPostComment(String review, Long postId) {
  PostComment comment = new PostComment()
    .setReview(review)
    .setPost(postRepository.getReferenceById(postId));
 
  postCommentRepository.save(comment);
 
  return comment;
}
Üretilen SQL şöyle
SELECT nextval ('hibernate_sequence')
 
INSERT INTO post_comment (
    post_id,
    review,
    id
)
VALUES (
    1,
    'Best book on JPA and Hibernate!',
    1
)




13 Aralık 2021 Pazartesi

SpringData JPA @Query Anotasyonu ve Named Parameter

Giriş
Named Parameter Queries  yavaş olabilir. Açıklaması şöyle
The major issue we might face with this parameterised approach is the query performance.The query might run well in your local environment, but suddenly when you start running in Production environment it starts taking too much of time.This is because of the dynamic binding of the parameters to the query by Spring JPA as it cannot determine the data type of the parameter its binding and has to run through all possible types.
@Param Anotasyonu
Java 8'den önce metod parametrelerinin SQL cümlesine atanması için @Param ile işaretli olması gerekir. Yoksa şöyle bir exception fırlatılır.
java.lang.IllegalArgumentException: Name for parameter binding must not be null or empty! For named parameters you need to use @Param for query method parameters on Java versions < 8.
Java 8'den itibaren derleyiciye -parameters seçeneği geçilir.

Eğer metod imzasında fazla parametre varsa exception fırlatılır.
Örnek
Elimizde şöyle bir kod olsun
@Repository
public interface ParentRepository extends JpaRepository<Parent, String> {

  @Query(value = "SELECT c FROM Child c where c.parent.id =:id")  
  public List<Child> findChildById(String id, Example example, Pageable pageable);

}
Exception olarak şunu alırız
Using named parameters for method public abstract 
java.util.List com.example.test.ParentRepository.findChildById
(java.lang.String,org.hibernate.criterion.Example,
 org.springframework.data.domain.Pageable)
but parameter 'Optional[example]' not found in annotated query 
'SELECT c FROM Child c where c.parent.id =:id'!
Örnek - select
Şöyle yaparız.
@Query("select p from Person p where p.name = :name and p.address=:address")
Person withNameAndAddressQuery(@Param("name")String name, @Param("address")String addr);
Örnek - select
Şöyle yaparız.
@Query("select t from TimeTable t where MONTH(t.date) =:month and YEAR(t.date) =:year")
List<TimeTable> findAll(@Param("month") Integer month, @Param("year") Integer year);
Örnek - count
Şöyle yaparız.
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("select count(u) > 0 from User u where u.email = :email")
  Boolean isEmailExist(@Param("email")String email);
}